aboutsummaryrefslogtreecommitdiff
path: root/src/server/worldserver/Main.cpp
diff options
context:
space:
mode:
authorleak <leak@bitmx.net>2014-07-04 15:22:06 +0200
committerleak <leak@bitmx.net>2014-07-04 15:22:06 +0200
commit021e18d152b68b9d2a0c5887bab7eb7b61ecd2ca (patch)
treed70e9680e7b4e6ced6a39939b47709c00d650548 /src/server/worldserver/Main.cpp
parent4f2f9e08f80f46149dbbe8e5ca469267f39ae438 (diff)
Refactored both world and auth main
- Master/Worldrunable removed - World Update loop now running on main (which was doing nothing before) - Processpriority moved to shared - Added a preliminary thread pool for boost::asio::io_service
Diffstat (limited to 'src/server/worldserver/Main.cpp')
-rw-r--r--src/server/worldserver/Main.cpp487
1 files changed, 458 insertions, 29 deletions
diff --git a/src/server/worldserver/Main.cpp b/src/server/worldserver/Main.cpp
index bcc058c7fb3..0fc2c1a7221 100644
--- a/src/server/worldserver/Main.cpp
+++ b/src/server/worldserver/Main.cpp
@@ -22,17 +22,30 @@
#include <openssl/opensslv.h>
#include <openssl/crypto.h>
+#include <boost/asio/io_service.hpp>
#include "Common.h"
-#include "Database/DatabaseEnv.h"
+#include "DatabaseEnv.h"
+#include "AsyncAcceptor.h"
+#include "RASession.h"
#include "Configuration/Config.h"
+#include "OpenSSLCrypto.h"
+#include "ProcessPriority.h"
+#include "BigNumber.h"
+#include "RealmList.h"
+#include "World.h"
+#include "MapManager.h"
+#include "ObjectAccessor.h"
+#include "ScriptMgr.h"
+#include "WorldSocketMgr.h"
+#include "OutdoorPvP/OutdoorPvPMgr.h"
+#include "BattlegroundMgr.h"
+#include "TCSoap.h"
+#include "CliRunnable.h"
+#include "SystemConfig.h"
-#include "Log.h"
-#include "Master.h"
-
-#ifndef _TRINITY_CORE_CONFIG
-# define _TRINITY_CORE_CONFIG "worldserver.conf"
-#endif
+#define TRINITY_CORE_CONFIG "worldserver.conf"
+#define WORLD_SLEEP_CONST 50
#ifdef _WIN32
#include "ServiceWin32.h"
@@ -48,31 +61,26 @@ char serviceDescription[] = "TrinityCore World of Warcraft emulator world servic
int m_ServiceStatus = -1;
#endif
+boost::asio::io_service _ioService;
WorldDatabaseWorkerPool WorldDatabase; ///< Accessor to the world database
CharacterDatabaseWorkerPool CharacterDatabase; ///< Accessor to the character database
LoginDatabaseWorkerPool LoginDatabase; ///< Accessor to the realm/login database
-
uint32 realmID; ///< Id of the realm
-/// Print out the usage string for this program on the console.
-void usage(const char* prog)
-{
- printf("Usage:\n");
- printf(" %s [<options>]\n", prog);
- printf(" -c config_file use config_file as configuration file\n");
-#ifdef _WIN32
- printf(" Running as service functions:\n");
- printf(" --service run as service\n");
- printf(" -s install install service\n");
- printf(" -s uninstall uninstall service\n");
-#endif
-}
+void usage(const char* prog);
+void SignalHandler(const boost::system::error_code& error, int signalNumber);
+void FreezeDetectorThread(uint32 delayTime);
+AsyncAcceptor<RASession>* StartRaSocketAcceptor(boost::asio::io_service& ioService);
+bool StartDB();
+void StopDB();
+void WorldUpdateLoop();
+void ClearOnlineAccounts();
/// Launch the Trinity server
extern int main(int argc, char** argv)
{
///- Command line parsing to get the configuration file name
- char const* cfg_file = _TRINITY_CORE_CONFIG;
+ char const* cfg_file = TRINITY_CORE_CONFIG;
int c = 1;
while (c < argc)
{
@@ -131,21 +139,442 @@ extern int main(int argc, char** argv)
return 1;
}
+ TC_LOG_INFO("server.worldserver", "%s (worldserver-daemon)", _FULLVERSION);
+ TC_LOG_INFO("server.worldserver", "<Ctrl-C> to stop.\n");
+ TC_LOG_INFO("server.worldserver", " ______ __");
+ TC_LOG_INFO("server.worldserver", "/\\__ _\\ __ __/\\ \\__");
+ TC_LOG_INFO("server.worldserver", "\\/_/\\ \\/ _ __ /\\_\\ ___ /\\_\\ \\, _\\ __ __");
+ TC_LOG_INFO("server.worldserver", " \\ \\ \\/\\`'__\\/\\ \\ /' _ `\\/\\ \\ \\ \\/ /\\ \\/\\ \\");
+ TC_LOG_INFO("server.worldserver", " \\ \\ \\ \\ \\/ \\ \\ \\/\\ \\/\\ \\ \\ \\ \\ \\_\\ \\ \\_\\ \\");
+ TC_LOG_INFO("server.worldserver", " \\ \\_\\ \\_\\ \\ \\_\\ \\_\\ \\_\\ \\_\\ \\__\\\\/`____ \\");
+ TC_LOG_INFO("server.worldserver", " \\/_/\\/_/ \\/_/\\/_/\\/_/\\/_/\\/__/ `/___/> \\");
+ TC_LOG_INFO("server.worldserver", " C O R E /\\___/");
+ TC_LOG_INFO("server.worldserver", "http://TrinityCore.org \\/__/\n");
TC_LOG_INFO("server.worldserver", "Using configuration file %s.", cfg_file);
-
TC_LOG_INFO("server.worldserver", "Using SSL version: %s (library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION));
- TC_LOG_INFO("server.worldserver", "Using Boost version: %s", BOOST_LIB_VERSION);
+ TC_LOG_INFO("server.worldserver", "Using Boost version: %i.%i.%i", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100);
+
+ OpenSSLCrypto::threadsSetup();
+ BigNumber seed1;
+ seed1.SetRand(16 * 8);
+
+ /// worldserver PID file creation
+ std::string pidFile = sConfigMgr->GetStringDefault("PidFile", "");
+ if (!pidFile.empty())
+ {
+ if (uint32 pid = CreatePIDFile(pidFile))
+ TC_LOG_INFO("server.worldserver", "Daemon PID: %u\n", pid);
+ else
+ {
+ TC_LOG_ERROR("server.worldserver", "Cannot create PID file %s.\n", pidFile.c_str());
+ return 1;
+ }
+ }
+
+ // Set signal handlers (this must be done before starting io_service threads, because otherwise they would unblock and exit)
+ boost::asio::signal_set signals(_ioService, SIGINT, SIGTERM);
+ signals.async_wait(SignalHandler);
+
+ // Start the Boost based thread pool
+ int numThreads = sConfigMgr->GetIntDefault("ThreadPool", 1);
+ std::vector<std::thread> threadPool;
+
+ if (numThreads < 1)
+ numThreads = 1;
+
+ for (int i = 0; i < numThreads; ++i)
+ threadPool.push_back(std::thread(boost::bind(&boost::asio::io_service::run, &_ioService)));
+
+ // Set process priority according to configuration settings
+ SetProcessPriority("server.worldserver");
+
+ // Start the databases
+ if (!StartDB())
+ return 1;
+
+ // Set server offline (not connectable)
+ LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = (flag & ~%u) | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, REALM_FLAG_INVALID, realmID);
+
+ // Initialize the World
+ sWorld->SetInitialWorldSettings();
+
+ // Launch CliRunnable thread
+ std::thread* cliThread = nullptr;
+#ifdef _WIN32
+ if (sConfigMgr->GetBoolDefault("Console.Enable", true) && (m_ServiceStatus == -1)/* need disable console in service mode*/)
+#else
+ if (sConfigMgr->GetBoolDefault("Console.Enable", true))
+#endif
+ {
+ cliThread = new std::thread(CliThread);
+ }
+
+ // Start the Remote Access port (acceptor) if enabled
+ AsyncAcceptor<RASession>* raAcceptor = nullptr;
+ if (sConfigMgr->GetBoolDefault("Ra.Enable", false))
+ raAcceptor = StartRaSocketAcceptor(_ioService);
+
+ // Start soap serving thread if enabled
+ std::thread* soapThread = nullptr;
+ if (sConfigMgr->GetBoolDefault("SOAP.Enabled", false))
+ {
+ soapThread = new std::thread(TCSoapThread, sConfigMgr->GetStringDefault("SOAP.IP", "127.0.0.1"), uint16(sConfigMgr->GetIntDefault("SOAP.Port", 7878)));
+ }
+
+ // Start up freeze catcher thread
+ std::thread* freezeDetectorThread = nullptr;
+ if (uint32 freezeDelay = sConfigMgr->GetIntDefault("MaxCoreStuckTime", 0))
+ freezeDetectorThread = new std::thread(FreezeDetectorThread, freezeDelay);
+
+ // Launch the worldserver listener socket
+ uint16 worldPort = uint16(sWorld->getIntConfig(CONFIG_PORT_WORLD));
+ std::string bindIp = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0");
+
+ if (sWorldSocketMgr->StartNetwork(worldPort, bindIp.c_str()) == -1)
+ {
+ TC_LOG_ERROR("server.worldserver", "Failed to start network");
+ return ERROR_EXIT_CODE;
+ }
- ///- and run the 'Master'
- /// @todo Why do we need this 'Master'? Can't all of this be in the Main as for Realmd?
- int ret = sMaster->Run();
+ // Set server online (allow connecting now)
+ LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag & ~%u, population = 0 WHERE id = '%u'", REALM_FLAG_INVALID, realmID);
+
+ TC_LOG_INFO("server.worldserver", "%s (worldserver-daemon) ready...", _FULLVERSION);
+
+ sScriptMgr->OnStartup();
+
+ WorldUpdateLoop();
+
+ // Shutdown starts here
+
+ _ioService.stop();
+
+ for (auto& thread : threadPool)
+ {
+ thread.join();
+ }
+
+ sScriptMgr->OnShutdown();
+
+ sWorld->KickAll(); // save and kick all players
+ sWorld->UpdateSessions(1); // real players unload required UpdateSessions call
+
+ // unload battleground templates before different singletons destroyed
+ sBattlegroundMgr->DeleteAllBattlegrounds();
+
+ sWorldSocketMgr->StopNetwork();
+
+ sMapMgr->UnloadAll(); // unload all grids (including locked in memory)
+ sObjectAccessor->UnloadAll(); // unload 'i_player2corpse' storage and remove from world
+ sScriptMgr->Unload();
+ sOutdoorPvPMgr->Die();
+
+ // set server offline
+ LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, realmID);
+
+ // Clean up threads if any
+ if (soapThread != nullptr)
+ {
+ soapThread->join();
+ delete soapThread;
+ }
+
+ if (raAcceptor != nullptr)
+ delete raAcceptor;
+
+ ///- Clean database before leaving
+ ClearOnlineAccounts();
+
+ StopDB();
+
+ TC_LOG_INFO("server.worldserver", "Halting process...");
+
+ if (cliThread != nullptr)
+ {
+#ifdef _WIN32
+
+ // this only way to terminate CLI thread exist at Win32 (alt. way exist only in Windows Vista API)
+ //_exit(1);
+ // send keyboard input to safely unblock the CLI thread
+ INPUT_RECORD b[4];
+ HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
+ b[0].EventType = KEY_EVENT;
+ b[0].Event.KeyEvent.bKeyDown = TRUE;
+ b[0].Event.KeyEvent.uChar.AsciiChar = 'X';
+ b[0].Event.KeyEvent.wVirtualKeyCode = 'X';
+ b[0].Event.KeyEvent.wRepeatCount = 1;
+
+ b[1].EventType = KEY_EVENT;
+ b[1].Event.KeyEvent.bKeyDown = FALSE;
+ b[1].Event.KeyEvent.uChar.AsciiChar = 'X';
+ b[1].Event.KeyEvent.wVirtualKeyCode = 'X';
+ b[1].Event.KeyEvent.wRepeatCount = 1;
+
+ b[2].EventType = KEY_EVENT;
+ b[2].Event.KeyEvent.bKeyDown = TRUE;
+ b[2].Event.KeyEvent.dwControlKeyState = 0;
+ b[2].Event.KeyEvent.uChar.AsciiChar = '\r';
+ b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
+ b[2].Event.KeyEvent.wRepeatCount = 1;
+ b[2].Event.KeyEvent.wVirtualScanCode = 0x1c;
+
+ b[3].EventType = KEY_EVENT;
+ b[3].Event.KeyEvent.bKeyDown = FALSE;
+ b[3].Event.KeyEvent.dwControlKeyState = 0;
+ b[3].Event.KeyEvent.uChar.AsciiChar = '\r';
+ b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
+ b[3].Event.KeyEvent.wVirtualScanCode = 0x1c;
+ b[3].Event.KeyEvent.wRepeatCount = 1;
+ DWORD numb;
+ WriteConsoleInput(hStdIn, b, 4, &numb);
+
+ cliThread->join();
+
+#endif
+
+ delete cliThread;
+ }
+
+ delete freezeDetectorThread;
+
+ OpenSSLCrypto::threadsCleanup();
- // at sMaster return function exist with codes
// 0 - normal shutdown
// 1 - shutdown at error
// 2 - restart command used, this code can be used by restarter for restart Trinityd
- return ret;
+ return World::GetExitCode();
+}
+
+
+void WorldUpdateLoop()
+{
+ uint32 realCurrTime = 0;
+ uint32 realPrevTime = getMSTime();
+
+ uint32 prevSleepTime = 0; // used for balanced full tick time length near WORLD_SLEEP_CONST
+
+ ///- While we have not World::m_stopEvent, update the world
+ while (!World::IsStopped())
+ {
+ ++World::m_worldLoopCounter;
+ realCurrTime = getMSTime();
+
+ uint32 diff = getMSTimeDiff(realPrevTime, realCurrTime);
+
+ sWorld->Update(diff);
+ realPrevTime = realCurrTime;
+
+ // diff (D0) include time of previous sleep (d0) + tick time (t0)
+ // we want that next d1 + t1 == WORLD_SLEEP_CONST
+ // we can't know next t1 and then can use (t0 + d1) == WORLD_SLEEP_CONST requirement
+ // d1 = WORLD_SLEEP_CONST - t0 = WORLD_SLEEP_CONST - (D0 - d0) = WORLD_SLEEP_CONST + d0 - D0
+ if (diff <= WORLD_SLEEP_CONST + prevSleepTime)
+ {
+ prevSleepTime = WORLD_SLEEP_CONST + prevSleepTime - diff;
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(prevSleepTime));
+ }
+ else
+ prevSleepTime = 0;
+
+#ifdef _WIN32
+ if (m_ServiceStatus == 0)
+ World::StopNow(SHUTDOWN_EXIT_CODE);
+
+ while (m_ServiceStatus == 2)
+ Sleep(1000);
+#endif
+ }
+}
+
+/// Print out the usage string for this program on the console.
+void usage(const char* prog)
+{
+ printf("Usage:\n");
+ printf(" %s [<options>]\n", prog);
+ printf(" -c config_file use config_file as configuration file\n");
+#ifdef _WIN32
+ printf(" Running as service functions:\n");
+ printf(" --service run as service\n");
+ printf(" -s install install service\n");
+ printf(" -s uninstall uninstall service\n");
+#endif
+}
+
+void SignalHandler(const boost::system::error_code& error, int signalNumber)
+{
+ if (!error)
+ {
+ switch (signalNumber)
+ {
+ case SIGINT:
+ case SIGTERM:
+ World::StopNow(SHUTDOWN_EXIT_CODE);
+ break;
+ }
+ }
+}
+
+void FreezeDetectorThread(uint32 delayTime)
+{
+ if (!delayTime)
+ return;
+
+ TC_LOG_INFO("server.worldserver", "Starting up anti-freeze thread (%u seconds max stuck time)...", delayTime / 1000);
+ uint32 loops = 0;
+ uint32 lastChange = 0;
+
+ while (!World::IsStopped())
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+ uint32 curtime = getMSTime();
+ // normal work
+ uint32 worldLoopCounter = World::m_worldLoopCounter;
+ if (loops != worldLoopCounter)
+ {
+ lastChange = curtime;
+ loops = worldLoopCounter;
+ }
+ // possible freeze
+ else if (getMSTimeDiff(lastChange, curtime) > delayTime)
+ {
+ TC_LOG_ERROR("server.worldserver", "World Thread hangs, kicking out server!");
+ ASSERT(false);
+ }
+ }
+ TC_LOG_INFO("server.worldserver", "Anti-freeze thread exiting without problems.");
+}
+
+AsyncAcceptor<RASession>* StartRaSocketAcceptor(boost::asio::io_service& ioService)
+{
+ uint16 raPort = uint16(sConfigMgr->GetIntDefault("Ra.Port", 3443));
+ std::string raListener = sConfigMgr->GetStringDefault("Ra.IP", "0.0.0.0");
+
+ return new AsyncAcceptor<RASession>(ioService, raListener, raPort);
+}
+
+/// Initialize connection to the databases
+bool StartDB()
+{
+ MySQL::Library_Init();
+
+ std::string dbString;
+ uint8 asyncThreads, synchThreads;
+
+ dbString = sConfigMgr->GetStringDefault("WorldDatabaseInfo", "");
+ if (dbString.empty())
+ {
+ TC_LOG_ERROR("server.worldserver", "World database not specified in configuration file");
+ return false;
+ }
+
+ asyncThreads = uint8(sConfigMgr->GetIntDefault("WorldDatabase.WorkerThreads", 1));
+ if (asyncThreads < 1 || asyncThreads > 32)
+ {
+ TC_LOG_ERROR("server.worldserver", "World database: invalid number of worker threads specified. "
+ "Please pick a value between 1 and 32.");
+ return false;
+ }
+
+ synchThreads = uint8(sConfigMgr->GetIntDefault("WorldDatabase.SynchThreads", 1));
+ ///- Initialize the world database
+ if (!WorldDatabase.Open(dbString, asyncThreads, synchThreads))
+ {
+ TC_LOG_ERROR("server.worldserver", "Cannot connect to world database %s", dbString.c_str());
+ return false;
+ }
+
+ ///- Get character database info from configuration file
+ dbString = sConfigMgr->GetStringDefault("CharacterDatabaseInfo", "");
+ if (dbString.empty())
+ {
+ TC_LOG_ERROR("server.worldserver", "Character database not specified in configuration file");
+ return false;
+ }
+
+ asyncThreads = uint8(sConfigMgr->GetIntDefault("CharacterDatabase.WorkerThreads", 1));
+ if (asyncThreads < 1 || asyncThreads > 32)
+ {
+ TC_LOG_ERROR("server.worldserver", "Character database: invalid number of worker threads specified. "
+ "Please pick a value between 1 and 32.");
+ return false;
+ }
+
+ synchThreads = uint8(sConfigMgr->GetIntDefault("CharacterDatabase.SynchThreads", 2));
+
+ ///- Initialize the Character database
+ if (!CharacterDatabase.Open(dbString, asyncThreads, synchThreads))
+ {
+ TC_LOG_ERROR("server.worldserver", "Cannot connect to Character database %s", dbString.c_str());
+ return false;
+ }
+
+ ///- Get login database info from configuration file
+ dbString = sConfigMgr->GetStringDefault("LoginDatabaseInfo", "");
+ if (dbString.empty())
+ {
+ TC_LOG_ERROR("server.worldserver", "Login database not specified in configuration file");
+ return false;
+ }
+
+ asyncThreads = uint8(sConfigMgr->GetIntDefault("LoginDatabase.WorkerThreads", 1));
+ if (asyncThreads < 1 || asyncThreads > 32)
+ {
+ TC_LOG_ERROR("server.worldserver", "Login database: invalid number of worker threads specified. "
+ "Please pick a value between 1 and 32.");
+ return false;
+ }
+
+ synchThreads = uint8(sConfigMgr->GetIntDefault("LoginDatabase.SynchThreads", 1));
+ ///- Initialise the login database
+ if (!LoginDatabase.Open(dbString, asyncThreads, synchThreads))
+ {
+ TC_LOG_ERROR("server.worldserver", "Cannot connect to login database %s", dbString.c_str());
+ return false;
+ }
+
+ ///- Get the realm Id from the configuration file
+ realmID = sConfigMgr->GetIntDefault("RealmID", 0);
+ if (!realmID)
+ {
+ TC_LOG_ERROR("server.worldserver", "Realm ID not defined in configuration file");
+ return false;
+ }
+ TC_LOG_INFO("server.worldserver", "Realm running as realm ID %d", realmID);
+
+ ///- Clean the database before starting
+ ClearOnlineAccounts();
+
+ ///- Insert version info into DB
+ WorldDatabase.PExecute("UPDATE version SET core_version = '%s', core_revision = '%s'", _FULLVERSION, _HASH); // One-time query
+
+ sWorld->LoadDBVersion();
+
+ TC_LOG_INFO("server.worldserver", "Using World DB: %s", sWorld->GetDBVersion());
+ return true;
+}
+
+void StopDB()
+{
+ CharacterDatabase.Close();
+ WorldDatabase.Close();
+ LoginDatabase.Close();
+
+ MySQL::Library_End();
+}
+
+/// Clear 'online' status for all accounts with characters in this realm
+void ClearOnlineAccounts()
+{
+ // Reset online status for all accounts with characters on the current realm
+ LoginDatabase.DirectPExecute("UPDATE account SET online = 0 WHERE online > 0 AND id IN (SELECT acctid FROM realmcharacters WHERE realmid = %d)", realmID);
+
+ // Reset online status for all characters
+ CharacterDatabase.DirectExecute("UPDATE characters SET online = 0 WHERE online <> 0");
+
+ // Battleground instance ids reset at server restart
+ CharacterDatabase.DirectExecute("UPDATE character_battleground_data SET instanceId = 0");
}
/// @}