1
0

Fixed and improved restarting

Restarts are now an actual, close-as-possible to application
exit+reopen.
This commit is contained in:
Tiger Wang 2015-06-17 15:38:00 +01:00
parent b5ed23d2a6
commit 4315a11393
7 changed files with 253 additions and 211 deletions

View File

@ -17,6 +17,7 @@ echo "Building..."
make -j 2;
make -j 2 test ARGS="-V";
cd MCServer/;
if [ "$TRAVIS_MCSERVER_BUILD_TYPE" != "COVERAGE" ]
then echo stop | $MCSERVER_PATH;
if [ "$TRAVIS_MCSERVER_BUILD_TYPE" != "COVERAGE" ]; then
echo restart | $MCSERVER_PATH;
echo stop | $MCSERVER_PATH;
fi

View File

@ -18,8 +18,36 @@
cNetworkSingleton::cNetworkSingleton(void):
m_HasTerminated(false)
cNetworkSingleton::cNetworkSingleton() :
m_HasTerminated(true)
{
}
cNetworkSingleton::~cNetworkSingleton()
{
// Check that Terminate has been called already:
ASSERT(m_HasTerminated);
}
cNetworkSingleton & cNetworkSingleton::Get(void)
{
static cNetworkSingleton Instance;
return Instance;
}
void cNetworkSingleton::Initialise(void)
{
// Windows: initialize networking:
#ifdef _WIN32
@ -64,26 +92,7 @@ cNetworkSingleton::cNetworkSingleton(void):
// Create the event loop thread:
m_EventLoopThread = std::thread(RunEventLoop, this);
}
cNetworkSingleton::~cNetworkSingleton()
{
// Check that Terminate has been called already:
ASSERT(m_HasTerminated);
}
cNetworkSingleton & cNetworkSingleton::Get(void)
{
static cNetworkSingleton Instance;
return Instance;
m_HasTerminated = false;
}

View File

