PageRenderTime 73ms CodeModel.GetById 10ms app.highlight 56ms RepoModel.GetById 1ms app.codeStats 1ms

/src/server/worldserver/Main.cpp

https://gitlab.com/tkrokli/TrinityCore_434
C++ | 564 lines | 389 code | 103 blank | 72 comment | 51 complexity | a075b664d0d0995bec1fa363f7a199a7 MD5 | raw file
  1/*
  2 * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/>
  3 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
  4 *
  5 * This program is free software; you can redistribute it and/or modify it
  6 * under the terms of the GNU General Public License as published by the
  7 * Free Software Foundation; either version 2 of the License, or (at your
  8 * option) any later version.
  9 *
 10 * This program is distributed in the hope that it will be useful, but WITHOUT
 11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 13 * more details.
 14 *
 15 * You should have received a copy of the GNU General Public License along
 16 * with this program. If not, see <http://www.gnu.org/licenses/>.
 17 */
 18
 19/// \addtogroup Trinityd Trinity Daemon
 20/// @{
 21/// \file
 22
 23#include "Common.h"
 24#include "Commands.h"
 25#include "ZmqContext.h"
 26#include "DatabaseEnv.h"
 27#include "AsyncAcceptor.h"
 28#include "RASession.h"
 29#include "Configuration/Config.h"
 30#include "OpenSSLCrypto.h"
 31#include "ProcessPriority.h"
 32#include "BigNumber.h"
 33#include "RealmList.h"
 34#include "World.h"
 35#include "MapManager.h"
 36#include "InstanceSaveMgr.h"
 37#include "ObjectAccessor.h"
 38#include "ScriptMgr.h"
 39#include "OutdoorPvP/OutdoorPvPMgr.h"
 40#include "BattlegroundMgr.h"
 41#include "TCSoap.h"
 42#include "CliRunnable.h"
 43#include "GitRevision.h"
 44#include "WorldSocket.h"
 45#include "WorldSocketMgr.h"
 46#include "BattlenetServerManager.h"
 47#include "DatabaseLoader.h"
 48#include "AppenderDB.h"
 49#include <openssl/opensslv.h>
 50#include <openssl/crypto.h>
 51#include <boost/asio/io_service.hpp>
 52#include <boost/asio/deadline_timer.hpp>
 53#include <boost/program_options.hpp>
 54
 55using namespace boost::program_options;
 56
 57#ifndef _TRINITY_CORE_CONFIG
 58    #define _TRINITY_CORE_CONFIG  "worldserver.conf"
 59#endif
 60
 61#define WORLD_SLEEP_CONST 50
 62
 63#ifdef _WIN32
 64#include "ServiceWin32.h"
 65char serviceName[] = "worldserver";
 66char serviceLongName[] = "TrinityCore world service";
 67char serviceDescription[] = "TrinityCore World of Warcraft emulator world service";
 68/*
 69 * -1 - not in service mode
 70 *  0 - stopped
 71 *  1 - running
 72 *  2 - paused
 73 */
 74int m_ServiceStatus = -1;
 75#endif
 76
 77boost::asio::io_service _ioService;
 78boost::asio::deadline_timer _freezeCheckTimer(_ioService);
 79uint32 _worldLoopCounter(0);
 80uint32 _lastChangeMsTime(0);
 81uint32 _maxCoreStuckTimeInMs(0);
 82
 83WorldDatabaseWorkerPool WorldDatabase;                      ///< Accessor to the world database
 84CharacterDatabaseWorkerPool CharacterDatabase;              ///< Accessor to the character database
 85LoginDatabaseWorkerPool LoginDatabase;                      ///< Accessor to the realm/login database
 86Battlenet::RealmHandle realmHandle;                         ///< Id of the realm
 87
 88void SignalHandler(const boost::system::error_code& error, int signalNumber);
 89void FreezeDetectorHandler(const boost::system::error_code& error);
 90AsyncAcceptor* StartRaSocketAcceptor(boost::asio::io_service& ioService);
 91bool StartDB();
 92void StopDB();
 93void WorldUpdateLoop();
 94void ClearOnlineAccounts();
 95void ShutdownCLIThread(std::thread* cliThread);
 96void ShutdownThreadPool(std::vector<std::thread>& threadPool);
 97variables_map GetConsoleArguments(int argc, char** argv, std::string& cfg_file, std::string& cfg_service);
 98
 99/// Launch the Trinity server
