PageRenderTime 41ms CodeModel.GetById 8ms RepoModel.GetById 1ms app.codeStats 0ms

/src/oregoncore/Master.cpp

https://bitbucket.org/oneb1t/crocoduckcore/
C++ | 542 lines | 385 code | 85 blank | 72 comment | 43 complexity | 150ce3c49717fc5d577b628ba6a2eb76 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. /*
  2. * Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
  3. *
  4. * Copyright (C) 2008 Trinity <http://www.trinitycore.org/>
  5. *
  6. * Copyright (C) 2010 Oregon <http://www.oregoncore.com/>
  7. *
  8. * This program is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  21. */
  22. #include <ace/OS_NS_signal.h>
  23. #include "WorldSocketMgr.h"
  24. #include "Common.h"
  25. #include "Master.h"
  26. #include "WorldSocket.h"
  27. #include "WorldRunnable.h"
  28. #include "World.h"
  29. #include "Log.h"
  30. #include "Timer.h"
  31. #include "Policies/SingletonImp.h"
  32. #include "SystemConfig.h"
  33. #include "Config/Config.h"
  34. #include "Database/DatabaseEnv.h"
  35. #include "DBCStores.h"
  36. #include "CliRunnable.h"
  37. #include "RASocket.h"
  38. #include "Util.h"
  39. #include "OCSoap.h"
  40. #include "sockets/TcpSocket.h"
  41. #include "sockets/Utility.h"
  42. #include "sockets/Parse.h"
  43. #include "sockets/Socket.h"
  44. #include "sockets/SocketHandler.h"
  45. #include "sockets/ListenSocket.h"
  46. #ifdef _WIN32
  47. #include "ServiceWin32.h"
  48. extern int m_ServiceStatus;
  49. #endif
  50. INSTANTIATE_SINGLETON_1( Master );
  51. volatile uint32 Master::m_masterLoopCounter = 0;
  52. class FreezeDetectorRunnable : public ACE_Based::Runnable
  53. {
  54. public:
  55. FreezeDetectorRunnable() { _delaytime = 0; }
  56. uint32 m_loops, m_lastchange;
  57. uint32 w_loops, w_lastchange;
  58. uint32 _delaytime;
  59. void SetDelayTime(uint32 t) { _delaytime = t; }
  60. void run(void)
  61. {
  62. if (!_delaytime)
  63. return;
  64. sLog.outString("Starting up anti-freeze thread (%u seconds max stuck time)...",_delaytime/1000);
  65. m_loops = 0;
  66. w_loops = 0;
  67. m_lastchange = 0;
  68. w_lastchange = 0;
  69. while (!World::IsStopped())
  70. {
  71. ACE_Based::Thread::Sleep(1000);
  72. uint32 curtime = getMSTime();
  73. // normal work
  74. if (w_loops != World::m_worldLoopCounter)
  75. {
  76. w_lastchange = curtime;
  77. w_loops = World::m_worldLoopCounter;
  78. }
  79. // possible freeze
  80. else if (getMSTimeDiff(w_lastchange,curtime) > _delaytime)
  81. {
  82. sLog.outError("World Thread is stuck. Terminating server!");
  83. *((uint32 volatile*)NULL) = 0; // bang crash
  84. }
  85. }
  86. sLog.outString("Anti-freeze thread exiting without problems.");
  87. }
  88. };
  89. class RARunnable : public ACE_Based::Runnable
  90. {
  91. public:
  92. uint32 numLoops, loopCounter;
  93. RARunnable ()
  94. {
  95. uint32 socketSelecttime = sWorld.getConfig (CONFIG_SOCKET_SELECTTIME);
  96. numLoops = (sConfig.GetIntDefault ("MaxPingTime", 30) * (MINUTE * 1000000 / socketSelecttime));
  97. loopCounter = 0;
  98. }
  99. void checkping ()
  100. {
  101. // ping if need
  102. if ((++loopCounter) == numLoops)
  103. {
  104. loopCounter = 0;
  105. sLog.outDetail ("Ping MySQL to keep connection alive");
  106. WorldDatabase.Query ("SELECT 1 FROM command LIMIT 1");
  107. LoginDatabase.Query ("SELECT 1 FROM realmlist LIMIT 1");
  108. CharacterDatabase.Query ("SELECT 1 FROM bugreport LIMIT 1");
  109. }
  110. }
  111. void run ()
  112. {
  113. SocketHandler h;
  114. // Launch the RA listener socket
  115. ListenSocket<RASocket> RAListenSocket (h);
  116. bool usera = sConfig.GetBoolDefault ("Ra.Enable", false);
  117. if (usera)
  118. {
  119. port_t raport = sConfig.GetIntDefault ("Ra.Port", 3443);
  120. std::string stringip = sConfig.GetStringDefault ("Ra.IP", "0.0.0.0");
  121. ipaddr_t raip;
  122. if (!Utility::u2ip (stringip, raip))
  123. sLog.outError ("Oregon RA can not bind to ip %s", stringip.c_str ());
  124. else if (RAListenSocket.Bind (raip, raport))
  125. sLog.outError ("Oregon RA can not bind to port %d on %s", raport, stringip.c_str ());
  126. else
  127. {
  128. h.Add (&RAListenSocket);
  129. sLog.outString ("Starting Remote access listener on port %d on %s", raport, stringip.c_str ());
  130. }
  131. }
  132. // Socket Select time is in microseconds , not miliseconds!!
  133. uint32 socketSelecttime = sWorld.getConfig (CONFIG_SOCKET_SELECTTIME);
  134. // if use ra spend time waiting for io, if not use ra ,just sleep
  135. if (usera)
  136. {
  137. while (!World::IsStopped())
  138. {
  139. h.Select (0, socketSelecttime);
  140. checkping ();
  141. }
  142. }
  143. else
  144. {
  145. while (!World::IsStopped())
  146. {
  147. ACE_Based::Thread::Sleep(static_cast<unsigned long> (socketSelecttime / 1000));
  148. checkping ();
  149. }
  150. }
  151. }
  152. };
  153. Master::Master()
  154. {
  155. }
  156. Master::~Master()
  157. {
  158. }
  159. // Main function
  160. int Master::Run()
  161. {
  162. sLog.outString( "%s (core-daemon)", _FULLVERSION );
  163. sLog.outString( "<Ctrl-C> to stop.\n" );
  164. sLog.outString( " _____ " );
  165. sLog.outString( " /\\ __`\\ " );
  166. sLog.outString( " \\ \\ \\/\\ \\ _ __ __ __ ___ ___ " );
  167. sLog.outString( " \\ \\ \\ \\ \\/\\`'__\\'__`\\ /'_ `\\ / __`\\/' _ `\\ " );
  168. sLog.outString( " \\ \\ \\_\\ \\ \\ \\/\\ __//\\ \\L\\ \\/\\ \\L\\ \\\\ \\/\\ \\ " );
  169. sLog.outString( " \\ \\_____\\ \\_\\ \\____\\ \\____ \\ \\____/ \\_\\ \\_\\ " );
  170. sLog.outString( " \\/_____/\\/_/\\/____/\\/___L\\ \\/___/ \\/_/\\/_/ " );
  171. sLog.outString( " /\\____/ " );
  172. sLog.outString( " \\_/__/ " );
  173. sLog.outString( " http://www.oregoncore.com \n " );
  174. // worldd PID file creation
  175. std::string pidfile = sConfig.GetStringDefault("PidFile", "");
  176. if (!pidfile.empty())
  177. {
  178. uint32 pid = CreatePIDFile(pidfile);
  179. if (!pid)
  180. {
  181. sLog.outError( "Cannot create PID file %s.\n", pidfile.c_str() );
  182. return 1;
  183. }
  184. sLog.outString( "Daemon PID: %u\n", pid );
  185. }
  186. // Start the databases
  187. if (!_StartDB())
  188. return 1;
  189. // Initialize the World
  190. sWorld.SetInitialWorldSettings();
  191. // Catch termination signals
  192. _HookSignals();
  193. // Launch WorldRunnable thread
  194. ACE_Based::Thread world_thread(new WorldRunnable);
  195. world_thread.setPriority(ACE_Based::Highest);
  196. // set realmbuilds depend on OregonCore expected builds, and set server online
  197. std::string builds = AcceptableClientBuildsListStr();
  198. LoginDatabase.escape_string(builds);
  199. LoginDatabase.PExecute("UPDATE realmlist SET color = 0, population = 0 WHERE id = '%d'", realmID);
  200. ACE_Based::Thread* cliThread = NULL;
  201. #ifdef _WIN32
  202. if (sConfig.GetBoolDefault("Console.Enable", true) && (m_ServiceStatus == -1)/* need disable console in service mode*/)
  203. #else
  204. if (sConfig.GetBoolDefault("Console.Enable", true))
  205. #endif
  206. {
  207. // Launch CliRunnable thread
  208. cliThread = new ACE_Based::Thread(new CliRunnable);
  209. }
  210. ACE_Based::Thread rar_thread(new RARunnable);
  211. // Handle affinity for multiple processors and process priority on Windows
  212. #ifdef _WIN32
  213. {
  214. HANDLE hProcess = GetCurrentProcess();
  215. uint32 Aff = sConfig.GetIntDefault("UseProcessors", 0);
  216. if (Aff > 0)
  217. {
  218. ULONG_PTR appAff;
  219. ULONG_PTR sysAff;
  220. if (GetProcessAffinityMask(hProcess,&appAff,&sysAff))
  221. {
  222. ULONG_PTR curAff = Aff & appAff; // remove non accessible processors
  223. if (!curAff )
  224. {
  225. sLog.outError("Processors marked in UseProcessors bitmask (hex) %x not accessible for OregonCore. Accessible processors bitmask (hex): %x",Aff,appAff);
  226. }
  227. else
  228. {
  229. if (SetProcessAffinityMask(hProcess,curAff))
  230. sLog.outString("Using processors (bitmask, hex): %x", curAff);
  231. else
  232. sLog.outError("Can't set used processors (hex): %x",curAff);
  233. }
  234. }
  235. sLog.outString();
  236. }
  237. bool Prio = sConfig.GetBoolDefault("ProcessPriority", false);
  238. if (Prio)
  239. {
  240. if (SetPriorityClass(hProcess,HIGH_PRIORITY_CLASS))
  241. sLog.outString("OregonCore process priority class set to HIGH");
  242. else
  243. sLog.outError("ERROR: Can't set OregonCore process priority class.");
  244. sLog.outString();
  245. }
  246. }
  247. #endif
  248. // Start soap serving thread
  249. ACE_Based::Thread* soap_thread = NULL;
  250. if (sConfig.GetBoolDefault("SOAP.Enabled", false))
  251. {
  252. OCSoapRunnable *runnable = new OCSoapRunnable();
  253. runnable->setListenArguments(sConfig.GetStringDefault("SOAP.IP", "127.0.0.1"), sConfig.GetIntDefault("SOAP.Port", 7878));
  254. soap_thread = new ACE_Based::Thread(runnable);
  255. }
  256. uint32 realCurrTime, realPrevTime;
  257. realCurrTime = realPrevTime = getMSTime();
  258. uint32 socketSelecttime = sWorld.getConfig(CONFIG_SOCKET_SELECTTIME);
  259. // Start up freeze catcher thread
  260. ACE_Based::Thread* freeze_thread = NULL;
  261. if (uint32 freeze_delay = sConfig.GetIntDefault("MaxCoreStuckTime", 0))
  262. {
  263. FreezeDetectorRunnable *fdr = new FreezeDetectorRunnable();
  264. fdr->SetDelayTime(freeze_delay*1000);
  265. freeze_thread = new ACE_Based::Thread(fdr);
  266. freeze_thread->setPriority(ACE_Based::Highest);
  267. }
  268. // Launch the world listener socket
  269. port_t wsport = sWorld.getConfig (CONFIG_PORT_WORLD);
  270. std::string bind_ip = sConfig.GetStringDefault ("BindIP", "0.0.0.0");
  271. if (sWorldSocketMgr->StartNetwork (wsport, bind_ip.c_str ()) == -1)
  272. {
  273. sLog.outError ("Failed to start network");
  274. World::StopNow(ERROR_EXIT_CODE);
  275. // go down and shutdown the server
  276. }
  277. sWorldSocketMgr->Wait();
  278. // Stop freeze protection before shutdown tasks
  279. if (freeze_thread)
  280. {
  281. freeze_thread->destroy();
  282. delete freeze_thread;
  283. }
  284. // Stop soap thread
  285. if (soap_thread)
  286. {
  287. soap_thread->wait();
  288. soap_thread->destroy();
  289. delete soap_thread;
  290. }
  291. // Set server offline in realmlist
  292. LoginDatabase.PExecute("UPDATE realmlist SET color = 2 WHERE id = '%d'", realmID);
  293. // Remove signal handling before leaving
  294. _UnhookSignals();
  295. // when the main thread closes the singletons get unloaded
  296. // since worldrunnable uses them, it will crash if unloaded after master
  297. world_thread.wait();
  298. rar_thread.wait ();
  299. // Clean account database before leaving
  300. clearOnlineAccounts();
  301. // Wait for delay threads to end
  302. CharacterDatabase.HaltDelayThread();
  303. WorldDatabase.HaltDelayThread();
  304. LoginDatabase.HaltDelayThread();
  305. sLog.outString( "Halting process..." );
  306. if (cliThread)
  307. {
  308. #ifdef _WIN32
  309. // this only way to terminate CLI thread exist at Win32 (alt. way exist only in Windows Vista API)
  310. //_exit(1);
  311. // send keyboard input to safely unblock the CLI thread
  312. INPUT_RECORD b[5];
  313. HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
  314. b[0].EventType = KEY_EVENT;
  315. b[0].Event.KeyEvent.bKeyDown = TRUE;
  316. b[0].Event.KeyEvent.uChar.AsciiChar = 'X';
  317. b[0].Event.KeyEvent.wVirtualKeyCode = 'X';
  318. b[0].Event.KeyEvent.wRepeatCount = 1;
  319. b[1].EventType = KEY_EVENT;
  320. b[1].Event.KeyEvent.bKeyDown = FALSE;
  321. b[1].Event.KeyEvent.uChar.AsciiChar = 'X';
  322. b[1].Event.KeyEvent.wVirtualKeyCode = 'X';
  323. b[1].Event.KeyEvent.wRepeatCount = 1;
  324. b[2].EventType = KEY_EVENT;
  325. b[2].Event.KeyEvent.bKeyDown = TRUE;
  326. b[2].Event.KeyEvent.dwControlKeyState = 0;
  327. b[2].Event.KeyEvent.uChar.AsciiChar = '\r';
  328. b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
  329. b[2].Event.KeyEvent.wRepeatCount = 1;
  330. b[2].Event.KeyEvent.wVirtualScanCode = 0x1c;
  331. b[3].EventType = KEY_EVENT;
  332. b[3].Event.KeyEvent.bKeyDown = FALSE;
  333. b[3].Event.KeyEvent.dwControlKeyState = 0;
  334. b[3].Event.KeyEvent.uChar.AsciiChar = '\r';
  335. b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
  336. b[3].Event.KeyEvent.wVirtualScanCode = 0x1c;
  337. b[3].Event.KeyEvent.wRepeatCount = 1;
  338. DWORD numb;
  339. WriteConsoleInput(hStdIn, b, 4, &numb);
  340. cliThread->wait();
  341. #else
  342. cliThread->destroy();
  343. #endif
  344. delete cliThread;
  345. }
  346. // for some unknown reason, unloading scripts here and not in worldrunnable
  347. // fixes a memory leak related to detaching threads from the module
  348. //UnloadScriptingModule();
  349. // Exit the process with specified return value
  350. return World::GetExitCode();
  351. }
  352. // Initialize connection to the databases
  353. bool Master::_StartDB()
  354. {
  355. sLog.SetLogDB(false);
  356. // Get world database info from configuration file
  357. std::string dbstring = sConfig.GetStringDefault("WorldDatabaseInfo", "");
  358. if (dbstring.empty())
  359. {
  360. sLog.outError("World database not specified in configuration file");
  361. return false;
  362. }
  363. // Initialise the world database
  364. if (!WorldDatabase.Initialize(dbstring.c_str()))
  365. {
  366. sLog.outError("Cannot connect to world database %s",dbstring.c_str());
  367. return false;
  368. }
  369. // Get character database info from configuration file
  370. dbstring = sConfig.GetStringDefault("CharacterDatabaseInfo", "");
  371. if (dbstring.empty())
  372. {
  373. sLog.outError("Character database not specified in configuration file");
  374. return false;
  375. }
  376. // Initialise the Character database
  377. if (!CharacterDatabase.Initialize(dbstring.c_str()))
  378. {
  379. sLog.outError("Cannot connect to Character database %s",dbstring.c_str());
  380. return false;
  381. }
  382. // Get login database info from configuration file
  383. dbstring = sConfig.GetStringDefault("LoginDatabaseInfo", "");
  384. if (dbstring.empty())
  385. {
  386. sLog.outError("Login database not specified in configuration file");
  387. return false;
  388. }
  389. // Initialise the login database
  390. if (!LoginDatabase.Initialize(dbstring.c_str()))
  391. {
  392. sLog.outError("Cannot connect to login database %s",dbstring.c_str());
  393. return false;
  394. }
  395. // Get the realm Id from the configuration file
  396. realmID = sConfig.GetIntDefault("RealmID", 0);
  397. if (!realmID)
  398. {
  399. sLog.outError("Realm ID not defined in configuration file");
  400. return false;
  401. }
  402. sLog.outString("Realm running as realm ID %d", realmID);
  403. // Initialize the DB logging system
  404. sLog.SetLogDBLater(sConfig.GetBoolDefault("EnableLogDB", false)); // set var to enable DB logging once startup finished.
  405. sLog.SetLogDB(false);
  406. sLog.SetRealmID(realmID);
  407. // Clean the database before starting
  408. clearOnlineAccounts();
  409. // Insert version info into DB
  410. WorldDatabase.PExecute("UPDATE version SET core_version = '%s', core_revision = '%s'", _FULLVERSION, _REVISION);
  411. sWorld.LoadDBVersion();
  412. sLog.outString("Using World DB: %s", sWorld.GetDBVersion());
  413. return true;
  414. }
  415. // Clear 'online' status for all accounts with characters in this realm
  416. void Master::clearOnlineAccounts()
  417. {
  418. // Cleanup online status for characters hosted at current realm
  419. // todo - Only accounts with characters logged on *this* realm should have online status reset. Move the online column from 'account' to 'realmcharacters'?
  420. LoginDatabase.PExecute("UPDATE account SET online = 0 WHERE online > 0 AND id IN (SELECT acctid FROM realmcharacters WHERE realmid = '%d')", realmID);
  421. CharacterDatabase.Execute("UPDATE characters SET online = 0 WHERE online<>0");
  422. }
  423. // Handle termination signals
  424. void Master::_OnSignal(int s)
  425. {
  426. switch (s)
  427. {
  428. case SIGINT:
  429. World::StopNow(RESTART_EXIT_CODE);
  430. break;
  431. case SIGTERM:
  432. #ifdef _WIN32
  433. case SIGBREAK:
  434. #endif
  435. World::StopNow(SHUTDOWN_EXIT_CODE);
  436. break;
  437. }
  438. signal(s, _OnSignal);
  439. }
  440. // Define hook '_OnSignal' for all termination signals
  441. void Master::_HookSignals()
  442. {
  443. signal(SIGINT, _OnSignal);
  444. signal(SIGTERM, _OnSignal);
  445. #ifdef _WIN32
  446. signal(SIGBREAK, _OnSignal);
  447. #endif
  448. }
  449. // Unhook the signals before leaving
  450. void Master::_UnhookSignals()
  451. {
  452. signal(SIGINT, 0);
  453. signal(SIGTERM, 0);
  454. #ifdef _WIN32
  455. signal(SIGBREAK, 0);
  456. #endif
  457. }