mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-16 07:30:42 +01:00
Docs at https://trinitycore.atlassian.net/wiki/display/tc/Monitoring+a+TrinityCore+server * Common/Graphs: Initial proof of concept * Move influx db code to its own class * Reuse the same socket * Allow to log values of different categories * Allow to log events * Pass the timestamp to influxdb * Send events in batches * Send data async * Log server shutdown. Fix memory leak. * Allow to enable/disable Stats in the settings and at runtime * Read interval between each batch send from config * Add InfluxDB connection info to configs * Move each event category to its own table * Log pathfinding queries * Move categories table initialization to constructor using enum as key to avoid assigning the table name to the wrong enum value * Log player login/logout events. Pass the hostname correctly in the HTTP request. * Fix linux build * Handle "Connection: close" HTTP header, reconnecting on next scheduled send. Disable StatsLogger if connection fails, logging the error. * Add an enum for categories of logged values, it's still possible to pass a string instead of the enum. * Don't log the whole batchedData when InfluxDB returns an error, it's too long and unreadable on console. * Allow to call a function at a specified interval in thread-safe World::Update() context to log data like player count. * Log map tile load/unload * Core/StatsLogger: Allow logging more value types other than ints https://docs.influxdata.com/influxdb/v0.10/write_protocols/write_syntax/ * Fix a typo in string escape of StatsLogger * Yet more fixes to the escaping in FormatInfluxDBValue * DB/Gameobject: Fix respawn time of few Quest GameObjects By Tauriella, closes #16701 * DB/Misc: Fix some engrish By tkrokli closes #16648 * Tools/MMaps: Add format library linking to mmaps_generator (Very) partial cherry pick ofed75b0649a* Core/StatsLogger: Simplify code Convert values and categories arrays to maps initialized in-place Remove constructor and destructor * Core/StatsLogger: Add realm name to the event and value tags * Log amount of processed packet of each session * Apply recent singleton changes to sStatsLogger too * Fix influxdb data format if no realm name is present * Remove unneeded newlines from request body, fixes response 400 from InfluxDB 0.10 * Rename Reporting folder to Metric * Rename StatsLogger to Metric * Rename InfluxDB configs to Metric * Add Grafana dashboards * Add a random annoying macro * Move string formatting to Metric::SendBatch(), reducing performance footprint of Metric::LogEvent() and Metric::LogValue() * Update grafana graphs refresing tags on load and showing now-15m data, refreshing every minute. These settings can be modified in grafana. * Rename MetricData fields * Contrib/Grafana: Rename dashboard files * Contrib/Grafana: Replace hardcoded Windows/Ubuntu realm names by the default, Trinity * Config/Worldserver: Add missing section to the index * Contrib/Grafana: Add singlestat panels with current online players, update diff averages (1 min, 5 mins and 15 mins) http://i.imgur.com/Zi8lfvS.png * Core/Metric: Replace the enums MetricEventCategory and MetricValueCategory by strings For the sake of simplicity and less recompile time when adding new metrics, similar to how TC_LOG_* works * Contrib/Grafana: Display the current number of online players and not its average Closes #15075 (cherry picked from commit3ae1016082) # Conflicts: # src/server/game/Server/WorldSession.cpp # src/server/game/World/World.cpp # src/server/worldserver/Main.cpp
641 lines
22 KiB
C++
641 lines
22 KiB
C++
/*
|
|
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
|
|
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/// \addtogroup Trinityd Trinity Daemon
|
|
/// @{
|
|
/// \file
|
|
|
|
#include "Common.h"
|
|
#include "DatabaseEnv.h"
|
|
#include "AsyncAcceptor.h"
|
|
#include "RASession.h"
|
|
#include "Configuration/Config.h"
|
|
#include "OpenSSLCrypto.h"
|
|
#include "ProcessPriority.h"
|
|
#include "BigNumber.h"
|
|
#include "World.h"
|
|
#include "MapManager.h"
|
|
#include "InstanceSaveMgr.h"
|
|
#include "ObjectAccessor.h"
|
|
#include "ScriptMgr.h"
|
|
#include "ScriptReloadMgr.h"
|
|
#include "ScriptLoader.h"
|
|
#include "OutdoorPvP/OutdoorPvPMgr.h"
|
|
#include "BattlegroundMgr.h"
|
|
#include "TCSoap.h"
|
|
#include "CliRunnable.h"
|
|
#include "GitRevision.h"
|
|
#include "WorldSocket.h"
|
|
#include "WorldSocketMgr.h"
|
|
#include "RealmList.h"
|
|
#include "DatabaseLoader.h"
|
|
#include "AppenderDB.h"
|
|
#include "Metric.h"
|
|
#include <openssl/opensslv.h>
|
|
#include <openssl/crypto.h>
|
|
#include <boost/asio/io_service.hpp>
|
|
#include <boost/asio/deadline_timer.hpp>
|
|
#include <boost/filesystem/path.hpp>
|
|
#include <boost/program_options.hpp>
|
|
#include <google/protobuf/stubs/common.h>
|
|
|
|
using namespace boost::program_options;
|
|
namespace fs = boost::filesystem;
|
|
|
|
#ifndef _TRINITY_CORE_CONFIG
|
|
#define _TRINITY_CORE_CONFIG "worldserver.conf"
|
|
#endif
|
|
|
|
#define WORLD_SLEEP_CONST 50
|
|
|
|
#ifdef _WIN32
|
|
#include "ServiceWin32.h"
|
|
char serviceName[] = "worldserver";
|
|
char serviceLongName[] = "TrinityCore world service";
|
|
char serviceDescription[] = "TrinityCore World of Warcraft emulator world service";
|
|
/*
|
|
* -1 - not in service mode
|
|
* 0 - stopped
|
|
* 1 - running
|
|
* 2 - paused
|
|
*/
|
|
int m_ServiceStatus = -1;
|
|
#endif
|
|
|
|
boost::asio::io_service _ioService;
|
|
boost::asio::deadline_timer _freezeCheckTimer(_ioService);
|
|
uint32 _worldLoopCounter(0);
|
|
uint32 _lastChangeMsTime(0);
|
|
uint32 _maxCoreStuckTimeInMs(0);
|
|
|
|
void SignalHandler(const boost::system::error_code& error, int signalNumber);
|
|
void FreezeDetectorHandler(const boost::system::error_code& error);
|
|
AsyncAcceptor* StartRaSocketAcceptor(boost::asio::io_service& ioService);
|
|
bool StartDB();
|
|
void StopDB();
|
|
void WorldUpdateLoop();
|
|
void ClearOnlineAccounts();
|
|
void ShutdownCLIThread(std::thread* cliThread);
|
|
void ShutdownThreadPool(std::vector<std::thread>& threadPool);
|
|
bool LoadRealmInfo();
|
|
variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, std::string& cfg_service);
|
|
|
|
/// Launch the Trinity server
|
|
extern int main(int argc, char** argv)
|
|
{
|
|
signal(SIGABRT, &Trinity::AbortHandler);
|
|
|
|
auto configFile = fs::absolute(_TRINITY_CORE_CONFIG);
|
|
std::string configService;
|
|
|
|
auto vm = GetConsoleArguments(argc, argv, configFile, configService);
|
|
// exit if help or version is enabled
|
|
if (vm.count("help") || vm.count("version"))
|
|
return 0;
|
|
|
|
GOOGLE_PROTOBUF_VERIFY_VERSION;
|
|
|
|
#ifdef _WIN32
|
|
if (configService.compare("install") == 0)
|
|
return WinServiceInstall() ? 0 : 1;
|
|
else if (configService.compare("uninstall") == 0)
|
|
return WinServiceUninstall() ? 0 : 1;
|
|
else if (configService.compare("run") == 0)
|
|
return WinServiceRun() ? 0 : 0;
|
|
#endif
|
|
|
|
std::string configError;
|
|
if (!sConfigMgr->LoadInitial(configFile.generic_string(),
|
|
std::vector<std::string>(argv, argv + argc),
|
|
configError))
|
|
{
|
|
printf("Error in config file: %s\n", configError.c_str());
|
|
return 1;
|
|
}
|
|
|
|
sLog->RegisterAppender<AppenderDB>();
|
|
// If logs are supposed to be handled async then we need to pass the io_service into the Log singleton
|
|
sLog->Initialize(sConfigMgr->GetBoolDefault("Log.Async.Enable", false) ? &_ioService : nullptr);
|
|
|
|
TC_LOG_INFO("server.worldserver", "%s (worldserver-daemon)", GitRevision::GetFullVersion());
|
|
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.", sConfigMgr->GetFilename().c_str());
|
|
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: %i.%i.%i", BOOST_VERSION / 100000, BOOST_VERSION / 100 % 1000, BOOST_VERSION % 100);
|
|
|
|
OpenSSLCrypto::threadsSetup();
|
|
|
|
// Seed the OpenSSL's PRNG here.
|
|
// That way it won't auto-seed when calling BigNumber::SetRand and slow down the first world login
|
|
BigNumber seed;
|
|
seed.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);
|
|
#if PLATFORM == PLATFORM_WINDOWS
|
|
signals.add(SIGBREAK);
|
|
#endif
|
|
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())
|
|
{
|
|
ShutdownThreadPool(threadPool);
|
|
return 1;
|
|
}
|
|
|
|
// Set server offline (not connectable)
|
|
LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, realm.Id.Realm);
|
|
|
|
sRealmList->Initialize(_ioService, sConfigMgr->GetIntDefault("RealmsStateUpdateDelay", 10));
|
|
|
|
LoadRealmInfo();
|
|
|
|
sMetric->Initialize(realm.Name, _ioService, []()
|
|
{
|
|
TC_METRIC_VALUE("online_players", sWorld->GetPlayerCount());
|
|
});
|
|
|
|
TC_METRIC_EVENT("events", "Worldserver started", "");
|
|
|
|
// Initialize the World
|
|
sScriptMgr->SetScriptLoader(AddScripts);
|
|
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* 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)));
|
|
}
|
|
|
|
// Launch the worldserver listener socket
|
|
uint16 worldPort = uint16(sWorld->getIntConfig(CONFIG_PORT_WORLD));
|
|
std::string worldListener = sConfigMgr->GetStringDefault("BindIP", "0.0.0.0");
|
|
|
|
int networkThreads = sConfigMgr->GetIntDefault("Network.Threads", 1);
|
|
|
|
if (networkThreads <= 0)
|
|
{
|
|
TC_LOG_ERROR("server.worldserver", "Network.Threads must be greater than 0");
|
|
return false;
|
|
}
|
|
|
|
sWorldSocketMgr.StartNetwork(_ioService, worldListener, worldPort, networkThreads);
|
|
|
|
// Set server online (allow connecting now)
|
|
LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag & ~%u, population = 0 WHERE id = '%u'", REALM_FLAG_OFFLINE, realm.Id.Realm);
|
|
realm.PopulationLevel = 0.0f;
|
|
realm.Flags = RealmFlags(realm.Flags & ~uint32(REALM_FLAG_OFFLINE));
|
|
|
|
// Start the freeze check callback cycle in 5 seconds (cycle itself is 1 sec)
|
|
if (int coreStuckTime = sConfigMgr->GetIntDefault("MaxCoreStuckTime", 0))
|
|
{
|
|
_maxCoreStuckTimeInMs = coreStuckTime * 1000;
|
|
_freezeCheckTimer.expires_from_now(boost::posix_time::seconds(5));
|
|
_freezeCheckTimer.async_wait(FreezeDetectorHandler);
|
|
TC_LOG_INFO("server.worldserver", "Starting up anti-freeze thread (%u seconds max stuck time)...", coreStuckTime);
|
|
}
|
|
|
|
TC_LOG_INFO("server.worldserver", "%s (worldserver-daemon) ready...", GitRevision::GetFullVersion());
|
|
|
|
sScriptMgr->OnStartup();
|
|
|
|
WorldUpdateLoop();
|
|
|
|
// Shutdown starts here
|
|
ShutdownThreadPool(threadPool);
|
|
|
|
sLog->SetSynchronous();
|
|
|
|
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();
|
|
|
|
sInstanceSaveMgr->Unload();
|
|
sOutdoorPvPMgr->Die(); // unload it before MapManager
|
|
sMapMgr->UnloadAll(); // unload all grids (including locked in memory)
|
|
sScriptMgr->Unload();
|
|
sScriptReloadMgr->Unload();
|
|
|
|
// set server offline
|
|
LoginDatabase.DirectPExecute("UPDATE realmlist SET flag = flag | %u WHERE id = '%d'", REALM_FLAG_OFFLINE, realm.Id.Realm);
|
|
sRealmList->Close();
|
|
|
|
// Clean up threads if any
|
|
if (soapThread != nullptr)
|
|
{
|
|
soapThread->join();
|
|
delete soapThread;
|
|
}
|
|
|
|
delete raAcceptor;
|
|
|
|
///- Clean database before leaving
|
|
ClearOnlineAccounts();
|
|
|
|
StopDB();
|
|
|
|
TC_METRIC_EVENT("events", "Worldserver shutdown", "");
|
|
sMetric->ForceSend();
|
|
|
|
TC_LOG_INFO("server.worldserver", "Halting process...");
|
|
|
|
ShutdownCLIThread(cliThread);
|
|
|
|
OpenSSLCrypto::threadsCleanup();
|
|
|
|
google::protobuf::ShutdownProtobufLibrary();
|
|
|
|
// 0 - normal shutdown
|
|
// 1 - shutdown at error
|
|
// 2 - restart command used, this code can be used by restarter for restart Trinityd
|
|
|
|
return World::GetExitCode();
|
|
}
|
|
|
|
void ShutdownCLIThread(std::thread* cliThread)
|
|
{
|
|
if (cliThread != nullptr)
|
|
{
|
|
#ifdef _WIN32
|
|
// First try to cancel any I/O in the CLI thread
|
|
if (!CancelSynchronousIo(cliThread->native_handle()))
|
|
{
|
|
// if CancelSynchronousIo() fails, print the error and try with old way
|
|
DWORD errorCode = GetLastError();
|
|
LPSTR errorBuffer;
|
|
|
|
DWORD formatReturnCode = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
nullptr, errorCode, 0, (LPTSTR)&errorBuffer, 0, nullptr);
|
|
if (!formatReturnCode)
|
|
errorBuffer = "Unknown error";
|
|
|
|
TC_LOG_DEBUG("server.worldserver", "Error cancelling I/O of CliThread, error code %u, detail: %s",
|
|
uint32(errorCode), errorBuffer);
|
|
LocalFree(errorBuffer);
|
|
|
|
// 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);
|
|
}
|
|
#endif
|
|
cliThread->join();
|
|
delete cliThread;
|
|
}
|
|
}
|
|
|
|
void ShutdownThreadPool(std::vector<std::thread>& threadPool)
|
|
{
|
|
sScriptMgr->OnNetworkStop();
|
|
|
|
_ioService.stop();
|
|
|
|
for (auto& thread : threadPool)
|
|
{
|
|
thread.join();
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
void SignalHandler(const boost::system::error_code& error, int /*signalNumber*/)
|
|
{
|
|
if (!error)
|
|
World::StopNow(SHUTDOWN_EXIT_CODE);
|
|
}
|
|
|
|
void FreezeDetectorHandler(const boost::system::error_code& error)
|
|
{
|
|
if (!error)
|
|
{
|
|
uint32 curtime = getMSTime();
|
|
|
|
uint32 worldLoopCounter = World::m_worldLoopCounter;
|
|
if (_worldLoopCounter != worldLoopCounter)
|
|
{
|
|
_lastChangeMsTime = curtime;
|
|
_worldLoopCounter = worldLoopCounter;
|
|
}
|
|
// possible freeze
|
|
else if (getMSTimeDiff(_lastChangeMsTime, curtime) > _maxCoreStuckTimeInMs)
|
|
{
|
|
TC_LOG_ERROR("server.worldserver", "World Thread hangs, kicking out server!");
|
|
ABORT();
|
|
}
|
|
|
|
_freezeCheckTimer.expires_from_now(boost::posix_time::seconds(1));
|
|
_freezeCheckTimer.async_wait(FreezeDetectorHandler);
|
|
}
|
|
}
|
|
|
|
AsyncAcceptor* 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");
|
|
|
|
AsyncAcceptor* acceptor = new AsyncAcceptor(ioService, raListener, raPort);
|
|
acceptor->AsyncAccept<RASession>();
|
|
return acceptor;
|
|
}
|
|
|
|
bool LoadRealmInfo()
|
|
{
|
|
boost::asio::ip::tcp::resolver resolver(_ioService);
|
|
boost::asio::ip::tcp::resolver::iterator end;
|
|
|
|
QueryResult result = LoginDatabase.PQuery("SELECT id, name, address, localAddress, localSubnetMask, port, icon, flag, timezone, allowedSecurityLevel, population, gamebuild, Region, Battlegroup FROM realmlist WHERE id = %u", realm.Id.Realm);
|
|
if (!result)
|
|
return false;
|
|
|
|
Field* fields = result->Fetch();
|
|
realm.Name = fields[1].GetString();
|
|
boost::asio::ip::tcp::resolver::query externalAddressQuery(ip::tcp::v4(), fields[2].GetString(), "");
|
|
|
|
boost::system::error_code ec;
|
|
boost::asio::ip::tcp::resolver::iterator endPoint = resolver.resolve(externalAddressQuery, ec);
|
|
if (endPoint == end || ec)
|
|
{
|
|
TC_LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[2].GetString().c_str());
|
|
return false;
|
|
}
|
|
|
|
realm.ExternalAddress = (*endPoint).endpoint().address();
|
|
|
|
boost::asio::ip::tcp::resolver::query localAddressQuery(ip::tcp::v4(), fields[3].GetString(), "");
|
|
endPoint = resolver.resolve(localAddressQuery, ec);
|
|
if (endPoint == end || ec)
|
|
{
|
|
TC_LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[3].GetString().c_str());
|
|
return false;
|
|
}
|
|
|
|
realm.LocalAddress = (*endPoint).endpoint().address();
|
|
|
|
boost::asio::ip::tcp::resolver::query localSubmaskQuery(ip::tcp::v4(), fields[4].GetString(), "");
|
|
endPoint = resolver.resolve(localSubmaskQuery, ec);
|
|
if (endPoint == end || ec)
|
|
{
|
|
TC_LOG_ERROR("server.worldserver", "Could not resolve address %s", fields[4].GetString().c_str());
|
|
return false;
|
|
}
|
|
|
|
realm.LocalSubnetMask = (*endPoint).endpoint().address();
|
|
|
|
realm.Port = fields[5].GetUInt16();
|
|
realm.Type = fields[6].GetUInt8();
|
|
realm.Flags = RealmFlags(fields[7].GetUInt8());
|
|
realm.Timezone = fields[8].GetUInt8();
|
|
realm.AllowedSecurityLevel = AccountTypes(fields[9].GetUInt8());
|
|
realm.PopulationLevel = fields[10].GetFloat();
|
|
realm.Id.Region = fields[12].GetUInt8();
|
|
realm.Id.Site = fields[13].GetUInt8();
|
|
realm.Build = fields[11].GetUInt32();
|
|
return true;
|
|
}
|
|
|
|
/// Initialize connection to the databases
|
|
bool StartDB()
|
|
{
|
|
MySQL::Library_Init();
|
|
|
|
// Load databases
|
|
DatabaseLoader loader("server.worldserver", DatabaseLoader::DATABASE_NONE);
|
|
loader
|
|
.AddDatabase(LoginDatabase, "Login")
|
|
.AddDatabase(CharacterDatabase, "Character")
|
|
.AddDatabase(WorldDatabase, "World")
|
|
.AddDatabase(HotfixDatabase, "Hotfix");
|
|
|
|
if (!loader.Load())
|
|
return false;
|
|
|
|
///- Get the realm Id from the configuration file
|
|
realm.Id.Realm = sConfigMgr->GetIntDefault("RealmID", 0);
|
|
if (!realm.Id.Realm)
|
|
{
|
|
TC_LOG_ERROR("server.worldserver", "Realm ID not defined in configuration file");
|
|
return false;
|
|
}
|
|
|
|
TC_LOG_INFO("server.worldserver", "Realm running as realm ID %u", realm.Id.Realm);
|
|
|
|
///- Clean the database before starting
|
|
ClearOnlineAccounts();
|
|
|
|
///- Insert version info into DB
|
|
WorldDatabase.PExecute("UPDATE version SET core_version = '%s', core_revision = '%s'", GitRevision::GetFullVersion(), GitRevision::GetHash()); // 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)", realm.Id.Realm);
|
|
|
|
// 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");
|
|
}
|
|
|
|
/// @}
|
|
|
|
variables_map GetConsoleArguments(int argc, char** argv, fs::path& configFile, std::string& configService)
|
|
{
|
|
// Silences warning about configService not be used if the OS is not Windows
|
|
(void)configService;
|
|
|
|
options_description all("Allowed options");
|
|
all.add_options()
|
|
("help,h", "print usage message")
|
|
("version,v", "print version build info")
|
|
("config,c", value<fs::path>(&configFile)->default_value(fs::absolute(_TRINITY_CORE_CONFIG)),
|
|
"use <arg> as configuration file")
|
|
;
|
|
#ifdef _WIN32
|
|
options_description win("Windows platform specific options");
|
|
win.add_options()
|
|
("service,s", value<std::string>(&configService)->default_value(""), "Windows service options: [install | uninstall]")
|
|
;
|
|
|
|
all.add(win);
|
|
#endif
|
|
variables_map vm;
|
|
try
|
|
{
|
|
store(command_line_parser(argc, argv).options(all).allow_unregistered().run(), vm);
|
|
notify(vm);
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
std::cerr << e.what() << "\n";
|
|
}
|
|
|
|
if (vm.count("help"))
|
|
{
|
|
std::cout << all << "\n";
|
|
}
|
|
else if (vm.count("version"))
|
|
{
|
|
std::cout << GitRevision::GetFullVersion() << "\n";
|
|
}
|
|
|
|
return vm;
|
|
}
|