Skip to content
This repository has been archived by the owner on Jun 12, 2020. It is now read-only.

Commit

Permalink
Merge pull request #8 from tgstation/ConnectionTimeouts
Browse files Browse the repository at this point in the history
Specify the timeout
  • Loading branch information
Cyberboss authored Jul 10, 2018
2 parents f101869 + 4750ef4 commit 7964692
Show file tree
Hide file tree
Showing 13 changed files with 75 additions and 26 deletions.
23 changes: 20 additions & 3 deletions src/BSQL/API.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,18 @@ extern "C" {
}

BYOND_FUNC CreateConnection(const int argumentCount, const char* const* const args) noexcept {
if (argumentCount != 1)
if (argumentCount != 3)
return "Invalid arguments!";
if (!library)
return "Library not initialized!";
const auto& connectionType(args[0]);
const auto& asyncTimeoutStr(args[1]);
const auto& blockingTimeoutStr(args[2]);
Connection::Type type;

const auto asyncTimeout(std::atoi(asyncTimeoutStr));
const auto blockingTimeout(std::atoi(blockingTimeoutStr));

try {
std::string conType(connectionType);
if (conType == "MySql")
Expand All @@ -110,11 +116,19 @@ extern "C" {
return "Out of memory!";
}

if (asyncTimeout < 0)
return "asyncTimeout must be an unsigned integer!";
if (blockingTimeout < 0)
return "blockingTimeout must be an unsigned integer!";

if (asyncTimeout != 0 && blockingTimeout > asyncTimeout)
return "asyncTimeout must be greater than or equal to blockingTimeout";

if (!lastCreatedConnection.empty())
//guess they didn't want it
library->ReleaseConnection(lastCreatedConnection);

auto result(library->CreateConnection(type));
auto result(library->CreateConnection(type, static_cast<unsigned int>(asyncTimeout), static_cast<unsigned int>(blockingTimeout)));
if (result.empty())
return "Out of memory";

Expand Down Expand Up @@ -339,8 +353,11 @@ extern "C" {
auto op(connection->GetOperation(operationIdentifier));
if (!op)
return "Operation identifier does not exist!";
while (!op->IsComplete(false))
auto I(0U);
for (; !op->IsComplete(false) && I < connection->blockingTimeout * 1000; ++I)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
if (I >= connection->blockingTimeout * 1000)
return "Operation timed out!"; //match this with the api, too lazy to do it any other way
return nullptr;
}
catch (std::bad_alloc&) {
Expand Down
3 changes: 2 additions & 1 deletion src/BSQL/Connection.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "BSQL.h"

Connection::Connection(Type type, Library& library) :
Connection::Connection(Type type, Library& library, const unsigned int blockingTimeout) :
blockingTimeout(blockingTimeout),
library(library),
type(type),
identifierCounter(0)
Expand Down
3 changes: 2 additions & 1 deletion src/BSQL/Connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ class Connection {
SqlServer
};
public:
const unsigned int blockingTimeout;
const Type type;
protected:
Library & library;
std::map<std::string, std::unique_ptr<Operation>> operations;
private:
unsigned long long identifierCounter;
protected:
Connection(Type type, Library& library);
Connection(Type type, Library& library, const unsigned int blockingTimeout);

std::string AddOp(std::unique_ptr<Operation>&& operation);
public:
Expand Down
4 changes: 2 additions & 2 deletions src/BSQL/Library.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ bool Library::ReleaseConnection(const std::string& identifier) noexcept {
return connections.erase(identifier) > 0;
}

std::string Library::CreateConnection(Connection::Type type) noexcept {
std::string Library::CreateConnection(Connection::Type type, const unsigned int asyncTimeout, const unsigned int blockingTimeout) noexcept {
if (identifierCounter < std::numeric_limits<unsigned long long>().max()) {
try {
auto identifier(std::to_string(++identifierCounter));

switch (type)
{
case Connection::Type::MySql:
connections.emplace(identifier, std::make_unique<MySqlConnection>(*this));
connections.emplace(identifier, std::make_unique<MySqlConnection>(*this, asyncTimeout, blockingTimeout));
break;
case Connection::Type::SqlServer:
--identifierCounter;
Expand Down
2 changes: 1 addition & 1 deletion src/BSQL/Library.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Library {
Library() noexcept;
~Library() noexcept;

std::string CreateConnection(Connection::Type connectionType) noexcept;
std::string CreateConnection(Connection::Type connectionType, const unsigned int asyncTimeout, const unsigned int blockingTimeout) noexcept;
Connection* GetConnection(const std::string& identifier) noexcept;
bool ReleaseConnection(const std::string& identifier) noexcept;
void RegisterZombieThread(std::thread&& thread) noexcept;
Expand Down
9 changes: 6 additions & 3 deletions src/BSQL/MySqlConnectOperation.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
#include "BSQL.h"

MySqlConnectOperation::MySqlConnectOperation(MySqlConnection& connPool, const std::string& address, const unsigned short port, const std::string& username, const std::string& password, const std::string& database) :
MySqlConnectOperation::MySqlConnectOperation(MySqlConnection& connPool, const std::string& address, const unsigned short port, const std::string& username, const std::string& password, const std::string& database, const unsigned int timeout) :
connPool(connPool),
mysql(nullptr),
complete(false),
state(std::make_shared<ClassState>()),
connectThread(&MySqlConnectOperation::DoConnect, this, address, port, username, password, database, InitMySql(), state)
connectThread(&MySqlConnectOperation::DoConnect, this, address, port, username, password, database, InitMySql(timeout), state)
{
}

MYSQL* MySqlConnectOperation::InitMySql() {
MYSQL* MySqlConnectOperation::InitMySql(const unsigned int timeout) {
const auto res(mysql_init(nullptr));
if (!res)
throw std::bad_alloc();
mysql_options(res, MYSQL_OPT_CONNECT_TIMEOUT, static_cast<const void*>(&timeout));
mysql_options(res, MYSQL_OPT_READ_TIMEOUT, static_cast<const void*>(&timeout));
mysql_options(res, MYSQL_OPT_WRITE_TIMEOUT, static_cast<const void*>(&timeout));
return res;
}

Expand Down
5 changes: 3 additions & 2 deletions src/BSQL/MySqlConnectOperation.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ class MySqlConnectOperation : public Operation {
bool complete;
std::shared_ptr<ClassState> state;
std::thread connectThread;

private:
static MYSQL* InitMySql();
static MYSQL* InitMySql(const unsigned int timeout);
void DoConnect(const std::string address, const unsigned short port, const std::string username, const std::string password, const std::string database, MYSQL* localMySql, std::shared_ptr<ClassState> localState);
public:
MySqlConnectOperation(MySqlConnection& connPool, const std::string& address, const unsigned short port, const std::string& username, const std::string& password, const std::string& database);
MySqlConnectOperation(MySqlConnection& connPool, const std::string& address, const unsigned short port, const std::string& username, const std::string& password, const std::string& database, const unsigned int timeout);
MySqlConnectOperation(const MySqlConnectOperation&) = delete;
MySqlConnectOperation(MySqlConnectOperation&&) = delete;
~MySqlConnectOperation() override = default;
Expand Down
9 changes: 5 additions & 4 deletions src/BSQL/MySqlConnection.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#include "BSQL.h"

MySqlConnection::MySqlConnection(Library& library) :
Connection(Type::MySql, library),
firstSuccessfulConnection(nullptr)
MySqlConnection::MySqlConnection(Library& library, const unsigned int asyncTimeout, const unsigned int blockingTimeout) :
Connection(Type::MySql, library, blockingTimeout),
firstSuccessfulConnection(nullptr),
asyncTimeout(asyncTimeout)
{}

MySqlConnection::~MySqlConnection() {
Expand Down Expand Up @@ -62,7 +63,7 @@ bool MySqlConnection::LoadNewConnection(std::string& fail, int& failno) {
return false;
}

newestConnectionAttemptKey = AddOp(std::make_unique<MySqlConnectOperation>(*this, address, port, username, password, database));
newestConnectionAttemptKey = AddOp(std::make_unique<MySqlConnectOperation>(*this, address, port, username, password, database, asyncTimeout));

return false;
}
Expand Down
6 changes: 4 additions & 2 deletions src/BSQL/MySqlConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ class MySqlConnection : public Connection {
std::string username;
std::string password;
std::string database;
unsigned short port;

std::stack<MYSQL*> availableConnections;
MYSQL* firstSuccessfulConnection;
std::string newestConnectionAttemptKey;

const unsigned int asyncTimeout;
unsigned short port;
private:
bool LoadNewConnection(std::string& fail, int& failno);
public:
MySqlConnection(Library& library);
MySqlConnection(Library& library, const unsigned int asyncTimeout, const unsigned int blockingTimeout);
~MySqlConnection() override;

std::string Connect(const std::string& address, const unsigned short port, const std::string& username, const std::string& password, const std::string& database) override;
Expand Down
12 changes: 9 additions & 3 deletions src/DMAPI/BSQL.dm
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//BSQL - DMAPI v1.1.1.0
//BSQL - DMAPI v1.2.0.0

//types of connections
#define BSQL_CONNECTION_TYPE_MARIADB "MySql"
#define BSQL_CONNECTION_TYPE_SQLSERVER "SqlServer"

#define BSQL_DEFAULT_TIMEOUT 5

//Call this before rebooting or shutting down your world to clean up gracefully. This invalidates all active connection and operation datums
/world/proc/BSQL_Shutdown()
return
Expand All @@ -18,8 +20,10 @@ Called whenever a library call is made with verbose information, override and do
/*
Create a new database connection, does not perform the actual connect
connection_type: The BSQL connection_type to use
asyncTimeout: The timeout to use for normal operations, 0 for infinite, defaults to BSQL_DEFAULT_TIMEOUT
blockingTimeout: The timeout to use for blocking operations, must be less than or equal to asyncTimeout, 0 for infinite, defaults to asyncTimeout
*/
/datum/BSQL_Connection/New(connection_type)
/datum/BSQL_Connection/New(connection_type, asyncTimeout, blockingTimeout)
return ..()

/*
Expand Down Expand Up @@ -61,7 +65,9 @@ Checks if the operation is complete. This, in some cases must be called multiple
return

/*
Blocks the entire game until the given operation completes. IsComplete should not be checked after calling this to avoid potential side effects
Blocks the entire game until the given operation completes. IsComplete should not be checked after calling this to avoid potential side effects.
Returns: TRUE on success, FALSE if the operation wait time exceeded the connection's blockingTimeout setting
*/
/datum/BSQL_Operation/proc/WaitForCompletion()
return
Expand Down
9 changes: 7 additions & 2 deletions src/DMAPI/BSQL/core/connection.dm
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@

BSQL_PROTECT_DATUM(/datum/BSQL_Connection)

/datum/BSQL_Connection/New(connection_type)
/datum/BSQL_Connection/New(connection_type, asyncTimeout, blockingTimeout)
if(asyncTimeout == null)
asyncTimeout = BSQL_DEFAULT_TIMEOUT
if(blockingTimeout == null)
blockingTimeout = asyncTimeout

src.connection_type = connection_type

world._BSQL_InitCheck(src)

var/error = world._BSQL_Internal_Call("CreateConnection", connection_type)
var/error = world._BSQL_Internal_Call("CreateConnection", connection_type, "[asyncTimeout]", "[blockingTimeout]")
if(error)
BSQL_ERROR(error)
return
Expand Down
4 changes: 4 additions & 0 deletions src/DMAPI/BSQL/core/operation.dm
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,8 @@ BSQL_DEL_PROC(/datum/BSQL_Operation)
return
var/error = world._BSQL_Internal_Call("BlockOnOperation", connection.id, id)
if(error)
if(error == "Operation timed out!") //match this with the implementation
return FALSE
BSQL_ERROR("Error waiting for operation [id] for connection [connection.id]! [error]")
return
return TRUE
12 changes: 10 additions & 2 deletions tests/Test.dm
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

/proc/Test()
world.log << "Beginning test"

var/host = world.params["dbhost"]
var/user = world.params["dbuser"]
var/port = text2num(world.params["dbport"])
Expand Down Expand Up @@ -84,7 +84,8 @@

q = conn.BeginQuery("CREATE DATABASE [quoted_db]");
world.log << "Create db op id: [q.id]"
q.WaitForCompletion()
if(!q.WaitForCompletion())
CRASH("WaitForCompletion timed out")
if(!q.IsComplete())
CRASH("Wait for completion didn't work!")
error = q.GetError()
Expand Down Expand Up @@ -161,6 +162,13 @@
results = q.CurrentRow()
if(results)
CRASH("Expected no third row! Got: [json_encode(results)] !")

q = conn.BeginQuery("LOCK TABLES asdf WRITE")
world.log << "Lock query id: [q.id]"
WaitOp(q)
error = q.GetError()
if(error)
CRASH(error)

del(q)
del(conn)
Expand Down

0 comments on commit 7964692

Please sign in to comment.