@ -44,13 +44,18 @@ typedef std::vector<cIPLookupPtr> cIPLookupPtrs;
class cNetworkSingleton
{
public:
cNetworkSingleton();
~cNetworkSingleton();
/** Returns the singleton instance of this class */
static cNetworkSingleton & Get(void);
/** Initialises all network-related threads.
To be called on first run or after app restart. */
void Initialise(void);
/** Terminates all network-related threads.
To be used only on app shutdown.
To be used only on app shutdown or restart.
MSVC runtime requires that the LibEvent networking be shut down before the main() function is exitted; this is the way to do it. */
void Terminate(void);
@ -122,10 +127,6 @@ protected:
/** The thread in which the main LibEvent loop runs. */
std::thread m_EventLoopThread;
/** Initializes the LibEvent internals. */
cNetworkSingleton(void);
/** Converts LibEvent-generated log events into log messages in MCS log. */
static void LogCallback(int a_Severity, const char * a_Msg);

View File

@ -26,10 +26,10 @@
#include <iostream>
#ifdef _WIN32
#include <conio.h>
#include <psapi.h>
#elif defined(__linux__)
#include <fstream>
#include <signal.h>
#elif defined(__APPLE__)
#include <mach/mach.h>
#endif
@ -39,7 +39,6 @@
cRoot * cRoot::s_Root = nullptr;
bool cRoot::m_ShouldStop = false;
@ -53,10 +52,10 @@ cRoot::cRoot(void) :
m_FurnaceRecipe(nullptr),
m_WebAdmin(nullptr),
m_PluginManager(nullptr),
m_MojangAPI(nullptr),
m_bRestart(false)
m_MojangAPI(nullptr)
{
s_Root = this;
m_InputThreadRunFlag.clear();
}
@ -76,24 +75,22 @@ void cRoot::InputThread(cRoot & a_Params)
{
cLogCommandOutputCallback Output;
while (!cRoot::m_ShouldStop && !a_Params.m_bRestart && !m_TerminateEventRaised && std::cin.good())
while (a_Params.m_InputThreadRunFlag.test_and_set() && std::cin.good())
{
AString Command;
std::getline(std::cin, Command);
if (!Command.empty())
{
// Execute and clear command string when submitted
a_Params.ExecuteConsoleCommand(TrimString(Command), Output);
}
}
if (m_TerminateEventRaised || !std::cin.good())
// We have come here because the std::cin has received an EOF / a terminate signal has been sent, and the server is still running
if (!std::cin.good())
{
// We have come here because the std::cin has received an EOF / a terminate signal has been sent, and the server is still running
// Stop the server:
if (!m_RunAsService) // Dont kill if running as a service
{
a_Params.m_ShouldStop = true;
}
a_Params.QueueExecuteConsoleCommand("stop");
}
}
@ -101,12 +98,12 @@ void cRoot::InputThread(cRoot & a_Params)
void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> overridesRepo)
void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo)
{
#ifdef _WIN32
HWND hwnd = GetConsoleWindow();
HMENU hmenu = GetSystemMenu(hwnd, FALSE);
EnableMenuItem(hmenu, SC_CLOSE, MF_GRAYED); // Disable close button when starting up; it causes problems with our CTRL-CLOSE handling
HWND hwnd = GetConsoleWindow();
HMENU hmenu = GetSystemMenu(hwnd, FALSE);
EnableMenuItem(hmenu, SC_CLOSE, MF_GRAYED); // Disable close button when starting up; it causes problems with our CTRL-CLOSE handling
#endif
cLogger::cListener * consoleLogListener = MakeConsoleListener(m_RunAsService);
@ -127,156 +124,197 @@ void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> overridesRepo)
#endif
cDeadlockDetect dd;
auto BeginTime = std::chrono::steady_clock::now();
m_ShouldStop = false;
while (!m_ShouldStop)
LoadGlobalSettings();
LOG("Creating new server instance...");
m_Server = new cServer();
LOG("Reading server config...");
auto IniFile = cpp14::make_unique<cIniFile>();
if (!IniFile->ReadFile("settings.ini"))
{
auto BeginTime = std::chrono::steady_clock::now();
m_bRestart = false;
LoadGlobalSettings();
LOG("Creating new server instance...");
m_Server = new cServer();
LOG("Reading server config...");
auto IniFile = cpp14::make_unique<cIniFile>();
if (!IniFile->ReadFile("settings.ini"))
{
LOGWARN("Regenerating settings.ini, all settings will be reset");
IniFile->AddHeaderComment(" This is the main server configuration");
IniFile->AddHeaderComment(" Most of the settings here can be configured using the webadmin interface, if enabled in webadmin.ini");
IniFile->AddHeaderComment(" See: http://wiki.mc-server.org/doku.php?id=configure:settings.ini for further configuration help");
}
auto settingsRepo = cpp14::make_unique<cOverridesSettingsRepository>(std::move(IniFile), std::move(overridesRepo));
LOG("Starting server...");
m_MojangAPI = new cMojangAPI;
bool ShouldAuthenticate = settingsRepo->GetValueSetB("Authentication", "Authenticate", true);
m_MojangAPI->Start(*settingsRepo, ShouldAuthenticate); // Mojang API needs to be started before plugins, so that plugins may use it for DB upgrades on server init
if (!m_Server->InitServer(*settingsRepo, ShouldAuthenticate))
{
settingsRepo->Flush();
LOGERROR("Failure starting server, aborting...");
return;
}
m_WebAdmin = new cWebAdmin();
m_WebAdmin->Init();
LOGD("Loading settings...");
m_RankManager.reset(new cRankManager());
m_RankManager->Initialize(*m_MojangAPI);
m_CraftingRecipes = new cCraftingRecipes;
m_FurnaceRecipe = new cFurnaceRecipe();
LOGD("Loading worlds...");
LoadWorlds(*settingsRepo);
LOGD("Loading plugin manager...");
m_PluginManager = new cPluginManager();
m_PluginManager->ReloadPluginsNow(*settingsRepo);
LOGD("Loading MonsterConfig...");
m_MonsterConfig = new cMonsterConfig;
// This sets stuff in motion
LOGD("Starting Authenticator...");
m_Authenticator.Start(*settingsRepo);
LOGD("Starting worlds...");
StartWorlds();
if (settingsRepo->GetValueSetB("DeadlockDetect", "Enabled", true))
{
LOGD("Starting deadlock detector...");
dd.Start(settingsRepo->GetValueSetI("DeadlockDetect", "IntervalSec", 20));
}
LOGWARN("Regenerating settings.ini, all settings will be reset");
IniFile->AddHeaderComment(" This is the main server configuration");
IniFile->AddHeaderComment(" Most of the settings here can be configured using the webadmin interface, if enabled in webadmin.ini");
IniFile->AddHeaderComment(" See: http://wiki.mc-server.org/doku.php?id=configure:settings.ini for further configuration help");
}
auto settingsRepo = cpp14::make_unique<cOverridesSettingsRepository>(std::move(IniFile), std::move(a_OverridesRepo));
LOG("Starting server...");
m_MojangAPI = new cMojangAPI;
bool ShouldAuthenticate = settingsRepo->GetValueSetB("Authentication", "Authenticate", true);
m_MojangAPI->Start(*settingsRepo, ShouldAuthenticate); // Mojang API needs to be started before plugins, so that plugins may use it for DB upgrades on server init
if (!m_Server->InitServer(*settingsRepo, ShouldAuthenticate))
{
settingsRepo->Flush();
LOGERROR("Failure starting server, aborting...");
return;
}
LOGD("Finalising startup...");
if (m_Server->Start())
{
m_WebAdmin->Start();
m_WebAdmin = new cWebAdmin();
m_WebAdmin->Init();
#if !defined(ANDROID_NDK)
LOGD("Loading settings...");
m_RankManager.reset(new cRankManager());
m_RankManager->Initialize(*m_MojangAPI);
m_CraftingRecipes = new cCraftingRecipes();
m_FurnaceRecipe = new cFurnaceRecipe();
LOGD("Loading worlds...");
LoadWorlds(*settingsRepo);
LOGD("Loading plugin manager...");
m_PluginManager = new cPluginManager();
m_PluginManager->ReloadPluginsNow(*settingsRepo);
LOGD("Loading MonsterConfig...");
m_MonsterConfig = new cMonsterConfig;
// This sets stuff in motion
LOGD("Starting Authenticator...");
m_Authenticator.Start(*settingsRepo);
LOGD("Starting worlds...");
StartWorlds();
if (settingsRepo->GetValueSetB("DeadlockDetect", "Enabled", true))
{
LOGD("Starting deadlock detector...");
dd.Start(settingsRepo->GetValueSetI("DeadlockDetect", "IntervalSec", 20));
}
settingsRepo->Flush();
LOGD("Finalising startup...");
if (m_Server->Start())
{
m_WebAdmin->Start();
#if !defined(ANDROID_NDK)
LOGD("Starting InputThread...");
try
{
m_InputThreadRunFlag.test_and_set();
m_InputThread = std::thread(InputThread, std::ref(*this));
m_InputThread.detach();
}
catch (std::system_error & a_Exception)
{
LOGERROR("cRoot::Start (std::thread) error %i: could not construct input thread; %s", a_Exception.code().value(), a_Exception.what());
}
#endif
#endif
LOG("Startup complete, took %ldms!", static_cast<long int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count()));
LOG("Startup complete, took %ldms!", static_cast<long int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count()));
// Save the current time
m_StartTime = std::chrono::steady_clock::now();
// Save the current time
m_StartTime = std::chrono::steady_clock::now();
#ifdef _WIN32
#ifdef _WIN32
EnableMenuItem(hmenu, SC_CLOSE, MF_ENABLED); // Re-enable close button
#endif
#endif
while (!m_ShouldStop && !m_bRestart && !m_TerminateEventRaised) // These are modified by external threads
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (m_TerminateEventRaised)
{
m_ShouldStop = true;
}
// Stop the server:
m_WebAdmin->Stop();
LOG("Shutting down server...");
m_Server->Shutdown();
} // if (m_Server->Start())
else
for (;;)
{
m_ShouldStop = true;
m_StopEvent.Wait();
if (m_TerminateEventRaised && m_RunAsService)
{
// Dont kill if running as a service
m_TerminateEventRaised = false;
}
else
{
break;
}
}
delete m_MojangAPI; m_MojangAPI = nullptr;
// Stop the server:
m_WebAdmin->Stop();
LOGD("Shutting down deadlock detector...");
dd.Stop();
LOG("Shutting down server...");
m_Server->Shutdown();
} // if (m_Server->Start()
LOGD("Stopping world threads...");
StopWorlds();
delete m_MojangAPI; m_MojangAPI = nullptr;
LOGD("Stopping authenticator...");
m_Authenticator.Stop();
LOGD("Shutting down deadlock detector...");
dd.Stop();
LOGD("Freeing MonsterConfig...");
delete m_MonsterConfig; m_MonsterConfig = nullptr;
delete m_WebAdmin; m_WebAdmin = nullptr;
LOGD("Stopping world threads...");
StopWorlds();
LOGD("Unloading recipes...");
delete m_FurnaceRecipe; m_FurnaceRecipe = nullptr;
delete m_CraftingRecipes; m_CraftingRecipes = nullptr;
LOGD("Stopping authenticator...");
m_Authenticator.Stop();
LOG("Unloading worlds...");
UnloadWorlds();
LOGD("Freeing MonsterConfig...");
delete m_MonsterConfig; m_MonsterConfig = nullptr;
delete m_WebAdmin; m_WebAdmin = nullptr;
LOGD("Stopping plugin manager...");
delete m_PluginManager; m_PluginManager = nullptr;
LOGD("Unloading recipes...");
delete m_FurnaceRecipe; m_FurnaceRecipe = nullptr;
delete m_CraftingRecipes; m_CraftingRecipes = nullptr;
cItemHandler::Deinit();
LOG("Unloading worlds...");
UnloadWorlds();
LOG("Cleaning up...");
delete m_Server; m_Server = nullptr;
LOGD("Stopping plugin manager...");
delete m_PluginManager; m_PluginManager = nullptr;
cItemHandler::Deinit();
LOG("Cleaning up...");
delete m_Server; m_Server = nullptr;
m_InputThreadRunFlag.clear();
#ifdef _WIN32
DWORD Length;
INPUT_RECORD Record
{
static_cast<WORD>(KEY_EVENT),
{
{
TRUE,
1,
VK_RETURN,
MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC),
{ { VK_RETURN } },
0
}
}
};
// Can't kill the input thread since it breaks cin (getline doesn't block / receive input on restart)
// Apparently no way to unblock getline
// Only thing I can think of for now
if (WriteConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &Record, 1, &Length) == 0)
{
LOGWARN("Couldn't notify the input thread; the server will hang before shutdown!");
m_TerminateEventRaised = true;
m_InputThread.detach();
}
else
{
m_InputThread.join();
}
#else
if (pthread_kill(m_InputThread.native_handle(), SIGKILL) != 0)
{
LOGWARN("Couldn't notify the input thread; the server will hang before shutdown!");
m_TerminateEventRaised = true;
m_InputThread.detach();
}
#endif
if (m_TerminateEventRaised)
{
LOG("Shutdown successful!");
}
else
{
LOG("Shutdown successful - restarting...");
}
LOG("--- Stopped Log ---");
cLogger::GetInstance().DetachListener(consoleLogListener);
@ -475,19 +513,9 @@ void cRoot::TickCommands(void)
void cRoot::QueueExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallback & a_Output)
{
// Some commands are built-in:
if (a_Cmd == "stop")
{
m_ShouldStop = true;
}
else if (a_Cmd == "restart")
{
m_bRestart = true;
}
// Put the command into a queue (Alleviates FS #363):
cCSLock Lock(m_CSPendingCommands);
m_PendingCommands.push_back(cCommand(a_Cmd, &a_Output));
m_PendingCommands.emplace_back(a_Cmd, &a_Output);
}
@ -507,15 +535,18 @@ void cRoot::QueueExecuteConsoleCommand(const AString & a_Cmd)
void cRoot::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallback & a_Output)
{
// cRoot handles stopping and restarting due to our access to controlling variables
// Some commands are built-in:
if (a_Cmd == "stop")
{
m_ShouldStop = true;
m_TerminateEventRaised = true;
m_StopEvent.Set();
m_InputThreadRunFlag.clear();
return;
}
else if (a_Cmd == "restart")
{
m_bRestart = true;
m_StopEvent.Set();
m_InputThreadRunFlag.clear();
return;
}

View File

@ -7,6 +7,7 @@
#include "Defines.h"
#include "RankManager.h"
#include <thread>
#include <atomic>
@ -48,13 +49,12 @@ public:
static bool m_TerminateEventRaised;
static bool m_RunAsService;
static bool m_ShouldStop;
cRoot(void);
~cRoot();
void Start(std::unique_ptr<cSettingsRepositoryInterface> overridesRepo);
void Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo);
// tolua_begin
cServer * GetServer(void) { return m_Server; }
@ -200,6 +200,8 @@ private:
cCommandQueue m_PendingCommands;
std::thread m_InputThread;
cEvent m_StopEvent;
std::atomic_flag m_InputThreadRunFlag;
cServer * m_Server;
cMonsterConfig * m_MonsterConfig;
@ -213,9 +215,7 @@ private:
std::unique_ptr<cRankManager> m_RankManager;
cHTTPServer m_HTTPServer;
bool m_bRestart;
cHTTPServer m_HTTPServer;
void LoadGlobalSettings();

