diff --git a/dlls/mysqlx/basic_sql.cpp b/dlls/mysqlx/basic_sql.cpp index dc3d70d2..1bcf18e4 100644 --- a/dlls/mysqlx/basic_sql.cpp +++ b/dlls/mysqlx/basic_sql.cpp @@ -55,6 +55,10 @@ static cell AMX_NATIVE_CALL SQL_MakeDbTuple(AMX *amx, cell *params) sql->user = strdup(MF_GetAmxString(amx, params[2], 0, &len)); sql->pass = strdup(MF_GetAmxString(amx, params[3], 0, &len)); sql->db = strdup(MF_GetAmxString(amx, params[4], 0, &len)); + if (params[0] / sizeof(cell) >= 5) + { + sql->max_timeout = static_cast(params[5]); + } unsigned int num = MakeHandle(sql, Handle_Connection, FreeConnection); @@ -87,11 +91,12 @@ static cell AMX_NATIVE_CALL SQL_Connect(AMX *amx, cell *params) nfo.pass = sql->pass; nfo.port = sql->port; nfo.host = sql->host; + nfo.max_timeout = sql->max_timeout; char buffer[512]; int errcode; - IDatabase *pDb = g_Mysql.Connect(&nfo, &errcode, buffer, sizeof(buffer)-1); + IDatabase *pDb = g_Mysql.Connect2(&nfo, &errcode, buffer, sizeof(buffer)-1); if (!pDb) { @@ -378,13 +383,15 @@ static cell AMX_NATIVE_CALL SQL_GetQueryString(AMX *amx, cell *params) { AmxQueryInfo *qInfo = (AmxQueryInfo *)GetHandle(params[1], Handle_Query); - if (!qInfo || !qInfo->pQuery) + if (!qInfo || (!qInfo->pQuery && !qInfo->opt_ptr)) { MF_LogError(amx, AMX_ERR_NATIVE, "Invalid query handle: %d", params[1]); return 0; } - return MF_SetAmxString(amx, params[2], qInfo->pQuery->GetQueryString(), params[3]); + const char *ptr = qInfo->pQuery ? qInfo->pQuery->GetQueryString() : qInfo->opt_ptr; + + return MF_SetAmxString(amx, params[2], ptr, params[3]); } static cell AMX_NATIVE_CALL SQL_FieldNameToNum(AMX *amx, cell *params) diff --git a/dlls/mysqlx/mysql/ISQLDriver.h b/dlls/mysqlx/mysql/ISQLDriver.h index 2772da7b..80c325ad 100644 --- a/dlls/mysqlx/mysql/ISQLDriver.h +++ b/dlls/mysqlx/mysql/ISQLDriver.h @@ -134,11 +134,13 @@ namespace SourceMod struct DatabaseInfo { + DatabaseInfo() : max_timeout(0) { }; const char *host; const char *database; const char *user; const char *pass; unsigned int port; + unsigned int max_timeout; }; class ISQLDriver @@ -147,6 +149,8 @@ namespace SourceMod virtual ~ISQLDriver() { }; public: virtual IDatabase *Connect(DatabaseInfo *info, int *errcode, char *error, size_t maxlength) =0; + //Supports the timeout clause + virtual IDatabase *Connect2(DatabaseInfo *info, int *errcode, char *error, size_t maxlength) =0; virtual const char *NameString() =0; virtual bool IsCompatDriver(const char *namestring) =0; }; diff --git a/dlls/mysqlx/mysql/MysqlDriver.cpp b/dlls/mysqlx/mysql/MysqlDriver.cpp index f578a6a4..3c28780f 100644 --- a/dlls/mysqlx/mysql/MysqlDriver.cpp +++ b/dlls/mysqlx/mysql/MysqlDriver.cpp @@ -21,6 +21,16 @@ const char *MysqlDriver::NameString() } IDatabase *MysqlDriver::Connect(DatabaseInfo *info, int *errcode, char *error, size_t maxlength) +{ + return _Connect(info, errcode, error, maxlength, false); +} + +IDatabase *MysqlDriver::Connect2(DatabaseInfo *info, int *errcode, char *error, size_t maxlength) +{ + return _Connect(info, errcode, error, maxlength, true); +} + +IDatabase *MysqlDriver::_Connect(DatabaseInfo *info, int *errcode, char *error, size_t maxlength, bool do_timeout) { MYSQL *mysql = mysql_init(NULL); @@ -35,6 +45,11 @@ IDatabase *MysqlDriver::Connect(DatabaseInfo *info, int *errcode, char *error, s return false; } + if (do_timeout && info->max_timeout) + { + mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&(info->max_timeout)); + } + if (mysql_real_connect(mysql, info->host, info->user, @@ -45,9 +60,13 @@ IDatabase *MysqlDriver::Connect(DatabaseInfo *info, int *errcode, char *error, s 0) == NULL) { if (errcode) + { *errcode = mysql_errno(mysql); + } if (error && maxlength) + { snprintf(error, maxlength, "%s", mysql_error(mysql)); + } return false; } diff --git a/dlls/mysqlx/mysql/MysqlDriver.h b/dlls/mysqlx/mysql/MysqlDriver.h index db4f3089..e313a09e 100644 --- a/dlls/mysqlx/mysql/MysqlDriver.h +++ b/dlls/mysqlx/mysql/MysqlDriver.h @@ -9,8 +9,11 @@ namespace SourceMod { public: IDatabase *Connect(DatabaseInfo *info, int *errcode, char *error, size_t maxlength); + IDatabase *Connect2(DatabaseInfo *info, int *errcode, char *error, size_t maxlength); const char *NameString(); bool IsCompatDriver(const char *namestring); + public: + IDatabase *_Connect(DatabaseInfo *info, int *errcode, char *error, size_t maxlength, bool do_timeout); }; }; diff --git a/dlls/mysqlx/mysql2_header.h b/dlls/mysqlx/mysql2_header.h index f136a2f2..0d1d9a8b 100644 --- a/dlls/mysqlx/mysql2_header.h +++ b/dlls/mysqlx/mysql2_header.h @@ -10,9 +10,11 @@ struct AmxQueryInfo { + AmxQueryInfo() : opt_ptr(NULL) { }; IQuery *pQuery; QueryInfo info; char error[255]; + char *opt_ptr; }; enum HandleType @@ -32,6 +34,7 @@ struct SQL_Connection char *pass; char *db; int port; + unsigned int max_timeout; }; typedef void (*FREEHANDLE)(void *, unsigned int); diff --git a/dlls/mysqlx/threading.cpp b/dlls/mysqlx/threading.cpp index ddbd2617..cd4bce74 100644 --- a/dlls/mysqlx/threading.cpp +++ b/dlls/mysqlx/threading.cpp @@ -58,7 +58,7 @@ static cell AMX_NATIVE_CALL SQL_ThreadQuery(AMX *amx, cell *params) int len; const char *handler = MF_GetAmxString(amx, params[2], 0, &len); - int fwd = MF_RegisterSPForwardByName(amx, handler, FP_CELL, FP_CELL, FP_STRING, FP_CELL, FP_ARRAY, FP_CELL, FP_DONE); + int fwd = MF_RegisterSPForwardByName(amx, handler, FP_CELL, FP_CELL, FP_STRING, FP_CELL, FP_ARRAY, FP_CELL, FP_CELL, FP_DONE); if (fwd < 1) { MF_LogError(amx, AMX_ERR_NATIVE, "Function not found: %s", handler); @@ -76,7 +76,7 @@ static cell AMX_NATIVE_CALL SQL_ThreadQuery(AMX *amx, cell *params) } g_QueueLock->Unlock(); - kmThread->SetInfo(cn->host, cn->user, cn->pass, cn->db, cn->port); + kmThread->SetInfo(cn->host, cn->user, cn->pass, cn->db, cn->port, cn->max_timeout); kmThread->SetForward(fwd); kmThread->SetQuery(MF_GetAmxString(amx, params[3], 1, &len)); kmThread->SetCellData(MF_GetAmxAddr(amx, params[4]), (ucell)params[5]); @@ -126,13 +126,15 @@ void MysqlThread::SetForward(int forward) m_fwd = forward; } -void MysqlThread::SetInfo(const char *host, const char *user, const char *pass, const char *db, int port) +void MysqlThread::SetInfo(const char *host, const char *user, const char *pass, const char *db, int port, unsigned int max_timeout) { m_host.assign(host); m_user.assign(user); m_pass.assign(pass); m_db.assign(db); + m_max_timeout = m_max_timeout; m_port = port; + m_qrInfo.queue_time = gpGlobals->time; } void MysqlThread::SetQuery(const char *query) @@ -149,10 +151,15 @@ void MysqlThread::RunThread(IThreadHandle *pHandle) info.user = m_user.c_str(); info.host = m_host.c_str(); info.port = m_port; + info.max_timeout = m_max_timeout; + + float save_time = m_qrInfo.queue_time; memset(&m_qrInfo, 0, sizeof(m_qrInfo)); - IDatabase *pDatabase = g_Mysql.Connect(&info, &m_qrInfo.amxinfo.info.errorcode, m_qrInfo.amxinfo.error, 254); + m_qrInfo.queue_time = save_time; + + IDatabase *pDatabase = g_Mysql.Connect2(&info, &m_qrInfo.amxinfo.info.errorcode, m_qrInfo.amxinfo.error, 254); IQuery *pQuery = NULL; if (!pDatabase) { @@ -172,14 +179,15 @@ void MysqlThread::RunThread(IThreadHandle *pHandle) if (m_qrInfo.query_success && m_qrInfo.amxinfo.info.rs) { m_atomicResult.CopyFrom(m_qrInfo.amxinfo.info.rs); - m_qrInfo.amxinfo.pQuery = pQuery; m_qrInfo.amxinfo.info.rs = &m_atomicResult; + } + + if (pQuery) + { + m_qrInfo.amxinfo.pQuery = pQuery; } else { - if (pQuery) - { - pQuery->FreeHandle(); - } - pQuery = NULL; + m_qrInfo.amxinfo.opt_ptr = new char[m_query.size() + 1]; + strcpy(m_qrInfo.amxinfo.opt_ptr, m_query.c_str()); } if (pDatabase) @@ -231,31 +239,37 @@ void MysqlThread::Execute() } else if (!m_qrInfo.query_success) { state = -1; } + float diff = gpGlobals->time - m_qrInfo.queue_time; + cell c_diff = amx_ftoc(diff); + unsigned int hndl = MakeHandle(&m_qrInfo.amxinfo, Handle_Query, NullFunc); if (state != 0) { MF_ExecuteForward(m_fwd, (cell)state, - (cell)0, + (cell)hndl, m_qrInfo.amxinfo.error, m_qrInfo.amxinfo.info.errorcode, data_addr, - m_datalen); + m_datalen, + c_diff); } else { - unsigned int hndl = MakeHandle(&m_qrInfo.amxinfo, Handle_Query, NullFunc); MF_ExecuteForward(m_fwd, (cell)0, (cell)hndl, "", (cell)0, data_addr, - m_datalen); - /* this should always be true I think */ - if (m_qrInfo.amxinfo.pQuery) - { - m_qrInfo.amxinfo.pQuery->FreeHandle(); - } - FreeHandle(hndl); + m_datalen, + c_diff); } + FreeHandle(hndl); + if (m_qrInfo.amxinfo.pQuery) + { + m_qrInfo.amxinfo.pQuery->FreeHandle(); + m_qrInfo.amxinfo.pQuery = NULL; + } + delete [] m_qrInfo.amxinfo.opt_ptr; + m_qrInfo.amxinfo.opt_ptr = NULL; } /***************** diff --git a/dlls/mysqlx/threading.h b/dlls/mysqlx/threading.h index e17045cc..11b1b32b 100644 --- a/dlls/mysqlx/threading.h +++ b/dlls/mysqlx/threading.h @@ -12,6 +12,7 @@ struct QueuedResultInfo AmxQueryInfo amxinfo; bool connect_success; bool query_success; + float queue_time; }; class AtomicResult : @@ -59,7 +60,7 @@ public: MysqlThread(); ~MysqlThread(); public: - void SetInfo(const char *host, const char *user, const char *pass, const char *db, int port); + void SetInfo(const char *host, const char *user, const char *pass, const char *db, int port, unsigned int max_timeout); void SetQuery(const char *query); void SetCellData(cell data[], ucell len); void SetForward(int forward); @@ -74,6 +75,7 @@ private: SourceHook::String m_user; SourceHook::String m_pass; SourceHook::String m_db; + unsigned int m_max_timeout; int m_port; cell *m_data; ucell m_datalen; diff --git a/dlls/sqlite/basic_sql.cpp b/dlls/sqlite/basic_sql.cpp index d169128b..b093f84f 100644 --- a/dlls/sqlite/basic_sql.cpp +++ b/dlls/sqlite/basic_sql.cpp @@ -382,13 +382,16 @@ static cell AMX_NATIVE_CALL SQL_FieldNumToName(AMX *amx, cell *params) static cell AMX_NATIVE_CALL SQL_GetQueryString(AMX *amx, cell *params) { AmxQueryInfo *qInfo = (AmxQueryInfo *)GetHandle(params[1], Handle_Query); - if (!qInfo) + + if (!qInfo || (!qInfo->pQuery && !qInfo->opt_ptr)) { MF_LogError(amx, AMX_ERR_NATIVE, "Invalid query handle: %d", params[1]); return 0; } - return MF_SetAmxString(amx, params[2], qInfo->pQuery->GetQueryString(), params[3]); + const char *ptr = qInfo->pQuery ? qInfo->pQuery->GetQueryString() : qInfo->opt_ptr; + + return MF_SetAmxString(amx, params[2], ptr, params[3]); } static cell AMX_NATIVE_CALL SQL_FieldNameToNum(AMX *amx, cell *params) diff --git a/dlls/sqlite/sdk/sh_list.h b/dlls/sqlite/sdk/sh_list.h index be136c41..e5c65347 100644 --- a/dlls/sqlite/sdk/sh_list.h +++ b/dlls/sqlite/sdk/sh_list.h @@ -23,6 +23,7 @@ namespace SourceHook { + //This class is from CSDM for AMX Mod X /* A circular, doubly-linked list with one sentinel node diff --git a/dlls/sqlite/sqlite_header.h b/dlls/sqlite/sqlite_header.h index cc84b75a..fa70ea2d 100644 --- a/dlls/sqlite/sqlite_header.h +++ b/dlls/sqlite/sqlite_header.h @@ -10,9 +10,11 @@ struct AmxQueryInfo { + AmxQueryInfo() : opt_ptr(NULL) { }; IQuery *pQuery; QueryInfo info; char error[255]; + char *opt_ptr; }; enum HandleType diff --git a/dlls/sqlite/thread/ThreadWorker.cpp b/dlls/sqlite/thread/ThreadWorker.cpp index daff21ed..4c061067 100644 --- a/dlls/sqlite/thread/ThreadWorker.cpp +++ b/dlls/sqlite/thread/ThreadWorker.cpp @@ -54,6 +54,9 @@ void ThreadWorker::RunThread(IThreadHandle *pHandle) /** * Check number of items in the queue */ + m_StateLock->Lock(); + this_state = m_state; + m_StateLock->Unlock(); if (this_state != Worker_Stopped) { m_QueueLock->Lock(); @@ -65,6 +68,11 @@ void ThreadWorker::RunThread(IThreadHandle *pHandle) */ m_Waiting = true; m_QueueLock->Unlock(); + /* first check if we should end again */ + if (this_state == Worker_Stopped) + { + break; + } m_AddSignal->Wait(); m_Waiting = false; } else { @@ -80,7 +88,9 @@ void ThreadWorker::RunThread(IThreadHandle *pHandle) { //wait until the lock is cleared. if (this_state == Worker_Paused) + { m_PauseSignal->Wait(); + } if (this_state == Worker_Stopped) { //if we're supposed to flush cleanrly, @@ -187,9 +197,12 @@ bool ThreadWorker::Stop(bool flush_cancel) { Unpause(); } else { - m_AddSignal->Signal(); - Pause(); - Unpause(); + m_QueueLock->Lock(); + if (m_Waiting) + { + m_AddSignal->Signal(); + } + m_QueueLock->Unlock(); } me->WaitForThread(); diff --git a/dlls/sqlite/thread/WinThreads.cpp b/dlls/sqlite/thread/WinThreads.cpp index 0d15da9d..6f3f436f 100644 --- a/dlls/sqlite/thread/WinThreads.cpp +++ b/dlls/sqlite/thread/WinThreads.cpp @@ -97,7 +97,7 @@ IThreadHandle *WinThreader::MakeThread(IThread *pThread, const ThreadParams *par IEventSignal *WinThreader::MakeEventSignal() { - HANDLE event = CreateEventA(NULL, TRUE, TRUE, NULL); + HANDLE event = CreateEventA(NULL, FALSE, FALSE, NULL); if (!event) return NULL; @@ -275,7 +275,6 @@ WinThreader::WinEvent::~WinEvent() void WinThreader::WinEvent::Wait() { WaitForSingleObject(m_event, INFINITE); - ResetEvent(m_event); } void WinThreader::WinEvent::Signal() diff --git a/dlls/sqlite/threading.cpp b/dlls/sqlite/threading.cpp index 5092d0da..6a06c690 100644 --- a/dlls/sqlite/threading.cpp +++ b/dlls/sqlite/threading.cpp @@ -129,6 +129,7 @@ void MysqlThread::SetForward(int forward) void MysqlThread::SetInfo(const char *db) { m_db.assign(db); + m_qrInfo.queue_time = gpGlobals->time; } void MysqlThread::SetQuery(const char *query) @@ -146,8 +147,12 @@ void MysqlThread::RunThread(IThreadHandle *pHandle) info.host = ""; info.port = 0; + float save_time = m_qrInfo.queue_time; + memset(&m_qrInfo, 0, sizeof(m_qrInfo)); + m_qrInfo.queue_time = save_time; + IDatabase *pDatabase = g_Sqlite.Connect(&info, &m_qrInfo.amxinfo.info.errorcode, m_qrInfo.amxinfo.error, 254); IQuery *pQuery = NULL; if (!pDatabase) @@ -168,15 +173,17 @@ void MysqlThread::RunThread(IThreadHandle *pHandle) if (m_qrInfo.query_success && m_qrInfo.amxinfo.info.rs) { m_atomicResult.CopyFrom(m_qrInfo.amxinfo.info.rs); - m_qrInfo.amxinfo.pQuery = NULL; m_qrInfo.amxinfo.info.rs = &m_atomicResult; } if (pQuery) { - pQuery->FreeHandle(); - pQuery = NULL; + m_qrInfo.amxinfo.pQuery = pQuery; + } else { + m_qrInfo.amxinfo.opt_ptr = new char[m_query.size() + 1]; + strcpy(m_qrInfo.amxinfo.opt_ptr, m_query.c_str()); } + if (pDatabase) { pDatabase->FreeHandle(); @@ -226,26 +233,37 @@ void MysqlThread::Execute() } else if (!m_qrInfo.query_success) { state = -1; } + float diff = gpGlobals->time - m_qrInfo.queue_time; + cell c_diff = amx_ftoc(diff); + unsigned int hndl = MakeHandle(&m_qrInfo.amxinfo, Handle_Query, NullFunc); if (state != 0) { MF_ExecuteForward(m_fwd, (cell)state, - (cell)0, + (cell)hndl, m_qrInfo.amxinfo.error, m_qrInfo.amxinfo.info.errorcode, data_addr, - m_datalen); + m_datalen, + c_diff); } else { - unsigned int hndl = MakeHandle(&m_qrInfo.amxinfo, Handle_Query, NullFunc); MF_ExecuteForward(m_fwd, (cell)0, (cell)hndl, "", (cell)0, data_addr, - m_datalen); - FreeHandle(hndl); + m_datalen, + c_diff); } + FreeHandle(hndl); + if (m_qrInfo.amxinfo.pQuery) + { + m_qrInfo.amxinfo.pQuery->FreeHandle(); + m_qrInfo.amxinfo.pQuery = NULL; + } + delete [] m_qrInfo.amxinfo.opt_ptr; + m_qrInfo.amxinfo.opt_ptr = NULL; } /***************** diff --git a/dlls/sqlite/threading.h b/dlls/sqlite/threading.h index 238840b6..c8e198ab 100644 --- a/dlls/sqlite/threading.h +++ b/dlls/sqlite/threading.h @@ -12,6 +12,7 @@ struct QueuedResultInfo AmxQueryInfo amxinfo; bool connect_success; bool query_success; + float queue_time; }; class AtomicResult : diff --git a/plugins/include/sqlx.inc b/plugins/include/sqlx.inc index e348320a..199686bf 100644 --- a/plugins/include/sqlx.inc +++ b/plugins/include/sqlx.inc @@ -30,8 +30,12 @@ enum Handle * !!NOTE!! I have seen most people think that this connects to the DB. * Nowhere does it say this, and in fact it does not. It only caches * the connection information, the host/user/pass/etc. + * + * The optional timeout parameter specifies how long connections should wait before + * giving up. If 0, the default (which is undefined) is used. + * */ -native Handle:SQL_MakeDbTuple(const host[], const user[], const pass[], const db[]); +native Handle:SQL_MakeDbTuple(const host[], const user[], const pass[], const db[], timeout=0); /** @@ -74,8 +78,9 @@ native Handle:SQL_PrepareQuery(Handle:db, const fmt[], {Float,_}:...); * @param errnum - An error code, if any. * @param data - Data array you passed in. * @param size - Size of the data array you passed in. + * @param queuetime - Amount of gametime that passed while the query was resolving. * - * public QueryHandler(failstate, Handle:query, error[], errnum, data[], size) + * public QueryHandler(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime) * * Note! The handle you pass in is a DB Tuple, NOT an active connection! * Note! The handle does not need to be freed. diff --git a/plugins/testsuite/sqlxtest.sma b/plugins/testsuite/sqlxtest.sma index e3d257f7..37e1f5eb 100644 --- a/plugins/testsuite/sqlxtest.sma +++ b/plugins/testsuite/sqlxtest.sma @@ -106,22 +106,22 @@ PrintQueryData(Handle:query) /** * Handler for when a threaded query is resolved. */ -public GetMyStuff(failstate, Handle:query, error[], errnum, data[], size) +public GetMyStuff(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime) { - server_print("Resolved query %d at: %f", data[0], get_gametime()) + server_print(" --> Resolved query %d, took %f seconds", data[0], queuetime) if (failstate) { if (failstate == TQUERY_CONNECT_FAILED) { - server_print("Connection failed!") + server_print(" --> Connection failed!") } else if (failstate == TQUERY_QUERY_FAILED) { - server_print("Query failed!") + server_print(" --> Query failed!") } - server_print("Error code: %d (Message: ^"%s^")", errnum, error) + server_print(" --> Error code: %d (Message: ^"%s^")", errnum, error) new querystring[1024] SQL_GetQueryString(query, querystring, 1023) - server_print("Original query: %s", querystring) + server_print(" --> Original query: %s", querystring) } else { PrintQueryData(query) }