core/Database.cpp
changeset 1096 8e11b2ebdc22
parent 869 caa5587e708a
child 1157 61c7092246c6
     1.1 --- a/core/Database.cpp	Fri Jul 13 19:19:30 2007 +0000
     1.2 +++ b/core/Database.cpp	Fri Jul 13 19:20:12 2007 +0000
     1.3 @@ -21,12 +21,14 @@
     1.4  #include "Logger.h"
     1.5  #include "ExtensionSys.h"
     1.6  #include <stdlib.h>
     1.7 +#include "ThreadSupport.h"
     1.8  
     1.9  #define DBPARSE_LEVEL_NONE		0
    1.10  #define DBPARSE_LEVEL_MAIN		1
    1.11  #define DBPARSE_LEVEL_DATABASE	2
    1.12  
    1.13  DBManager g_DBMan;
    1.14 +static bool s_OneTimeThreaderErrorMsg = false;
    1.15  
    1.16  DBManager::DBManager() 
    1.17  : m_StrTab(512), m_ParseLevel(0), m_ParseState(0), m_pDefault(NULL)
    1.18 @@ -47,12 +49,24 @@
    1.19  	g_ShareSys.AddInterface(NULL, this);
    1.20  
    1.21  	g_SourceMod.BuildPath(Path_SM, m_Filename, sizeof(m_Filename), "configs/databases.cfg");
    1.22 +
    1.23 +	m_pConfigLock = g_pThreader->MakeMutex();
    1.24 +	m_pThinkLock = g_pThreader->MakeMutex();
    1.25 +	m_pQueueLock = g_pThreader->MakeMutex();
    1.26 +
    1.27 +	g_PluginSys.AddPluginsListener(this);
    1.28  }
    1.29  
    1.30  void DBManager::OnSourceModLevelChange(const char *mapName)
    1.31  {
    1.32  	SMCParseError err;
    1.33  	unsigned int line = 0;
    1.34 +
    1.35 +	/* We lock and don't give up the lock until we're done.
    1.36 +	 * This way the thread's search won't be searching through a
    1.37 +	 * potentially empty/corrupt list, which would be very bad.
    1.38 +	 */
    1.39 +	m_pConfigLock->Lock();
    1.40  	if ((err = g_TextParser.ParseFile_SMC(m_Filename, this, &line, NULL)) != SMCParse_Okay)
    1.41  	{
    1.42  		g_Logger.LogError("[SM] Detected parse error(s) in file \"%s\"", m_Filename);
    1.43 @@ -62,10 +76,17 @@
    1.44  			g_Logger.LogError("[SM] Line %d: %s", line, txt);
    1.45  		}
    1.46  	}
    1.47 +	m_pConfigLock->Unlock();
    1.48 +
    1.49 +	g_PluginSys.RemovePluginsListener(this);
    1.50  }
    1.51  
    1.52  void DBManager::OnSourceModShutdown()
    1.53  {
    1.54 +	KillWorkerThread();
    1.55 +	m_pConfigLock->DestroyThis();
    1.56 +	m_pThinkLock->DestroyThis();
    1.57 +	m_pQueueLock->DestroyThis();
    1.58  	g_HandleSys.RemoveType(m_DatabaseType, g_pCoreIdent);
    1.59  	g_HandleSys.RemoveType(m_DriverType, g_pCoreIdent);
    1.60  }
    1.61 @@ -269,11 +290,24 @@
    1.62  
    1.63  void DBManager::AddDriver(IDBDriver *pDriver)
    1.64  {
    1.65 +	 /* Let's kill the worker.  Join the thread and let the queries flush.
    1.66 +	  * This is kind of stupid but we just want to unload safely.
    1.67 +	  * Rather than recreate the worker, we'll wait until someone throws 
    1.68 +	  * another query through.
    1.69 +	  */
    1.70 +	KillWorkerThread();
    1.71 +
    1.72  	 m_drivers.push_back(pDriver);
    1.73  }
    1.74  
    1.75  void DBManager::RemoveDriver(IDBDriver *pDriver)
    1.76  {
    1.77 +	/* Again, we're forced to kill the worker.  How rude!
    1.78 +	 * Doing this flushes the queue, and thus we don't need to 
    1.79 +	 * clean anything else.  
    1.80 +	 */
    1.81 +	KillWorkerThread();
    1.82 +
    1.83  	for (size_t i=0; i<m_drivers.size(); i++)
    1.84  	{
    1.85  		if (m_drivers[i] == pDriver)
    1.86 @@ -293,6 +327,31 @@
    1.87  			db.realDriver = NULL;
    1.88  		}
    1.89  	}
    1.90 +
    1.91 +	/* Now that the driver is gone, we have to test the think queue.
    1.92 +	 * Whatever happens therein is up to the db op!
    1.93 +	 */
    1.94 +	Queue<IDBThreadOperation *>::iterator qiter = m_ThinkQueue.begin();
    1.95 +	Queue<IDBThreadOperation *> templist;
    1.96 +	while (qiter != m_ThinkQueue.end())
    1.97 +	{
    1.98 +		IDBThreadOperation *op = (*qiter);
    1.99 +		if (op->GetDriver() == pDriver)
   1.100 +		{
   1.101 +			templist.push(op);
   1.102 +			qiter = m_ThinkQueue.erase(qiter);
   1.103 +		} else {
   1.104 +			qiter++;
   1.105 +		}
   1.106 +	}
   1.107 +
   1.108 +	for (qiter = templist.begin();
   1.109 +		 qiter != templist.end();
   1.110 +		 qiter++)
   1.111 +	{
   1.112 +		IDBThreadOperation *op = (*qiter);
   1.113 +		op->CancelThinkPart();
   1.114 +	}
   1.115  }
   1.116  
   1.117  IDBDriver *DBManager::GetDefaultDriver()
   1.118 @@ -417,3 +476,182 @@
   1.119  
   1.120  	return NULL;
   1.121  }
   1.122 +
   1.123 +void DBManager::KillWorkerThread()
   1.124 +{
   1.125 +	if (m_pWorker)
   1.126 +	{
   1.127 +		m_pWorker->Stop(false);
   1.128 +		g_pThreader->DestroyWorker(m_pWorker);
   1.129 +		m_pWorker = NULL;
   1.130 +		s_OneTimeThreaderErrorMsg = false;
   1.131 +	}
   1.132 +}
   1.133 +
   1.134 +bool DBManager::AddToThreadQueue(IDBThreadOperation *op, PrioQueueLevel prio)
   1.135 +{
   1.136 +	if (!m_pWorker)
   1.137 +	{
   1.138 +		m_pWorker = g_pThreader->MakeWorker(this, true);
   1.139 +		if (!m_pWorker)
   1.140 +		{
   1.141 +			if (!s_OneTimeThreaderErrorMsg)
   1.142 +			{
   1.143 +				g_Logger.LogError("[SM] Unable to create db threader (error unknown)");
   1.144 +				s_OneTimeThreaderErrorMsg = true;
   1.145 +			}
   1.146 +			return false;
   1.147 +		}
   1.148 +		if (!m_pWorker->Start())
   1.149 +		{
   1.150 +			if (!s_OneTimeThreaderErrorMsg)
   1.151 +			{
   1.152 +				g_Logger.LogError("[SM] Unable to start db threader (error unknown)");
   1.153 +				s_OneTimeThreaderErrorMsg = true;
   1.154 +			}
   1.155 +			g_pThreader->DestroyWorker(m_pWorker);
   1.156 +			m_pWorker = NULL;
   1.157 +			return false;
   1.158 +		}
   1.159 +	}
   1.160 +
   1.161 +	/* Add to the queue */
   1.162 +	{
   1.163 +		m_pQueueLock->Lock();
   1.164 +		Queue<IDBThreadOperation *> &queue = m_OpQueue.GetQueue(prio);
   1.165 +		queue.push(op);
   1.166 +		m_pQueueLock->Unlock();
   1.167 +	}
   1.168 +
   1.169 +	/* Make the thread */
   1.170 +	m_pWorker->MakeThread(this);
   1.171 +
   1.172 +	return true;
   1.173 +}
   1.174 +
   1.175 +void DBManager::OnWorkerStart(IThreadWorker *pWorker)
   1.176 +{
   1.177 +	m_drSafety.clear();
   1.178 +	for (size_t i=0; i<m_drivers.size(); i++)
   1.179 +	{
   1.180 +		if (m_drivers[i]->IsThreadSafe())
   1.181 +		{
   1.182 +			m_drSafety.push_back(m_drivers[i]->InitializeThreadSafety());
   1.183 +		} else {
   1.184 +			m_drSafety.push_back(false);
   1.185 +		}
   1.186 +	}
   1.187 +}
   1.188 +
   1.189 +void DBManager::OnWorkerStop(IThreadWorker *pWorker)
   1.190 +{
   1.191 +	for (size_t i=0; i<m_drivers.size(); i++)
   1.192 +	{
   1.193 +		if (m_drSafety[i])
   1.194 +		{
   1.195 +			m_drivers[i]->ShutdownThreadSafety();
   1.196 +		}
   1.197 +	}
   1.198 +	m_drSafety.clear();
   1.199 +}
   1.200 +
   1.201 +void DBManager::RunThread(IThreadHandle *pThread)
   1.202 +{
   1.203 +	IDBThreadOperation *op = NULL;
   1.204 +
   1.205 +	/* Get something from the queue */
   1.206 +	{
   1.207 +		m_pQueueLock->Lock();
   1.208 +		Queue<IDBThreadOperation *> &queue = m_OpQueue.GetLikelyQueue();
   1.209 +		if (!queue.empty())
   1.210 +		{
   1.211 +			op = queue.first();
   1.212 +			queue.pop();
   1.213 +		}
   1.214 +		m_pQueueLock->Unlock();
   1.215 +	}
   1.216 +
   1.217 +	/* whoa.  hi.  did we get something? we should have. */
   1.218 +	if (!op)
   1.219 +	{
   1.220 +		/* wtf? */
   1.221 +		return;
   1.222 +	}
   1.223 +
   1.224 +	op->RunThreadPart();
   1.225 +
   1.226 +	m_pThinkLock->Lock();
   1.227 +	m_ThinkQueue.push(op);
   1.228 +	m_pThinkLock->Unlock();
   1.229 +}
   1.230 +
   1.231 +void DBManager::RunFrame()
   1.232 +{
   1.233 +	/* Don't bother if we're empty */
   1.234 +	if (!m_ThinkQueue.size())
   1.235 +	{
   1.236 +		return;
   1.237 +	}
   1.238 +
   1.239 +	/* Dump one thing per-frame so the server stays sane. */
   1.240 +	m_pThinkLock->Lock();
   1.241 +	IDBThreadOperation *op = m_ThinkQueue.first();
   1.242 +	m_ThinkQueue.pop();
   1.243 +	m_pThinkLock->Unlock();
   1.244 +	op->RunThinkPart();
   1.245 +}
   1.246 +
   1.247 +void DBManager::OnTerminate(IThreadHandle *pThread, bool cancel)
   1.248 +{
   1.249 +	/* Do nothing */
   1.250 +}
   1.251 +
   1.252 +void DBManager::OnPluginUnloaded(IPlugin *plugin)
   1.253 +{
   1.254 +	/* Kill the thread so we can flush everything into the think queue... */
   1.255 +	KillWorkerThread();
   1.256 +
   1.257 +	/* Mark the plugin as being unloaded so future database calls will ignore threading... */
   1.258 +	plugin->SetProperty("DisallowDBThreads", NULL);
   1.259 +
   1.260 +	/* Run all of the think operations.
   1.261 +	 * Unlike the driver unloading example, we'll let these calls go through, 
   1.262 +	 * since a plugin unloading is far more normal.
   1.263 +	 */
   1.264 +	Queue<IDBThreadOperation *>::iterator iter = m_ThinkQueue.begin();
   1.265 +	Queue<IDBThreadOperation *> templist;
   1.266 +	while (iter != m_ThinkQueue.end())
   1.267 +	{
   1.268 +		IDBThreadOperation *op = (*iter);
   1.269 +		if (op->GetPlugin() == plugin)
   1.270 +		{
   1.271 +			templist.push(op);
   1.272 +			iter = m_ThinkQueue.erase(iter);
   1.273 +		} else {
   1.274 +			iter++;
   1.275 +		}
   1.276 +	}
   1.277 +
   1.278 +	for (iter = templist.begin();
   1.279 +		 iter != templist.end();
   1.280 +		 iter++)
   1.281 +	{
   1.282 +		IDBThreadOperation *op = (*iter);
   1.283 +		op->RunThinkPart();
   1.284 +	}
   1.285 +}
   1.286 +
   1.287 +void DBManager::LockConfig()
   1.288 +{
   1.289 +	m_pConfigLock->Lock();
   1.290 +}
   1.291 +
   1.292 +void DBManager::UnlockConfig()
   1.293 +{
   1.294 +	m_pConfigLock->Unlock();
   1.295 +}
   1.296 +
   1.297 +const char *DBManager::GetDefaultDriverName()
   1.298 +{
   1.299 +	return m_DefDriver.c_str();
   1.300 +}