100extern int main(int argc, char** argv)
101{
102    std::string configFile = _TRINITY_CORE_CONFIG;
103    std::string configService;
104
105    auto vm = GetConsoleArguments(argc, argv, configFile, configService);
106    // exit if help or version is enabled
107    if (vm.count("help") || vm.count("version"))
108        return 0;
109
110#ifdef _WIN32
111    if (configService.compare("install") == 0)
112        return WinServiceInstall() == true ? 0 : 1;
113    else if (configService.compare("uninstall") == 0)
114        return WinServiceUninstall() == true ? 0 : 1;
115    else if (configService.compare("run") == 0)
116        WinServiceRun();
117#endif
118
119    std::string configError;
120    if (!sConfigMgr->LoadInitial(configFile, configError))
121    {
122        printf("Error in config file: %s\n", configError.c_str());
123        return 1;
124    }
125
126    sLog->RegisterAppender<AppenderDB>();
127    // If logs are supposed to be handled async then we need to pass the io_service into the Log singleton
128    sLog->Initialize(sConfigMgr->GetBoolDefault("Log.Async.Enable", false) ? &_ioService : nullptr);
129
130    TC_LOG_INFO("server.worldserver", "%s (worldserver-daemon)", GitRevision::GetFullVersion());
131    TC_LOG_INFO("server.worldserver", "<Ctrl-C> to stop.\n");
132    TC_LOG_INFO("server.worldserver", " ______                       __");
133    TC_LOG_INFO("server.worldserver", "/\\__  _\\       __          __/\\ \\__");
134    TC_LOG_INFO("server.worldserver", "\\/_/\\ \\/ _ __ /\\_\\    ___ /\\_\\ \\, _\\  __  __");
135    TC_LOG_INFO("server.worldserver", "   \\ \\ \\/\\`'__\\/\\ \\ /' _ `\\/\\ \\ \\ \\/ /\\ \\/\\ \\");
136    TC_LOG_INFO("server.worldserver", "    \\ \\ \\ \\ \\/ \\ \\ \\/\\ \\/\\ \\ \\ \\ \\ \\_\\ \\ \\_\\ \\");
137    TC_LOG_INFO("server.worldserver", "     \\ \\_\\ \\_\\  \\ \\_\\ \\_\\ \\_\\ \\_\\ \\__\\\\/`____ \\");
138    TC_LOG_INFO("server.worldserver", "      \\/_/\\/_/   \\/_/\\/_/\\/_/\\/_/\\/__/ `/___/> \\");
139    TC_LOG_INFO("server.worldserver", "                                 C O R E  /\\___/");
140    TC_LOG_INFO("server.worldserver", "http://TrinityCore.org                    \\/__/\n");
141    TC_LOG_INFO("server.worldserver", "Using configuration file %s.", configFile.c_str());
142    TC_LOG_INFO("server.worldserver", "Using SSL version: %s (library: %s)", OPENSSL_VERSION_TEXT, SSLeay_version(SSLEAY_VERSION));
143    TC_LOG_INFO("server.worldserver", "Using Boost version: %i.%i.%i", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100);
144
145    OpenSSLCrypto::threadsSetup();
146
147    // Seed the OpenSSL's PRNG here.
148    // That way it won't auto-seed when calling BigNumber::SetRand and slow down the first world login
149    BigNumber seed;
150    seed.SetRand(16 * 8);
151
152    /// worldserver PID file creation
153    std::string pidFile = sConfigMgr->GetStringDefault("PidFile", "");
154    if (!pidFile.empty())
155    {
156        if (uint32 pid = CreatePIDFile(pidFile))
157            TC_LOG_INFO("server.worldserver", "Daemon PID: %u\n", pid);
158        else
159        {
160            TC_LOG_ERROR("server.worldserver", "Cannot create PID file %s.\n", pidFile.c_str());
161            return 1;
162        }
163    }
164
165    // Set signal handlers (this must be done before starting io_service threads, because otherwise they would unblock and exit)
166    boost::asio::signal_set signals(_ioService, SIGINT, SIGTERM);
167#if PLATFORM == PLATFORM_WINDOWS
168    signals.add(SIGBREAK);
169#endif
170    signals.async_wait(SignalHandler);
171
172    // Start the Boost based thread pool
173    int numThreads = sConfigMgr->GetIntDefault("ThreadPool", 1);
174    std::vector<std::thread> threadPool;
175
176    if (numThreads < 1)
177        numThreads = 1;
178
179    for (int i = 0; i < numThreads; ++i)
180        threadPool.push_back(std::thread(boost::bind(&boost::asio::io_service::run, &_ioService)));
181
182    // Set process priority according to configuration settings
183    SetProcessPriority("server.worldserver");
184
185    // Start the databases
186    if (!StartDB())
187    {
188        ShutdownThreadPool(threadPool);
189        return 1;
190    }
191
192    // Set server offline (not connectable)
193    LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = (flag & ~%u) | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, REALM_FLAG_INVALID, realmHandle.Index);
194
195    // Initialize the World
196    sWorld->SetInitialWorldSettings();
197
198    // Launch CliRunnable thread
199    std::thread* cliThread = nullptr;
200#ifdef _WIN32
201    if (sConfigMgr->GetBoolDefault("Console.Enable", true) && (m_ServiceStatus == -1)/* need disable console in service mode*/)
202#else
203    if (sConfigMgr->GetBoolDefault("Console.Enable", true))
204#endif
205    {
206        cliThread = new std::thread(CliThread);
207    }
208
209    // Start the Remote Access port (acceptor) if enabled
210    AsyncAcceptor* raAcceptor = nullptr;
211    if (sConfigMgr->GetBoolDefault("Ra.Enable", false))
212        raAcceptor = StartRaSocketAcceptor(_ioService);
213
214    // Start soap serving thread if enabled
215    std::thread* soapThread = nullptr;
216    if (sConfigMgr->GetBoolDefault("SOAP.Enabled", false))
217    {
218        soapThread = new std::thread(TCSoapThread, sConfigMgr->GetStringDefault("SOAP.IP", "127.0.0.1"), uint16(sConfigMgr->GetIntDefault("SOAP.Port", 7878)));
219    }
220
221    // Launch the worldserver listener socket
222    uint16 worldPort = uint16(sWorld->getIntConfig(CONFIG_PORT_WORLD));
223    std::string worldListener = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0");
224
225    sWorldSocketMgr.StartNetwork(_ioService, worldListener, worldPort);
226
227    // Set server online (allow connecting now)
228    LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag & ~%u, population = 0 WHERE id = '%u'", REALM_FLAG_INVALID, realmHandle.Index);
229
230    // Start the freeze check callback cycle in 5 seconds (cycle itself is 1 sec)
231    if (int coreStuckTime = sConfigMgr->GetIntDefault("MaxCoreStuckTime", 0))
232    {
233        _maxCoreStuckTimeInMs = coreStuckTime * 1000;
234        _freezeCheckTimer.expires_from_now(boost::posix_time::seconds(5));
235        _freezeCheckTimer.async_wait(FreezeDetectorHandler);
236        TC_LOG_INFO("server.worldserver", "Starting up anti-freeze thread (%u seconds max stuck time)...", coreStuckTime);
237    }
238
239    sIpcContext->Initialize();
240    TC_LOG_INFO("server.worldserver", "%s (worldserver-daemon) ready...", GitRevision::GetFullVersion());
241
242    sBattlenetServer.InitializeConnection();
243
244
245    sScriptMgr->OnStartup();
246
247    WorldUpdateLoop();
248
249    // Shutdown starts here
250    ShutdownThreadPool(threadPool);
251
252    sScriptMgr->OnShutdown();
253
254    sIpcContext->Close();
255
256    sBattlenetServer.CloseConnection();
257
258    sWorld->KickAll();                                       // save and kick all players
259    sWorld->UpdateSessions(1);                             // real players unload required UpdateSessions call
260
261    // unload battleground templates before different singletons destroyed
262    sBattlegroundMgr->DeleteAllBattlegrounds();
263
264    sWorldSocketMgr.StopNetwork();
265
266    sInstanceSaveMgr->Unload();
267    sMapMgr->UnloadAll();                     // unload all grids (including locked in memory)
268    sObjectAccessor->UnloadAll();             // unload 'i_player2corpse' storage and remove from world
269    sScriptMgr->Unload();
270    sOutdoorPvPMgr->Die();
271
272    // set server offline
273    LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, realmHandle.Index);
274
275    // Clean up threads if any
276    if (soapThread != nullptr)
277    {
278        soapThread->join();
279        delete soapThread;
280    }
281
282    delete raAcceptor;
283
284    ///- Clean database before leaving
285    ClearOnlineAccounts();
286
287    StopDB();
288
289    TC_LOG_INFO("server.worldserver", "Halting process...");
290
291    ShutdownCLIThread(cliThread);
292
293    OpenSSLCrypto::threadsCleanup();
294
295    // 0 - normal shutdown
296    // 1 - shutdown at error
297    // 2 - restart command used, this code can be used by restarter for restart Trinityd
298
299    return World::GetExitCode();
300}
301
302void ShutdownCLIThread(std::thread* cliThread)
303{
304    if (cliThread != nullptr)
305    {
306#ifdef _WIN32
307        // First try to cancel any I/O in the CLI thread
308        if (!CancelSynchronousIo(cliThread->native_handle()))
309        {
310            // if CancelSynchronousIo() fails, print the error and try with old way
311            DWORD errorCode = GetLastError();
312            LPSTR errorBuffer;
313
314            DWORD formatReturnCode = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
315                                                   nullptr, errorCode, 0, (LPTSTR)&errorBuffer, 0, nullptr);
316            if (!formatReturnCode)
317                errorBuffer = "Unknown error";
318
319            TC_LOG_DEBUG("server.worldserver", "Error cancelling I/O of CliThread, error code %u, detail: %s",
320                uint32(errorCode), errorBuffer);
321            LocalFree(errorBuffer);
322
323            // send keyboard input to safely unblock the CLI thread
324            INPUT_RECORD b[4];
325            HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
326            b[0].EventType = KEY_EVENT;
327            b[0].Event.KeyEvent.bKeyDown = TRUE;
328            b[0].Event.KeyEvent.uChar.AsciiChar = 'X';
329            b[0].Event.KeyEvent.wVirtualKeyCode = 'X';
330            b[0].Event.KeyEvent.wRepeatCount = 1;
331
332            b[1].EventType = KEY_EVENT;
333            b[1].Event.KeyEvent.bKeyDown = FALSE;
334            b[1].Event.KeyEvent.uChar.AsciiChar = 'X';
335            b[1].Event.KeyEvent.wVirtualKeyCode = 'X';
336            b[1].Event.KeyEvent.wRepeatCount = 1;
337
338            b[2].EventType = KEY_EVENT;
339            b[2].Event.KeyEvent.bKeyDown = TRUE;
340            b[2].Event.KeyEvent.dwControlKeyState = 0;
341            b[2].Event.KeyEvent.uChar.AsciiChar = '\r';
342            b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
343            b[2].Event.KeyEvent.wRepeatCount = 1;
344            b[2].Event.KeyEvent.wVirtualScanCode = 0x1c;
345
346            b[3].EventType = KEY_EVENT;
347            b[3].Event.KeyEvent.bKeyDown = FALSE;
348            b[3].Event.KeyEvent.dwControlKeyState = 0;
349            b[3].Event.KeyEvent.uChar.AsciiChar = '\r';
350            b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
351            b[3].Event.KeyEvent.wVirtualScanCode = 0x1c;
352            b[3].Event.KeyEvent.wRepeatCount = 1;
353            DWORD numb;
354            WriteConsoleInput(hStdIn, b, 4, &numb);
355        }
356#endif
357        cliThread->join();
358        delete cliThread;
359    }
360}
361
362void ShutdownThreadPool(std::vector<std::thread>& threadPool)
363{
364    sScriptMgr->OnNetworkStop();
365
366    _ioService.stop();
367
368    for (auto& thread : threadPool)
369    {
370        thread.join();
371    }
372}
373
374void WorldUpdateLoop()
375{
376    uint32 realCurrTime = 0;
377    uint32 realPrevTime = getMSTime();
378
379    uint32 prevSleepTime = 0;                               // used for balanced full tick time length near WORLD_SLEEP_CONST
380
381    ///- While we have not World::m_stopEvent, update the world
382    while (!World::IsStopped())
383    {
384        ++World::m_worldLoopCounter;
385        realCurrTime = getMSTime();
386
387        uint32 diff = getMSTimeDiff(realPrevTime, realCurrTime);
388
389        sWorld->Update(diff);
390        realPrevTime = realCurrTime;
391
392        // diff (D0) include time of previous sleep (d0) + tick time (t0)
393        // we want that next d1 + t1 == WORLD_SLEEP_CONST
394        // we can't know next t1 and then can use (t0 + d1) == WORLD_SLEEP_CONST requirement
395        // d1 = WORLD_SLEEP_CONST - t0 = WORLD_SLEEP_CONST - (D0 - d0) = WORLD_SLEEP_CONST + d0 - D0
396        if (diff <= WORLD_SLEEP_CONST + prevSleepTime)
397        {
398            prevSleepTime = WORLD_SLEEP_CONST + prevSleepTime - diff;
399
400            std::this_thread::sleep_for(std::chrono::milliseconds(prevSleepTime));
401        }
402        else
403            prevSleepTime = 0;
404
405#ifdef _WIN32
406        if (m_ServiceStatus == 0)
407            World::StopNow(SHUTDOWN_EXIT_CODE);
408
409        while (m_ServiceStatus == 2)
410            Sleep(1000);
411#endif
412    }
413}
414
415void SignalHandler(const boost::system::error_code& error, int /*signalNumber*/)
416{
417    if (!error)
418        World::StopNow(SHUTDOWN_EXIT_CODE);
419}
420
421void FreezeDetectorHandler(const boost::system::error_code& error)
422{
423    if (!error)
424    {
425        uint32 curtime = getMSTime();
426
427        uint32 worldLoopCounter = World::m_worldLoopCounter;
428        if (_worldLoopCounter != worldLoopCounter)
429        {
430            _lastChangeMsTime = curtime;
431            _worldLoopCounter = worldLoopCounter;
432        }
433        // possible freeze
434        else if (getMSTimeDiff(_lastChangeMsTime, curtime) > _maxCoreStuckTimeInMs)
435        {
436            TC_LOG_ERROR("server.worldserver", "World Thread hangs, kicking out server!");
437            ASSERT(false);
438        }
439
440        _freezeCheckTimer.expires_from_now(boost::posix_time::seconds(1));
441        _freezeCheckTimer.async_wait(FreezeDetectorHandler);
442    }
443}
444
445AsyncAcceptor* StartRaSocketAcceptor(boost::asio::io_service& ioService)
446{
447    uint16 raPort = uint16(sConfigMgr->GetIntDefault("Ra.Port", 3443));
448    std::string raListener = sConfigMgr->GetStringDefault("Ra.IP", "0.0.0.0");
449
450    AsyncAcceptor* acceptor = new AsyncAcceptor(ioService, raListener, raPort);
451    acceptor->AsyncAccept<RASession>();
452    return acceptor;
453}
454
455/// Initialize connection to the databases
456bool StartDB()
457{
458    MySQL::Library_Init();
459
460    // Load databases
461    DatabaseLoader loader("server.worldserver", DatabaseLoader::DATABASE_NONE);
462    loader
463        .AddDatabase(WorldDatabase, "World")
464        .AddDatabase(CharacterDatabase, "Character")
465        .AddDatabase(LoginDatabase, "Login");
466
467    if (!loader.Load())
468        return false;
469
470    ///- Get the realm Id from the configuration file
471    realmHandle.Index = sConfigMgr->GetIntDefault("RealmID", 0);
472    if (!realmHandle.Index)
473    {
474        TC_LOG_ERROR("server.worldserver", "Realm ID not defined in configuration file");
475        return false;
476    }
477
478    QueryResult realmIdQuery = LoginDatabase.PQuery("SELECT `Region`,`Battlegroup` FROM `realmlist` WHERE `id`=%u", realmHandle.Index);
479    if (!realmIdQuery)
480    {
481        TC_LOG_ERROR("server.worldserver", "Realm id %u not defined in realmlist table", realmHandle.Index);
482        return false;
483    }
484
485    realmHandle.Region = (*realmIdQuery)[0].GetUInt8();
486    realmHandle.Battlegroup = (*realmIdQuery)[1].GetUInt8();
487
488    TC_LOG_INFO("server.worldserver", "Realm running as realm ID %u region %u battlegroup %u", realmHandle.Index, uint32(realmHandle.Region), uint32(realmHandle.Battlegroup));
489
490    ///- Clean the database before starting
491    ClearOnlineAccounts();
492
493    ///- Insert version info into DB
494    WorldDatabase.PExecute("UPDATE version SET core_version = '%s', core_revision = '%s'", GitRevision::GetFullVersion(), GitRevision::GetHash());        // One-time query
495
496    sWorld->LoadDBVersion();
497
498    TC_LOG_INFO("server.worldserver", "Using World DB: %s", sWorld->GetDBVersion());
499    return true;
500}
501
502void StopDB()
503{
504    CharacterDatabase.Close();
505    WorldDatabase.Close();
506    LoginDatabase.Close();
507
508    MySQL::Library_End();
509}
510
511/// Clear 'online' status for all accounts with characters in this realm
512void ClearOnlineAccounts()
513{
514    // Reset online status for all accounts with characters on the current realm
515    LoginDatabase.DirectPExecute("UPDATE account SET online = 0 WHERE online > 0 AND id IN (SELECT acctid FROM realmcharacters WHERE realmid = %d)", realmHandle.Index);
516
517    // Reset online status for all characters
518    CharacterDatabase.DirectExecute("UPDATE characters SET online = 0 WHERE online <> 0");
519
520    // Battleground instance ids reset at server restart
521    CharacterDatabase.DirectExecute("UPDATE character_battleground_data SET instanceId = 0");
522}
523
524/// @}
525
526variables_map GetConsoleArguments(int argc, char** argv, std::string& configFile, std::string& configService)
527{
528    // Silences warning about configService not be used if the OS is not Windows
529    (void)configService;
530
531    options_description all("Allowed options");
532    all.add_options()
533        ("help,h", "print usage message")
534        ("version,v", "print version build info")
535        ("config,c", value<std::string>(&configFile)->default_value(_TRINITY_CORE_CONFIG), "use <arg> as configuration file")
536        ;
537#ifdef _WIN32
538    options_description win("Windows platform specific options");
539    win.add_options()
540        ("service,s", value<std::string>(&configService)->default_value(""), "Windows service options: [install | uninstall]")
541        ;
542
543    all.add(win);
544#endif
545    variables_map vm;
546    try
547    {
548        store(command_line_parser(argc, argv).options(all).allow_unregistered().run(), vm);
549        notify(vm);
550    }
551    catch (std::exception& e) {
552        std::cerr << e.what() << "\n";
553    }
554
555    if (vm.count("help")) {
556        std::cout << all << "\n";
557    }
558    else if (vm.count("version"))
559    {
560        std::cout << GitRevision::GetFullVersion() << "\n";
561    }
562
563    return vm;
564}