diff --git a/src/BSQL/API.cpp b/src/BSQL/API.cpp index 9e8333e..0a7a257 100644 --- a/src/BSQL/API.cpp +++ b/src/BSQL/API.cpp @@ -1,9 +1,7 @@ #include "BSQL.h" -namespace { - std::unique_ptr library; - std::string lastCreatedConnection, lastCreatedOperation, lastCreatedOperationConnectionId, lastRow, returnValueHolder; -} +std::unique_ptr library; +std::string lastCreatedConnection, lastCreatedOperation, lastCreatedOperationConnectionId, lastRow, returnValueHolder; const char* TryLoadQuery(const int argumentCount, const char* const* const args, Query** query) noexcept { if (argumentCount != 2) @@ -256,7 +254,7 @@ extern "C" { auto operation(connection->GetOperation(operationIdentifier)); if (!operation) return nullptr; - return operation->IsComplete(true) ? "DONE" : "NOTDONE"; + return operation->IsComplete(false) ? "DONE" : "NOTDONE"; } catch (std::bad_alloc&) { return "Out of memory!"; @@ -312,4 +310,30 @@ extern "C" { return nullptr; } } + + BYOND_FUNC BlockOnOperation(const int argumentCount, const char* const* const args) noexcept { + if (argumentCount != 2) + return "Invalid arguments!"; + const auto& connectionIdentifier(args[0]), operationIdentifier(args[1]); + if (!connectionIdentifier) + return "Invalid connection identifier!"; + if (!operationIdentifier) + return "Invalid operation identifier!"; + if (!library) + return "Library not initialized!"; + try { + auto connection(library->GetConnection(connectionIdentifier)); + if (!connection) + return "Connection identifier does not exist!"; + auto op(connection->GetOperation(operationIdentifier)); + if (!op) + return "Operation identifier does not exist!"; + while (!op->IsComplete(false)) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + return nullptr; + } + catch (std::bad_alloc&) { + return "Out of memory!"; + } + } } \ No newline at end of file diff --git a/src/BSQL/BSQL.h b/src/BSQL/BSQL.h index 3f1ca90..c38ecfe 100644 --- a/src/BSQL/BSQL.h +++ b/src/BSQL/BSQL.h @@ -6,15 +6,19 @@ #include -#include #include #include +#include #include #include +#include +#include #include #include #include +class Library; + #include "Operation.h" #include "Query.h" #include "Connection.h" diff --git a/src/BSQL/CMakeLists.txt b/src/BSQL/CMakeLists.txt index 7b66080..3ccbaf9 100644 --- a/src/BSQL/CMakeLists.txt +++ b/src/BSQL/CMakeLists.txt @@ -14,9 +14,13 @@ Query.cpp ) if(WIN32) #vcpkg +#use static crt +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") find_library(MARIA_LIBRARY libmariadb) find_path(MARIA_INCLUDE_DIR mysql/mysql.h) add_precompiled_header(BSQL BSQL.h FORCEINCLUDE) +set(WSLIB ws2_32) else() #system package set_target_properties(BSQL PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") find_path(MARIA_INCLUDE_DIR NAMES "mysql.h" PATHS "/usr/include/mysql") @@ -25,5 +29,5 @@ endif() include_directories(${MARIA_INCLUDE_DIR}) -target_link_libraries(BSQL ${MARIA_LIBRARY}) +target_link_libraries(BSQL ${MARIA_LIBRARY} ${WSLIB}) diff --git a/src/BSQL/Connection.cpp b/src/BSQL/Connection.cpp index dd8ef3c..f4f1cb2 100644 --- a/src/BSQL/Connection.cpp +++ b/src/BSQL/Connection.cpp @@ -1,6 +1,7 @@ #include "BSQL.h" -Connection::Connection(Type type) : +Connection::Connection(Type type, Library& library) : + library(library), type(type), identifierCounter(0) {} @@ -12,6 +13,12 @@ std::string Connection::AddOp(std::unique_ptr&& operation) { } bool Connection::ReleaseOperation(const std::string& identifier) { + auto op(GetOperation(identifier)); + if (!op) + return false; + auto thread(op->GetActiveThread()); + if (thread) + library.RegisterZombieThread(std::move(*thread)); return operations.erase(identifier) > 0; } diff --git a/src/BSQL/Connection.h b/src/BSQL/Connection.h index 1fdda27..e50816c 100644 --- a/src/BSQL/Connection.h +++ b/src/BSQL/Connection.h @@ -9,11 +9,12 @@ class Connection { public: const Type type; protected: + Library & library; std::map> operations; private: unsigned long long identifierCounter; protected: - Connection(Type type); + Connection(Type type, Library& library); std::string AddOp(std::unique_ptr&& operation); public: diff --git a/src/BSQL/Library.cpp b/src/BSQL/Library.cpp index 8965905..47add99 100644 --- a/src/BSQL/Library.cpp +++ b/src/BSQL/Library.cpp @@ -2,7 +2,16 @@ Library::Library() noexcept : identifierCounter(0) -{} +{ + mysql_library_init(0, nullptr, nullptr); +} + +Library::~Library() noexcept { + for (auto& I : zombieThreads) + I.join(); + //https://jira.mariadb.org/browse/CONC-336 + //mysql_library_end(); +} Connection* Library::GetConnection(const std::string& identifier) noexcept { auto iter(connections.find(identifier)); @@ -23,7 +32,7 @@ std::string Library::CreateConnection(Connection::Type type) noexcept { switch (type) { case Connection::Type::MySql: - connections.emplace(identifier, std::make_unique()); + connections.emplace(identifier, std::make_unique(*this)); break; case Connection::Type::SqlServer: --identifierCounter; @@ -35,4 +44,14 @@ std::string Library::CreateConnection(Connection::Type type) noexcept { } } return std::string(); -} \ No newline at end of file +} + +void Library::RegisterZombieThread(std::thread&& thread) noexcept { + try { + zombieThreads.emplace_back(std::move(thread)); + } + catch(std::bad_alloc&) { + //gotta wait then + thread.join(); + } +} diff --git a/src/BSQL/Library.h b/src/BSQL/Library.h index 6720288..e1cbe47 100644 --- a/src/BSQL/Library.h +++ b/src/BSQL/Library.h @@ -1,17 +1,17 @@ #pragma once class Library { -public: - static const char* GoodReturn; - static const char* BadReturn; private: unsigned long long identifierCounter; std::map> connections; + std::deque zombieThreads; public: Library() noexcept; + ~Library() noexcept; std::string CreateConnection(Connection::Type connectionType) noexcept; Connection* GetConnection(const std::string& identifier) noexcept; bool ReleaseConnection(const std::string& identifier) noexcept; + void RegisterZombieThread(std::thread&& thread) noexcept; }; \ No newline at end of file diff --git a/src/BSQL/MySqlConnectOperation.cpp b/src/BSQL/MySqlConnectOperation.cpp index bc40d57..f2c2706 100644 --- a/src/BSQL/MySqlConnectOperation.cpp +++ b/src/BSQL/MySqlConnectOperation.cpp @@ -2,38 +2,65 @@ MySqlConnectOperation::MySqlConnectOperation(MySqlConnection& connPool, const std::string& address, const unsigned short port, const std::string& username, const std::string& password, const std::string& database) : connPool(connPool), - mysql(mysql_init(nullptr)), - complete(false) + mysql(nullptr), + complete(false), + state(std::make_shared()), + connectThread(&MySqlConnectOperation::DoConnect, this, address, port, username, password, database, InitMySql(), state) { - if (mysql == nullptr) +} + +MYSQL* MySqlConnectOperation::InitMySql() { + const auto res(mysql_init(nullptr)); + if (!res) throw std::bad_alloc(); - mysql_options(mysql, MYSQL_OPT_NONBLOCK, 0); - mysql_real_connect_start(&ret, mysql, address.c_str(), username.c_str(), password.c_str(), database.empty() ? nullptr : database.c_str(), port, nullptr, 0); + return res; } -MySqlConnectOperation::~MySqlConnectOperation() { - while (!IsComplete(false)) - std::this_thread::sleep_for(std::chrono::milliseconds(100)); +void MySqlConnectOperation::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 localState) { + mysql_thread_init(); + const auto result(mysql_real_connect(localMySql, address.c_str(), username.c_str(), password.c_str(), database.empty() ? nullptr : database.c_str(), port, nullptr, 0)); + localState->lock.lock(); + if (localState->alive) { + error = mysql_error(localMySql); + errnum = mysql_errno(localMySql); + if (result) + mysql = localMySql; + complete = true; + } + if (!result || !localState->alive) + mysql_close(localMySql); + mysql_thread_end(); + localState->lock.unlock(); } bool MySqlConnectOperation::IsQuery() { return false; } -bool MySqlConnectOperation::IsComplete(bool noOps) { - if (complete) - return true; - const auto status(mysql_real_connect_cont(&ret, mysql, 0)); - complete = status == 0; - if (complete) { - if (!ret) { - error = "mysql_real_connect() returns error: " + std::string(mysql_error(mysql)); - mysql_close(mysql); //don't use connPool Kill since it's never seen this connection - } - else - connPool.ReleaseConnection(mysql); - } - else +bool MySqlConnectOperation::IsComplete(bool noSkip) { + if (!complete) return false; + + if (mysql) { + auto tmp(mysql); + mysql = nullptr; //recursion issue + connPool.ReleaseConnection(tmp); + } + return true; } + +std::thread* MySqlConnectOperation::GetActiveThread() { + state->lock.lock(); + + if (IsComplete(false)) { + state->lock.unlock(); + connectThread.join(); + return nullptr; + } + + state->alive = false; + state->lock.unlock(); + + return &connectThread; +} diff --git a/src/BSQL/MySqlConnectOperation.h b/src/BSQL/MySqlConnectOperation.h index dfed064..e998225 100644 --- a/src/BSQL/MySqlConnectOperation.h +++ b/src/BSQL/MySqlConnectOperation.h @@ -3,15 +3,21 @@ class MySqlConnectOperation : public Operation { private: MySqlConnection& connPool; - MYSQL* ret, *mysql; + MYSQL *mysql; bool complete; + std::shared_ptr state; + std::thread connectThread; +private: + static MYSQL* InitMySql(); + 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 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(const MySqlConnectOperation&) = delete; MySqlConnectOperation(MySqlConnectOperation&&) = delete; - ~MySqlConnectOperation() override; + ~MySqlConnectOperation() override = default; - bool IsComplete(bool noOps) override; + bool IsComplete(bool noSkip) override; bool IsQuery() override; + std::thread* GetActiveThread() override; }; diff --git a/src/BSQL/MySqlConnection.cpp b/src/BSQL/MySqlConnection.cpp index 55952cf..a5f8bfa 100644 --- a/src/BSQL/MySqlConnection.cpp +++ b/src/BSQL/MySqlConnection.cpp @@ -1,13 +1,17 @@ #include "BSQL.h" -MySqlConnection::MySqlConnection() : - Connection(Type::MySql), - firstSuccessfulConnection(nullptr), - newestConnectionAttempt(nullptr) +MySqlConnection::MySqlConnection(Library& library) : + Connection(Type::MySql, library), + firstSuccessfulConnection(nullptr) {} MySqlConnection::~MySqlConnection() { //do this first so all reserved connections are returned to the queue + for (auto& I : operations) { + auto thread(I.second->GetActiveThread()); + if (thread) + library.RegisterZombieThread(std::move(*thread)); + } operations.clear(); //and release them while (!availableConnections.empty()) { @@ -31,27 +35,34 @@ std::string MySqlConnection::Connect(const std::string& address, const unsigned this->username = username; this->password = password; this->database = database; - - LoadNewConnection(); - + int tmp; + LoadNewConnection(newestConnectionAttemptKey, tmp); + newestConnectionAttemptKey = std::string(); return operations.begin()->first; } -bool MySqlConnection::LoadNewConnection() { - if (newestConnectionAttempt) { +bool MySqlConnection::LoadNewConnection(std::string& fail, int& failno) { + if (!newestConnectionAttemptKey.empty()) { //this will chain into calling ReleaseConnection and clear the var - if (newestConnectionAttempt->IsComplete(false)) { - if (availableConnections.size() > 0) + auto nca(newestConnectionAttemptKey); + auto& op(*GetOperation(nca)); + if (op.IsComplete(false)) { + const auto success(availableConnections.size() > 0); + if (success) { + ReleaseOperation(nca); return true; - newestConnectionAttempt = nullptr; + } + else { + fail = op.GetError(); + failno = op.GetErrno(); + ReleaseOperation(nca); + } } else return false; } - auto newCon(std::make_unique(*this, address, port, username, password, database)); - newestConnectionAttempt = newCon.get(); - AddOp(std::move(newCon)); + newestConnectionAttemptKey = AddOp(std::make_unique(*this, address, port, username, password, database)); return false; } @@ -60,40 +71,28 @@ std::string MySqlConnection::CreateQuery(const std::string& queryText) { return AddOp(std::make_unique(*this, std::string(queryText))); } -MYSQL* MySqlConnection::RequestConnection() { - if (availableConnections.empty() && !LoadNewConnection()) +MYSQL* MySqlConnection::RequestConnection(std::string& fail, int& failno, bool& doNotClose) { + if (availableConnections.empty() && !LoadNewConnection(fail, failno)) return nullptr; auto front(availableConnections.top()); availableConnections.pop(); + doNotClose = front == firstSuccessfulConnection; return front; } void MySqlConnection::ReleaseConnection(MYSQL* connection) { availableConnections.emplace(connection); + if (!firstSuccessfulConnection) firstSuccessfulConnection = connection; - if (newestConnectionAttempt) { - auto tmp(newestConnectionAttempt); - newestConnectionAttempt = nullptr; - if (!tmp->IsComplete(false)) - newestConnectionAttempt = tmp; - } -} - -bool MySqlConnection::ReleaseOperation(const std::string& identifier) { - auto op(GetOperation(identifier)); - if (op && op == newestConnectionAttempt) - newestConnectionAttempt = nullptr; - auto result(Connection::ReleaseOperation(identifier)); - if (availableConnections.empty()) - LoadNewConnection(); - return result; -} -void MySqlConnection::KillConnection(MYSQL* connection) { - if (connection != firstSuccessfulConnection) //we keep the first around for quoting - mysql_close(connection); + if (!newestConnectionAttemptKey.empty()) { + std::string tmp; + std::swap(tmp, newestConnectionAttemptKey); + if (!GetOperation(tmp)->IsComplete(false)) + std::swap(tmp, newestConnectionAttemptKey); + } } std::string MySqlConnection::Quote(const std::string& str) { diff --git a/src/BSQL/MySqlConnection.h b/src/BSQL/MySqlConnection.h index 1422abb..de80159 100644 --- a/src/BSQL/MySqlConnection.h +++ b/src/BSQL/MySqlConnection.h @@ -12,19 +12,17 @@ class MySqlConnection : public Connection { std::stack availableConnections; MYSQL* firstSuccessfulConnection; - MySqlConnectOperation* newestConnectionAttempt; + std::string newestConnectionAttemptKey; private: - bool LoadNewConnection(); + bool LoadNewConnection(std::string& fail, int& failno); public: - MySqlConnection(); + MySqlConnection(Library& library); ~MySqlConnection() override; - bool ReleaseOperation(const std::string& identifier) 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; std::string CreateQuery(const std::string& queryText) override; std::string Quote(const std::string& str) override; - MYSQL* RequestConnection(); - void KillConnection(MYSQL* connection); + MYSQL* RequestConnection(std::string& fail, int& failno, bool& doNotClose); void ReleaseConnection(MYSQL* connection); }; \ No newline at end of file diff --git a/src/BSQL/MySqlQueryOperation.cpp b/src/BSQL/MySqlQueryOperation.cpp index d4ca7bf..59dcece 100644 --- a/src/BSQL/MySqlQueryOperation.cpp +++ b/src/BSQL/MySqlQueryOperation.cpp @@ -1,132 +1,159 @@ #include "BSQL.h" MySqlQueryOperation::MySqlQueryOperation(MySqlConnection& connPool, std::string&& queryText) : - queryText(queryText), + queryText(std::move(queryText)), connPool(connPool), - connection(connPool.RequestConnection()), - result(nullptr), + connection(nullptr), + state(std::make_shared()), + connectionAttempts(0), + started(false), complete(false), - queryFinished(false) + operationThread(TryStart()) { - StartQuery(); } MySqlQueryOperation::~MySqlQueryOperation() { - if (connection) { - //must ensure everything is taken care of + if (!connection) + return; + connPool.ReleaseConnection(connection); +} + +std::thread MySqlQueryOperation::TryStart() { + connection = connPool.RequestConnection(error, errnum, noClose); + if (!connection) { + if (!error.empty()) + complete = ++connectionAttempts == 3; + return std::thread(); + } + started = true; + return std::thread(&MySqlQueryOperation::StartQuery, this, connection, std::move(queryText), state); +} + +void MySqlQueryOperation::QuestionableExit(MYSQL* mysql, std::shared_ptr& localClassState) { + //resultless? + localClassState->lock.lock(); + if (localClassState->alive) { + complete = true; + const auto tmpErr(mysql_errno(mysql)); + if (tmpErr) { + //no it's an error + error = mysql_error(mysql); + errnum = tmpErr; + } + } + else if (!noClose) + mysql_close(mysql); + mysql_thread_end(); + localClassState->lock.unlock(); +} +void MySqlQueryOperation::StartQuery(MYSQL* mysql, std::string&& localQueryText, std::shared_ptr localClassState) { + mysql_thread_init(); + + const auto localError(mysql_real_query(mysql, localQueryText.c_str(), localQueryText.length())); + + if (localError) { + QuestionableExit(mysql, localClassState); + return; + } + + const auto result(mysql_use_result(mysql)); + if (!result) { + QuestionableExit(mysql, localClassState); + return; + } + + for (MYSQL_ROW row(mysql_fetch_row(result)); row != nullptr; row = mysql_fetch_row(result)) { try { - while (!complete) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - IsComplete(false); + std::string json("{"); + bool first(true); + const auto numFields(mysql_num_fields(result)); + mysql_field_seek(result, 0); + for (auto I(0U); I < numFields; ++I) { + const auto field(mysql_fetch_field(result)); + if (first) + first = false; + else + json.append(","); + json.append("\""); + json.append(field->name); + json.append("\":"); + if (row[I] == nullptr) + json.append("null"); + else { + json.append("\""); + json.append(row[I]); + json.append("\""); + } } + json.append("}"); + + localClassState->lock.lock(); + const auto alive(localClassState->alive); + if (alive) + results.emplace(std::move(json)); + localClassState->lock.unlock(); + if (!alive) + break; } - //can't save it catch (std::bad_alloc&) { - connPool.KillConnection(connection); - connection = nullptr; - } - if (result) mysql_free_result(result); - if (connection) - connPool.ReleaseConnection(connection); + localClassState->lock.lock(); + if (localClassState->alive) { + complete = true; + errnum = -1; + error = "Out of memory!"; + } + else if (!noClose) + mysql_close(mysql); + mysql_thread_end(); + localClassState->lock.unlock(); + return; + } } -} -void MySqlQueryOperation::StartQuery() { - if (connection == nullptr) - return; + mysql_free_result(result); - mysql_real_query_start(&queryError, connection, queryText.c_str(), queryText.length()); + QuestionableExit(mysql, localClassState); } -bool MySqlQueryOperation::IsComplete(bool noOps) { - if (complete) - return true; - - if (!connection) { - connection = connPool.RequestConnection(); - StartQuery(); +bool MySqlQueryOperation::IsComplete(bool noSkip) { + if (!started) { + operationThread = TryStart(); return false; } - if (!queryFinished) { - const auto status(mysql_real_query_cont(&queryError, connection, 0)); - if (status != 0) - return false; - - if (queryError) { - complete = true; - error = "mysql_real_query() returns error: " + std::string(mysql_error(connection)); - return true; + state->lock.lock(); + if (!results.empty()) { + if (!noSkip) { + currentRow = std::move(results.front()); + results.pop(); } - - queryFinished = true; - - result = mysql_use_result(connection); - if (!result) { - //resultless? - complete = true; - if(mysql_errno(connection)) - //no it's an error - error = "mysql_use_result() returns error: " + std::string(mysql_error(connection)); - return true; - } - if (mysql_fetch_row_start(&row, result) != 0) - return false; - else - waitNext = false; + state->lock.unlock(); + return true; } - - if(waitNext){ - const auto status(mysql_fetch_row_cont(&row, result, 0)); - if (status != 0) - return false; - } + const auto result(complete); + state->lock.unlock(); + if (result && !noSkip) + currentRow = std::string(); + return result; +} - if (row != nullptr) { - struct ColInfo { - std::string name; - enum_field_types type; - }; - - std::string json("{"); - bool first(true); - const auto numFields(mysql_num_fields(result)); - mysql_field_seek(result, 0); - for (auto I(0U); I < numFields; ++I) { - const auto field(mysql_fetch_field(result)); - if (first) - first = false; - else - json.append(","); - json.append("\""); - json.append(field->name); - json.append("\":"); - if (row[I] == nullptr) - json.append("null"); - else { - json.append("\""); - json.append(row[I]); - json.append("\""); - } - } - json.append("}"); +std::thread* MySqlQueryOperation::GetActiveThread() { + if (!started) + return nullptr; - currentRow = std::move(json); + state->lock.lock(); - if(!noOps) - waitNext = mysql_fetch_row_start(&row, result) != 0; + if (complete) { + state->lock.unlock(); + operationThread.join(); + return nullptr; } - else { - //resultless? - complete = true; - if (mysql_errno(connection)) - //no it's an error - error = "mysql_fetch_row() returns error: " + std::string(mysql_error(connection)); - else - currentRow = std::string(); - } - return true; -} \ No newline at end of file + + state->alive = false; + state->lock.unlock(); + connection = nullptr; + return &operationThread; +} diff --git a/src/BSQL/MySqlQueryOperation.h b/src/BSQL/MySqlQueryOperation.h index 45c6815..21c9675 100644 --- a/src/BSQL/MySqlQueryOperation.h +++ b/src/BSQL/MySqlQueryOperation.h @@ -2,18 +2,24 @@ class MySqlQueryOperation : public Query { private: - const std::string queryText; + std::string queryText; MySqlConnection& connPool; MYSQL* connection; - MYSQL_RES* result; - MYSQL_ROW row; - int queryError; - bool complete, queryFinished, waitNext; + bool noClose; + std::shared_ptr state; + std::queue results; + int connectionAttempts; + bool started, complete; + std::thread operationThread; private: - void StartQuery(); + std::thread TryStart(); + + void QuestionableExit(MYSQL* mysql, std::shared_ptr& localClassState); + void StartQuery(MYSQL* mysql, std::string&& localQueryText, std::shared_ptr localClassState); public: MySqlQueryOperation(MySqlConnection& connPool, std::string&& queryText); ~MySqlQueryOperation() override; - bool IsComplete(bool noOps) override; + bool IsComplete(bool noSkip) override; + std::thread* GetActiveThread() override; }; diff --git a/src/BSQL/Operation.cpp b/src/BSQL/Operation.cpp index d1b39d2..c885302 100644 --- a/src/BSQL/Operation.cpp +++ b/src/BSQL/Operation.cpp @@ -4,4 +4,10 @@ std::string Operation::GetError() { if (!IsComplete(true)) return std::string(); return error; -} \ No newline at end of file +} + +int Operation::GetErrno() { + if (!IsComplete(true)) + return -1; + return errnum; +} diff --git a/src/BSQL/Operation.h b/src/BSQL/Operation.h index 1fbae9c..0c9ef78 100644 --- a/src/BSQL/Operation.h +++ b/src/BSQL/Operation.h @@ -3,12 +3,20 @@ class Operation { protected: + struct ClassState { + std::mutex lock; + bool alive = true; + }; +protected: + int errnum; std::string error; public: virtual ~Operation() = default; std::string GetError(); + int GetErrno(); - virtual bool IsComplete(bool noOps) = 0; + virtual bool IsComplete(bool noSkip) = 0; virtual bool IsQuery() = 0; + virtual std::thread* GetActiveThread() = 0; }; diff --git a/src/DMAPI/BSQL.dm b/src/DMAPI/BSQL.dm index 2798c90..d07d52d 100644 --- a/src/DMAPI/BSQL.dm +++ b/src/DMAPI/BSQL.dm @@ -1,4 +1,4 @@ -//BSQL - DMAPI v1.0.3.0 +//BSQL - DMAPI v1.1.0.0 //types of connections #define BSQL_CONNECTION_TYPE_MARIADB "MySql" @@ -8,6 +8,13 @@ /world/proc/BSQL_Shutdown() return +/* +Called whenever a library call is made with verbose information, override and do with as you please + message: English debug message +*/ +/world/proc/BSQL_Debug(msg) + return + /* Create a new database connection, does not perform the actual connect connection_type: The BSQL connection_type to use @@ -44,7 +51,7 @@ Starts an operation for a query */ /datum/BSQL_Connection/proc/BeginQuery(query) return - + /* Checks if the operation is complete. This, in some cases must be called multiple times with false return before a result is present regardless of timespan. For best performance check it once per tick @@ -53,6 +60,12 @@ Checks if the operation is complete. This, in some cases must be called multiple /datum/BSQL_Operation/proc/IsComplete() return +/* +Blocks the entire game until the given operation completes. IsComplete should not be checked after calling this to avoid potential side effects +*/ +/datum/BSQL_Operation/proc/WaitForCompletion() + return + /* Get the error message associated with an operation. Should not be used while IsComplete() returns FALSE diff --git a/src/DMAPI/BSQL/core/library.dm b/src/DMAPI/BSQL/core/library.dm index 5bfbc6c..bdb396b 100644 --- a/src/DMAPI/BSQL/core/library.dm +++ b/src/DMAPI/BSQL/core/library.dm @@ -1,5 +1,8 @@ /world/proc/_BSQL_Internal_Call(func, ...) - return call(_BSQL_Library_Path(), func)(arglist(args.Copy(2))) + var/list/call_args = args.Copy(2) + BSQL_Debug("[.....]: [args[1]]([call_args.Join(", ")])") + . = call(_BSQL_Library_Path(), func)(arglist(call_args)) + BSQL_Debug("Result: [. == null ? "NULL" : "\"[.]\""]") /world/proc/_BSQL_Library_Path() return system_type == MS_WINDOWS ? "BSQL.dll" : "libBSQL.so" diff --git a/src/DMAPI/BSQL/core/operation.dm b/src/DMAPI/BSQL/core/operation.dm index b219a3b..2c820ee 100644 --- a/src/DMAPI/BSQL/core/operation.dm +++ b/src/DMAPI/BSQL/core/operation.dm @@ -29,3 +29,10 @@ BSQL_DEL_PROC(/datum/BSQL_Operation) if(BSQL_IS_DELETED(connection)) return "Connection deleted!" return world._BSQL_Internal_Call("GetError", connection.id, id) + +/datum/BSQL_Operation/WaitForCompletion() + if(BSQL_IS_DELETED(connection)) + return + var/error = world._BSQL_Internal_Call("BlockOnOperation", connection.id, id) + if(error) + BSQL_ERROR("Error waiting for operation [id] for connection [connection.id]! [error]") diff --git a/tests/Test.dm b/tests/Test.dm index a2dacbd..99b8e63 100644 --- a/tests/Test.dm +++ b/tests/Test.dm @@ -12,7 +12,14 @@ world.log << "TestStart" sleep(10) world.log << "Init time elapsed" - Test() + //run the test 10 times for those awkward race conditions + var/fail = FALSE + for(var/I in 1 to 10) + if(!Test()) + fail = TRUE + break + if(!fail) + text2file("Success!", "clean_run.lk") del(world) /proc/WaitOp(datum/BSQL_Operation/op) @@ -21,6 +28,13 @@ sleep(1) world.log << "Op [op.id] (conn: [op.connection.id]) complete" +/datum/BSQL_Operation/Del() + world.log << "Operation [id] (conn: [connection ? connection.id : "null"]) deleted" + return ..() + +/world/BSQL_Debug(msg) + world.log << "BSQL_DEBUG: [msg]" + /proc/Test() world.log << "Beginning test" @@ -59,7 +73,9 @@ q = conn.BeginQuery("CREATE DATABASE [quoted_db]"); world.log << "Create db op id: [q.id]" - WaitOp(q) + q.WaitForCompletion() + if(!q.IsComplete()) + CRASH("Wait for completion didn't work!") error = q.GetError() if(error) CRASH(error) @@ -140,4 +156,4 @@ world.BSQL_Shutdown() - text2file("Success!", "clean_run.lk") + return TRUE