View File

@ -22,9 +22,6 @@
/** If something has told the server to stop; checked periodically in cRoot */
bool cRoot::m_TerminateEventRaised = false;
/** Set to true when the server terminates, so our CTRL handler can then tell the OS to close the console. */
static bool g_ServerTerminated = false;
/** If set to true, the protocols will log each player's incoming (C->S) communication to a per-connection logfile */
bool g_ShouldLogCommIn;
@ -72,7 +69,7 @@ Synchronize this with Server.cpp to enable the "dumpmem" console command. */
void NonCtrlHandler(int a_Signal)
{
LOGD("Terminate event raised from std::signal");
cRoot::m_TerminateEventRaised = true;
cRoot::Get()->QueueExecuteConsoleCommand("stop");
switch (a_Signal)
{
@ -189,13 +186,11 @@ LONG WINAPI LastChanceExceptionFilter(__in struct _EXCEPTION_POINTERS * a_Except
// Handle CTRL events in windows, including console window close
BOOL CtrlHandler(DWORD fdwCtrlType)
{
cRoot::m_TerminateEventRaised = true;
cRoot::Get()->QueueExecuteConsoleCommand("stop");
LOGD("Terminate event raised from the Windows CtrlHandler");
while (!g_ServerTerminated)
{
std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Delay as much as possible to try to get the server to shut down cleanly
}
std::this_thread::sleep_for(std::chrono::seconds(10)); // Delay as much as possible to try to get the server to shut down cleanly - 10 seconds given by Windows
// Returning from main() automatically aborts this handler thread
return TRUE;
}
@ -206,29 +201,22 @@ BOOL CtrlHandler(DWORD fdwCtrlType)
////////////////////////////////////////////////////////////////////////////////
// universalMain - Main startup logic for both standard running and as a service
// UniversalMain - Main startup logic for both standard running and as a service
void universalMain(std::unique_ptr<cSettingsRepositoryInterface> overridesRepo)
void UniversalMain(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo)
{
#ifdef _WIN32
if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE))
{
LOGERROR("Could not install the Windows CTRL handler!");
}
#endif
// Initialize logging subsystem:
cLogger::InitiateMultithreading();
// Initialize LibEvent:
cNetworkSingleton::Get();
cNetworkSingleton::Get().Initialise();
#if !defined(ANDROID_NDK)
try
#endif
{
cRoot Root;
Root.Start(std::move(overridesRepo));
Root.Start(std::move(a_OverridesRepo));
}
#if !defined(ANDROID_NDK)
catch (std::exception & e)
@ -241,8 +229,6 @@ void universalMain(std::unique_ptr<cSettingsRepositoryInterface> overridesRepo)
}
#endif
g_ServerTerminated = true;
// Shutdown all of LibEvent:
cNetworkSingleton::Get().Terminate();
}
@ -259,8 +245,11 @@ DWORD WINAPI serviceWorkerThread(LPVOID lpParam)
{
UNREFERENCED_PARAMETER(lpParam);
// Do the normal startup
universalMain(cpp14::make_unique<cMemorySettingsRepository>());
while (!cRoot::m_TerminateEventRaised)
{
// Do the normal startup
UniversalMain(cpp14::make_unique<cMemorySettingsRepository>());
}
return ERROR_SUCCESS;
}
@ -274,8 +263,7 @@ DWORD WINAPI serviceWorkerThread(LPVOID lpParam)
void serviceSetState(DWORD acceptedControls, DWORD newState, DWORD exitCode)
{
SERVICE_STATUS serviceStatus;
ZeroMemory(&serviceStatus, sizeof(SERVICE_STATUS));
SERVICE_STATUS serviceStatus = {};
serviceStatus.dwCheckPoint = 0;
serviceStatus.dwControlsAccepted = acceptedControls;
serviceStatus.dwCurrentState = newState;
@ -302,11 +290,10 @@ void WINAPI serviceCtrlHandler(DWORD CtrlCode)
{
case SERVICE_CONTROL_STOP:
{
cRoot::m_ShouldStop = true;
cRoot::Get()->QueueExecuteConsoleCommand("stop");
serviceSetState(0, SERVICE_STOP_PENDING, 0);
break;
}
default:
{
break;
@ -365,7 +352,7 @@ void WINAPI serviceMain(DWORD argc, TCHAR *argv[])
std::unique_ptr<cMemorySettingsRepository> parseArguments(int argc, char **argv)
std::unique_ptr<cMemorySettingsRepository> ParseArguments(int argc, char **argv)
{
try
{
@ -484,7 +471,13 @@ int main(int argc, char **argv)
#endif // SIGABRT_COMPAT
#endif
auto argsRepo = parseArguments(argc, argv);
#ifdef _WIN32
if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE))
{
LOGERROR("Could not install the Windows CTRL handler!");
}
#endif
// Attempt to run as a service
if (cRoot::m_RunAsService)
@ -522,13 +515,19 @@ int main(int argc, char **argv)
close(STDOUT_FILENO);
close(STDERR_FILENO);
universalMain(std::move(argsRepo));
while (!cRoot::m_TerminateEventRaised)
{
UniversalMain(std::move(ParseArguments(argc, argv)));
}
#endif
}
else
{
// Not running as a service, do normal startup
universalMain(std::move(argsRepo));
while (!cRoot::m_TerminateEventRaised)
{
// Not running as a service, do normal startup
UniversalMain(std::move(ParseArguments(argc, argv)));
}
}
#if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER)

View File

@ -118,6 +118,7 @@ static void DoTest(void)
int main()
{
cNetworkSingleton::Get().Initialise();
DoTest();
cNetworkSingleton::Get().Terminate();