core/Database.cpp
author David Anderson <dvander@alliedmods.net>
Fri Jul 13 19:20:12 2007 +0000 (2007-07-13)
changeset 1096 8e11b2ebdc22
parent 869 caa5587e708a
child 1157 61c7092246c6
permissions -rw-r--r--
initial import of threaded sql functionality
     1 /**
     2  * vim: set ts=4 :
     3  * ===============================================================
     4  * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
     5  * ===============================================================
     6  *
     7  * This file is not open source and may not be copied without explicit
     8  * written permission of AlliedModders LLC.  This file may not be redistributed 
     9  * in whole or significant part.
    10  * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
    11  *
    12  * Version: $Id$
    13  */
    14 
    15 #include "Database.h"
    16 #include "HandleSys.h"
    17 #include "ShareSys.h"
    18 #include "sourcemod.h"
    19 #include "sm_stringutil.h"
    20 #include "TextParsers.h"
    21 #include "Logger.h"
    22 #include "ExtensionSys.h"
    23 #include <stdlib.h>
    24 #include "ThreadSupport.h"
    25 
    26 #define DBPARSE_LEVEL_NONE		0
    27 #define DBPARSE_LEVEL_MAIN		1
    28 #define DBPARSE_LEVEL_DATABASE	2
    29 
    30 DBManager g_DBMan;
    31 static bool s_OneTimeThreaderErrorMsg = false;
    32 
    33 DBManager::DBManager() 
    34 : m_StrTab(512), m_ParseLevel(0), m_ParseState(0), m_pDefault(NULL)
    35 {
    36 }
    37 
    38 void DBManager::OnSourceModAllInitialized()
    39 {
    40 	HandleAccess sec;
    41 
    42 	g_HandleSys.InitAccessDefaults(NULL, &sec);
    43 	sec.access[HandleAccess_Delete] |= HANDLE_RESTRICT_IDENTITY;
    44 	sec.access[HandleAccess_Clone] |= HANDLE_RESTRICT_IDENTITY;
    45 	
    46 	m_DriverType = g_HandleSys.CreateType("IDriver", this, 0, NULL, &sec, g_pCoreIdent, NULL);
    47 	m_DatabaseType = g_HandleSys.CreateType("IDatabase", this, 0, NULL, NULL, g_pCoreIdent, NULL);
    48 
    49 	g_ShareSys.AddInterface(NULL, this);
    50 
    51 	g_SourceMod.BuildPath(Path_SM, m_Filename, sizeof(m_Filename), "configs/databases.cfg");
    52 
    53 	m_pConfigLock = g_pThreader->MakeMutex();
    54 	m_pThinkLock = g_pThreader->MakeMutex();
    55 	m_pQueueLock = g_pThreader->MakeMutex();
    56 
    57 	g_PluginSys.AddPluginsListener(this);
    58 }
    59 
    60 void DBManager::OnSourceModLevelChange(const char *mapName)
    61 {
    62 	SMCParseError err;
    63 	unsigned int line = 0;
    64 
    65 	/* We lock and don't give up the lock until we're done.
    66 	 * This way the thread's search won't be searching through a
    67 	 * potentially empty/corrupt list, which would be very bad.
    68 	 */
    69 	m_pConfigLock->Lock();
    70 	if ((err = g_TextParser.ParseFile_SMC(m_Filename, this, &line, NULL)) != SMCParse_Okay)
    71 	{
    72 		g_Logger.LogError("[SM] Detected parse error(s) in file \"%s\"", m_Filename);
    73 		if (err != SMCParse_Custom)
    74 		{
    75 			const char *txt = g_TextParser.GetSMCErrorString(err);
    76 			g_Logger.LogError("[SM] Line %d: %s", line, txt);
    77 		}
    78 	}
    79 	m_pConfigLock->Unlock();
    80 
    81 	g_PluginSys.RemovePluginsListener(this);
    82 }
    83 
    84 void DBManager::OnSourceModShutdown()
    85 {
    86 	KillWorkerThread();
    87 	m_pConfigLock->DestroyThis();
    88 	m_pThinkLock->DestroyThis();
    89 	m_pQueueLock->DestroyThis();
    90 	g_HandleSys.RemoveType(m_DatabaseType, g_pCoreIdent);
    91 	g_HandleSys.RemoveType(m_DriverType, g_pCoreIdent);
    92 }
    93 
    94 unsigned int DBManager::GetInterfaceVersion()
    95 {
    96 	return SMINTERFACE_DBI_VERSION;
    97 }
    98 
    99 const char *DBManager::GetInterfaceName()
   100 {
   101 	return SMINTERFACE_DBI_NAME;
   102 }
   103 
   104 void DBManager::OnHandleDestroy(HandleType_t type, void *object)
   105 {
   106 	if (type == m_DriverType)
   107 	{
   108 		/* Ignore */
   109 		return;
   110 	}
   111 
   112 	if (g_HandleSys.TypeCheck(type, m_DatabaseType))
   113 	{
   114 		IDatabase *pdb = (IDatabase *)object;
   115 		pdb->Close();
   116 	}
   117 }
   118 
   119 void DBManager::ReadSMC_ParseStart()
   120 {
   121 	m_confs.clear();
   122 	m_ParseLevel = 0;
   123 	m_ParseState = DBPARSE_LEVEL_NONE;
   124 	m_StrTab.Reset();
   125 	m_DefDriver.clear();
   126 }
   127 
   128 ConfDbInfo s_CurInfo;
   129 SMCParseResult DBManager::ReadSMC_NewSection(const char *name, bool opt_quotes)
   130 {
   131 	if (m_ParseLevel)
   132 	{
   133 		m_ParseLevel++;
   134 		return SMCParse_Continue;
   135 	}
   136 
   137 	if (m_ParseState == DBPARSE_LEVEL_NONE)
   138 	{
   139 		if (strcmp(name, "Databases") == 0)
   140 		{
   141 			m_ParseState = DBPARSE_LEVEL_MAIN;
   142 		} else {
   143 			m_ParseLevel++;
   144 		}
   145 	} else if (m_ParseState == DBPARSE_LEVEL_MAIN) {
   146 		s_CurInfo = ConfDbInfo();
   147 		s_CurInfo.name = m_StrTab.AddString(name);
   148 		m_ParseState = DBPARSE_LEVEL_DATABASE;
   149 	} else if (m_ParseState == DBPARSE_LEVEL_DATABASE) {
   150 		m_ParseLevel++;
   151 	}
   152 
   153 	return SMCParse_Continue;
   154 }
   155 
   156 SMCParseResult DBManager::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
   157 {
   158 	if (m_ParseLevel)
   159 	{
   160 		return SMCParse_Continue;
   161 	}
   162 
   163 	if (m_ParseState == DBPARSE_LEVEL_MAIN)
   164 	{
   165 		if (strcmp(key, "driver_default") == 0)
   166 		{
   167 			m_DefDriver.assign(value);
   168 		}
   169 	} else if (m_ParseState == DBPARSE_LEVEL_DATABASE) {
   170 		if (strcmp(key, "driver") == 0)
   171 		{
   172 			if (strcmp(value, "default") != 0)
   173 			{
   174 				s_CurInfo.driver = m_StrTab.AddString(value);
   175 			}
   176 		} else if (strcmp(key, "database") == 0) {
   177 			s_CurInfo.database = m_StrTab.AddString(value);
   178 		} else if (strcmp(key, "host") == 0) {
   179 			s_CurInfo.host = m_StrTab.AddString(value);
   180 		} else if (strcmp(key, "user") == 0) {
   181 			s_CurInfo.user = m_StrTab.AddString(value);
   182 		} else if (strcmp(key, "pass") == 0) {
   183 			s_CurInfo.pass = m_StrTab.AddString(value);
   184 		} else if (strcmp(key, "timeout") == 0) {
   185 			s_CurInfo.info.maxTimeout = atoi(value);
   186 		} else if (strcmp(key, "port") == 0) {
   187 			s_CurInfo.info.port = atoi(value);
   188 		}
   189 	}
   190 
   191 	return SMCParse_Continue;
   192 }
   193 
   194 #define ASSIGN_VAR(var) \
   195 	if (s_CurInfo.var == -1) { \
   196 		s_CurInfo.info.var = ""; \
   197 	} else { \
   198 		s_CurInfo.info.var = m_StrTab.GetString(s_CurInfo.var); \
   199 	}
   200 
   201 SMCParseResult DBManager::ReadSMC_LeavingSection()
   202 {
   203 	if (m_ParseLevel)
   204 	{
   205 		m_ParseLevel--;
   206 		return SMCParse_Continue;
   207 	}
   208 
   209 	if (m_ParseState == DBPARSE_LEVEL_DATABASE)
   210 	{
   211 		/* Set all of the info members to either a blank string
   212 		 * or the string pointer from the string table.
   213 		 */
   214 		ASSIGN_VAR(driver);
   215 		ASSIGN_VAR(database);
   216 		ASSIGN_VAR(host);
   217 		ASSIGN_VAR(user);
   218 		ASSIGN_VAR(pass);
   219 		
   220 		/* Save it.. */
   221 		m_confs.push_back(s_CurInfo);
   222 
   223 		/* Go up one level */
   224 		m_ParseState = DBPARSE_LEVEL_MAIN;
   225 	} else if (m_ParseState == DBPARSE_LEVEL_MAIN) {
   226 		m_ParseState = DBPARSE_LEVEL_NONE;
   227 		return SMCParse_Halt;
   228 	}
   229 
   230 	return SMCParse_Continue;
   231 }
   232 #undef ASSIGN_VAR
   233 
   234 void DBManager::ReadSMC_ParseEnd(bool halted, bool failed)
   235 {
   236 }
   237 
   238 bool DBManager::Connect(const char *name, IDBDriver **pdr, IDatabase **pdb, bool persistent, char *error, size_t maxlength)
   239 {
   240 	ConfDbInfo *pInfo = GetDatabaseConf(name);
   241 
   242 	if (!pInfo)
   243 	{
   244 		if (pdr)
   245 		{
   246 			*pdr = NULL;
   247 		}
   248 		*pdb = NULL;
   249 		UTIL_Format(error, maxlength, "Configuration \"%s\" not found", name);
   250 		return false;
   251 	}
   252 
   253 	const char *dname = pInfo->info.driver;
   254 	if (!pInfo->realDriver)
   255 	{
   256 		/* Try to assign a real driver pointer */
   257 		if (pInfo->info.driver[0] == '\0')
   258 		{
   259 			if (!m_pDefault && m_DefDriver.size() > 0)
   260 			{
   261 				m_pDefault = FindOrLoadDriver(m_DefDriver.c_str());
   262 			}
   263 			dname = m_DefDriver.size() ? m_DefDriver.c_str() : "default";
   264 			pInfo->realDriver = m_pDefault;
   265 		} else {
   266 			pInfo->realDriver = FindOrLoadDriver(pInfo->info.driver);
   267 		}
   268 	}
   269 
   270 	if (pInfo->realDriver)
   271 	{
   272 		if (pdr)
   273 		{
   274 			*pdr = pInfo->realDriver;
   275 		}
   276 		*pdb = pInfo->realDriver->Connect(&pInfo->info, persistent, error, maxlength);
   277 		return (*pdb != NULL);
   278 	}
   279 
   280 	if (pdr)
   281 	{
   282 		*pdr = NULL;
   283 	}
   284 	*pdb = NULL;
   285 
   286 	UTIL_Format(error, maxlength, "Driver \"%s\" not found", dname);
   287 
   288 	return false;
   289 }
   290 
   291 void DBManager::AddDriver(IDBDriver *pDriver)
   292 {
   293 	 /* Let's kill the worker.  Join the thread and let the queries flush.
   294 	  * This is kind of stupid but we just want to unload safely.
   295 	  * Rather than recreate the worker, we'll wait until someone throws 
   296 	  * another query through.
   297 	  */
   298 	KillWorkerThread();
   299 
   300 	 m_drivers.push_back(pDriver);
   301 }
   302 
   303 void DBManager::RemoveDriver(IDBDriver *pDriver)
   304 {
   305 	/* Again, we're forced to kill the worker.  How rude!
   306 	 * Doing this flushes the queue, and thus we don't need to 
   307 	 * clean anything else.  
   308 	 */
   309 	KillWorkerThread();
   310 
   311 	for (size_t i=0; i<m_drivers.size(); i++)
   312 	{
   313 		if (m_drivers[i] == pDriver)
   314 		{
   315 			m_drivers.erase(m_drivers.iterAt(i));
   316 			return;
   317 		}
   318 	}
   319 
   320 	/* Make sure NOTHING references this! */
   321 	List<ConfDbInfo>::iterator iter;
   322 	for (iter=m_confs.begin(); iter!=m_confs.end(); iter++)
   323 	{
   324 		ConfDbInfo &db = (*iter);
   325 		if (db.realDriver == pDriver)
   326 		{
   327 			db.realDriver = NULL;
   328 		}
   329 	}
   330 
   331 	/* Now that the driver is gone, we have to test the think queue.
   332 	 * Whatever happens therein is up to the db op!
   333 	 */
   334 	Queue<IDBThreadOperation *>::iterator qiter = m_ThinkQueue.begin();
   335 	Queue<IDBThreadOperation *> templist;
   336 	while (qiter != m_ThinkQueue.end())
   337 	{
   338 		IDBThreadOperation *op = (*qiter);
   339 		if (op->GetDriver() == pDriver)
   340 		{
   341 			templist.push(op);
   342 			qiter = m_ThinkQueue.erase(qiter);
   343 		} else {
   344 			qiter++;
   345 		}
   346 	}
   347 
   348 	for (qiter = templist.begin();
   349 		 qiter != templist.end();
   350 		 qiter++)
   351 	{
   352 		IDBThreadOperation *op = (*qiter);
   353 		op->CancelThinkPart();
   354 	}
   355 }
   356 
   357 IDBDriver *DBManager::GetDefaultDriver()
   358 {
   359 	if (!m_pDefault && m_DefDriver.size() > 0)
   360 	{
   361 		m_pDefault = FindOrLoadDriver(m_DefDriver.c_str());
   362 	}
   363 
   364 	return m_pDefault;
   365 }
   366 
   367 Handle_t DBManager::CreateHandle(DBHandleType dtype, void *ptr, IdentityToken_t *pToken)
   368 {
   369 	HandleType_t type = 0;
   370 
   371 	if (dtype == DBHandle_Driver)
   372 	{
   373 		type = m_DriverType;
   374 	} else if (dtype == DBHandle_Database) {
   375 		type = m_DatabaseType;
   376 	} else {
   377 		return BAD_HANDLE;
   378 	}
   379 
   380 	return g_HandleSys.CreateHandle(type, ptr, pToken, g_pCoreIdent, NULL);
   381 }
   382 
   383 HandleError DBManager::ReadHandle(Handle_t hndl, DBHandleType dtype, void **ptr)
   384 {
   385 	HandleType_t type = 0;
   386 
   387 	if (dtype == DBHandle_Driver)
   388 	{
   389 		type = m_DriverType;
   390 	} else if (dtype == DBHandle_Database) {
   391 		type = m_DatabaseType;
   392 	} else {
   393 		return HandleError_Type;
   394 	}
   395 
   396 	HandleSecurity sec(NULL, g_pCoreIdent);
   397 
   398 	return g_HandleSys.ReadHandle(hndl, type, &sec, ptr);
   399 }
   400 
   401 HandleError DBManager::ReleaseHandle(Handle_t hndl, DBHandleType type, IdentityToken_t *token)
   402 {
   403 	HandleSecurity sec(token, g_pCoreIdent);
   404 	return g_HandleSys.FreeHandle(hndl, &sec);
   405 }
   406 
   407 unsigned int DBManager::GetDriverCount()
   408 {
   409 	return (unsigned int)m_drivers.size();
   410 }
   411 
   412 IDBDriver *DBManager::GetDriver(unsigned int index)
   413 {
   414 	if (index >= m_drivers.size())
   415 	{
   416 		return NULL;
   417 	}
   418 
   419 	return m_drivers[index];
   420 }
   421 
   422 const DatabaseInfo *DBManager::FindDatabaseConf(const char *name)
   423 {
   424 	ConfDbInfo *info = GetDatabaseConf(name);
   425 	if (!info)
   426 	{
   427 		return NULL;
   428 	}
   429 
   430 	return &info->info;
   431 }
   432 
   433 ConfDbInfo *DBManager::GetDatabaseConf(const char *name)
   434 {
   435 	List<ConfDbInfo>::iterator iter;
   436 
   437 	for (iter=m_confs.begin(); iter!=m_confs.end(); iter++)
   438 	{
   439 		ConfDbInfo &conf = (*iter);
   440 		if (strcmp(m_StrTab.GetString(conf.name), name) == 0)
   441 		{
   442 			return &conf;
   443 		}
   444 	}
   445 
   446 	return NULL;
   447 }
   448 
   449 IDBDriver *DBManager::FindOrLoadDriver(const char *name)
   450 {
   451 	size_t last_size = m_drivers.size();
   452 	for (size_t i=0; i<last_size; i++)
   453 	{
   454 		if (strcmp(m_drivers[i]->GetIdentifier(), name) == 0)
   455 		{
   456 			return m_drivers[i];
   457 		}
   458 	}
   459 
   460 	char filename[PLATFORM_MAX_PATH];
   461 	UTIL_Format(filename, sizeof(filename), "dbi.%s.ext", name);
   462 
   463 	IExtension *pExt = g_Extensions.LoadAutoExtension(filename);
   464 	if (!pExt || !pExt->IsLoaded() || m_drivers.size() <= last_size)
   465 	{
   466 		return NULL;
   467 	}
   468 
   469 	/* last_size is now gauranteed to be a valid index.
   470 	 * The identifier must match the name.
   471 	 */
   472 	if (strcmp(m_drivers[last_size]->GetIdentifier(), name) == 0)
   473 	{
   474 		return m_drivers[last_size];
   475 	}
   476 
   477 	return NULL;
   478 }
   479 
   480 void DBManager::KillWorkerThread()
   481 {
   482 	if (m_pWorker)
   483 	{
   484 		m_pWorker->Stop(false);
   485 		g_pThreader->DestroyWorker(m_pWorker);
   486 		m_pWorker = NULL;
   487 		s_OneTimeThreaderErrorMsg = false;
   488 	}
   489 }
   490 
   491 bool DBManager::AddToThreadQueue(IDBThreadOperation *op, PrioQueueLevel prio)
   492 {
   493 	if (!m_pWorker)
   494 	{
   495 		m_pWorker = g_pThreader->MakeWorker(this, true);
   496 		if (!m_pWorker)
   497 		{
   498 			if (!s_OneTimeThreaderErrorMsg)
   499 			{
   500 				g_Logger.LogError("[SM] Unable to create db threader (error unknown)");
   501 				s_OneTimeThreaderErrorMsg = true;
   502 			}
   503 			return false;
   504 		}
   505 		if (!m_pWorker->Start())
   506 		{
   507 			if (!s_OneTimeThreaderErrorMsg)
   508 			{
   509 				g_Logger.LogError("[SM] Unable to start db threader (error unknown)");
   510 				s_OneTimeThreaderErrorMsg = true;
   511 			}
   512 			g_pThreader->DestroyWorker(m_pWorker);
   513 			m_pWorker = NULL;
   514 			return false;
   515 		}
   516 	}
   517 
   518 	/* Add to the queue */
   519 	{
   520 		m_pQueueLock->Lock();
   521 		Queue<IDBThreadOperation *> &queue = m_OpQueue.GetQueue(prio);
   522 		queue.push(op);
   523 		m_pQueueLock->Unlock();
   524 	}
   525 
   526 	/* Make the thread */
   527 	m_pWorker->MakeThread(this);
   528 
   529 	return true;
   530 }
   531 
   532 void DBManager::OnWorkerStart(IThreadWorker *pWorker)
   533 {
   534 	m_drSafety.clear();
   535 	for (size_t i=0; i<m_drivers.size(); i++)
   536 	{
   537 		if (m_drivers[i]->IsThreadSafe())
   538 		{
   539 			m_drSafety.push_back(m_drivers[i]->InitializeThreadSafety());
   540 		} else {
   541 			m_drSafety.push_back(false);
   542 		}
   543 	}
   544 }
   545 
   546 void DBManager::OnWorkerStop(IThreadWorker *pWorker)
   547 {
   548 	for (size_t i=0; i<m_drivers.size(); i++)
   549 	{
   550 		if (m_drSafety[i])
   551 		{
   552 			m_drivers[i]->ShutdownThreadSafety();
   553 		}
   554 	}
   555 	m_drSafety.clear();
   556 }
   557 
   558 void DBManager::RunThread(IThreadHandle *pThread)
   559 {
   560 	IDBThreadOperation *op = NULL;
   561 
   562 	/* Get something from the queue */
   563 	{
   564 		m_pQueueLock->Lock();
   565 		Queue<IDBThreadOperation *> &queue = m_OpQueue.GetLikelyQueue();
   566 		if (!queue.empty())
   567 		{
   568 			op = queue.first();
   569 			queue.pop();
   570 		}
   571 		m_pQueueLock->Unlock();
   572 	}
   573 
   574 	/* whoa.  hi.  did we get something? we should have. */
   575 	if (!op)
   576 	{
   577 		/* wtf? */
   578 		return;
   579 	}
   580 
   581 	op->RunThreadPart();
   582 
   583 	m_pThinkLock->Lock();
   584 	m_ThinkQueue.push(op);
   585 	m_pThinkLock->Unlock();
   586 }
   587 
   588 void DBManager::RunFrame()
   589 {
   590 	/* Don't bother if we're empty */
   591 	if (!m_ThinkQueue.size())
   592 	{
   593 		return;
   594 	}
   595 
   596 	/* Dump one thing per-frame so the server stays sane. */
   597 	m_pThinkLock->Lock();
   598 	IDBThreadOperation *op = m_ThinkQueue.first();
   599 	m_ThinkQueue.pop();
   600 	m_pThinkLock->Unlock();
   601 	op->RunThinkPart();
   602 }
   603 
   604 void DBManager::OnTerminate(IThreadHandle *pThread, bool cancel)
   605 {
   606 	/* Do nothing */
   607 }
   608 
   609 void DBManager::OnPluginUnloaded(IPlugin *plugin)
   610 {
   611 	/* Kill the thread so we can flush everything into the think queue... */
   612 	KillWorkerThread();
   613 
   614 	/* Mark the plugin as being unloaded so future database calls will ignore threading... */
   615 	plugin->SetProperty("DisallowDBThreads", NULL);
   616 
   617 	/* Run all of the think operations.
   618 	 * Unlike the driver unloading example, we'll let these calls go through, 
   619 	 * since a plugin unloading is far more normal.
   620 	 */
   621 	Queue<IDBThreadOperation *>::iterator iter = m_ThinkQueue.begin();
   622 	Queue<IDBThreadOperation *> templist;
   623 	while (iter != m_ThinkQueue.end())
   624 	{
   625 		IDBThreadOperation *op = (*iter);
   626 		if (op->GetPlugin() == plugin)
   627 		{
   628 			templist.push(op);
   629 			iter = m_ThinkQueue.erase(iter);
   630 		} else {
   631 			iter++;
   632 		}
   633 	}
   634 
   635 	for (iter = templist.begin();
   636 		 iter != templist.end();
   637 		 iter++)
   638 	{
   639 		IDBThreadOperation *op = (*iter);
   640 		op->RunThinkPart();
   641 	}
   642 }
   643 
   644 void DBManager::LockConfig()
   645 {
   646 	m_pConfigLock->Lock();
   647 }
   648 
   649 void DBManager::UnlockConfig()
   650 {
   651 	m_pConfigLock->Unlock();
   652 }
   653 
   654 const char *DBManager::GetDefaultDriverName()
   655 {
   656 	return m_DefDriver.c_str();
   657 }