diff options
Diffstat (limited to 'src/Root.cpp')
-rw-r--r-- | src/Root.cpp | 370 |
1 files changed, 170 insertions, 200 deletions
diff --git a/src/Root.cpp b/src/Root.cpp index 4b40fd983..4291c5779 100644 --- a/src/Root.cpp +++ b/src/Root.cpp @@ -46,7 +46,10 @@ -cRoot * cRoot::s_Root = nullptr; +extern bool g_RunAsService; +decltype(cRoot::s_Root) cRoot::s_Root; +decltype(cRoot::s_NextState) cRoot::s_NextState; +decltype(cRoot::s_StopEvent) cRoot::s_StopEvent; @@ -65,7 +68,7 @@ cRoot::cRoot(void) : { Temporary::RegisterAllBlockHandlers(m_BlockTypeRegistry); s_Root = this; - m_InputThreadRunFlag.clear(); + TransitionNextState(NextState::Run); } @@ -81,85 +84,28 @@ cRoot::~cRoot() -void cRoot::InputThread(cRoot & a_Params) +bool cRoot::Run(cSettingsRepositoryInterface & a_OverridesRepo) { - cLogCommandOutputCallback Output; - AString Command; - - while (a_Params.m_InputThreadRunFlag.test_and_set() && std::cin.good()) - { - #ifndef _WIN32 - timeval Timeout{ 0, 0 }; - Timeout.tv_usec = 100 * 1000; // 100 msec - - fd_set ReadSet; - FD_ZERO(&ReadSet); - FD_SET(STDIN_FILENO, &ReadSet); - - if (select(STDIN_FILENO + 1, &ReadSet, nullptr, nullptr, &Timeout) <= 0) - { - // Don't call getline because there's nothing to read - continue; - } - #endif - - std::getline(std::cin, Command); - - if (!a_Params.m_InputThreadRunFlag.test_and_set()) - { - // Already shutting down, can't execute commands - break; - } - - if (!Command.empty()) - { - // Execute and clear command string when submitted - a_Params.ExecuteConsoleCommand(TrimString(Command), Output); - } - } - - // We have come here because the std::cin has received an EOF / a terminate signal has been sent, and the server is still running - // Ignore this when running as a service, cin was never opened in that case - if (!std::cin.good() && !m_RunAsService) - { - // Stop the server: - a_Params.QueueExecuteConsoleCommand("stop"); - } -} - - - - - -void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo) -{ - #ifdef _WIN32 - HMENU ConsoleMenu = GetSystemMenu(GetConsoleWindow(), FALSE); - EnableMenuItem(ConsoleMenu, SC_CLOSE, MF_GRAYED); // Disable close button when starting up; it causes problems with our CTRL-CLOSE handling - #endif - - auto consoleLogListener = MakeConsoleListener(m_RunAsService); + auto consoleLogListener = MakeConsoleListener(g_RunAsService); auto consoleAttachment = cLogger::GetInstance().AttachListener(std::move(consoleLogListener)); cLogger::cAttachment fileAttachment; - if (!a_OverridesRepo->HasValue("Server","DisableLogFile")) + if (!a_OverridesRepo.HasValue("Server","DisableLogFile")) { auto fileLogListenerRet = MakeFileListener(); if (!fileLogListenerRet.first) { - m_TerminateEventRaised = true; - LOGERROR("Failed to open log file, aborting"); - return; + throw std::runtime_error("failed to open log file"); } fileAttachment = cLogger::GetInstance().AttachListener(std::move(fileLogListenerRet.second)); } LOG("--- Started Log ---"); - #ifdef BUILD_ID - LOG("Cuberite " BUILD_SERIES_NAME " build id: " BUILD_ID); - LOG("from commit id: " BUILD_COMMIT_ID " built at: " BUILD_DATETIME); - #endif +#ifdef BUILD_ID + LOG("Cuberite " BUILD_SERIES_NAME " (id: " BUILD_ID ")"); + LOG("from commit " BUILD_COMMIT_ID " built at: " BUILD_DATETIME); +#endif cDeadlockDetect dd; auto BeginTime = std::chrono::steady_clock::now(); @@ -172,9 +118,9 @@ void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo) LOG("Reading server config..."); m_SettingsFilename = "settings.ini"; - if (a_OverridesRepo->HasValue("Server","ConfigFile")) + if (a_OverridesRepo.HasValue("Server","ConfigFile")) { - m_SettingsFilename = a_OverridesRepo->GetValue("Server","ConfigFile"); + m_SettingsFilename = a_OverridesRepo.GetValue("Server","ConfigFile"); } auto IniFile = std::make_unique<cIniFile>(); @@ -187,7 +133,7 @@ void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo) IniFile->AddHeaderComment(" Most of the settings here can be configured using the webadmin interface, if enabled in webadmin.ini"); } - auto settingsRepo = std::make_unique<cOverridesSettingsRepository>(std::move(IniFile), std::move(a_OverridesRepo)); + auto settingsRepo = std::make_unique<cOverridesSettingsRepository>(std::move(IniFile), a_OverridesRepo); LOG("Starting server..."); @@ -200,8 +146,7 @@ void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo) if (!m_Server->InitServer(*settingsRepo, ShouldAuthenticate)) { settingsRepo->Flush(); - LOGERROR("Failure starting server, aborting..."); - return; + throw std::runtime_error("failure starting server"); } m_WebAdmin = new cWebAdmin(); @@ -245,32 +190,13 @@ void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo) { m_WebAdmin->Start(); - LOGD("Starting InputThread..."); - try - { - m_InputThreadRunFlag.test_and_set(); - m_InputThread = std::thread(InputThread, std::ref(*this)); - } - 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()); - } - 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(); - #ifdef _WIN32 - EnableMenuItem(ConsoleMenu, SC_CLOSE, MF_ENABLED); // Re-enable close button - #endif - - for (;;) - { - m_StopEvent.Wait(); - - break; - } + HandleInput(); + s_StopEvent.Wait(); // Stop the server: m_WebAdmin->Stop(); @@ -278,13 +204,6 @@ void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo) LOG("Shutting down server..."); m_Server->Shutdown(); } // if (m_Server->Start() - else - { - cRoot::m_TerminateEventRaised = true; - #ifdef _WIN32 - EnableMenuItem(ConsoleMenu, SC_CLOSE, MF_ENABLED); // Re-enable close button - #endif - } delete m_MojangAPI; m_MojangAPI = nullptr; @@ -313,75 +232,28 @@ void cRoot::Start(std::unique_ptr<cSettingsRepositoryInterface> a_OverridesRepo) LOG("Cleaning up..."); delete m_Server; m_Server = nullptr; - m_InputThreadRunFlag.clear(); - #ifdef _WIN32 - DWORD Length; - INPUT_RECORD Record - { - KEY_EVENT, - { - { - TRUE, - 1, - VK_RETURN, - static_cast<WORD>(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 - m_InputThread.join(); - #endif - - if (m_TerminateEventRaised) - { - LOG("Shutdown successful!"); - } - else - { - LOG("Shutdown successful - restarting..."); - } + LOG("Shutdown successful!"); LOG("--- Stopped Log ---"); + + return s_NextState == NextState::Restart; } -void cRoot::StopServer() +void cRoot::Stop() { - // Kick all players from the server with custom disconnect message + TransitionNextState(NextState::Stop); +} - bool SentDisconnect = false; - cRoot::Get()->ForEachPlayer([&](cPlayer & a_Player) - { - a_Player.GetClientHandlePtr()->Kick(m_Server->GetShutdownMessage()); - SentDisconnect = true; - return false; - } - ); - if (SentDisconnect) - { - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - m_TerminateEventRaised = true; - m_StopEvent.Set(); - m_InputThreadRunFlag.clear(); + + + + +void cRoot::Restart() +{ + TransitionNextState(NextState::Restart); } @@ -607,58 +479,39 @@ bool cRoot::ForEachWorld(cWorldListCallback a_Callback) -void cRoot::TickCommands(void) -{ - // Execute any pending commands: - cCommandQueue PendingCommands; - { - cCSLock Lock(m_CSPendingCommands); - std::swap(PendingCommands, m_PendingCommands); - } - for (cCommandQueue::iterator itr = PendingCommands.begin(), end = PendingCommands.end(); itr != end; ++itr) - { - ExecuteConsoleCommand(itr->m_Command, *(itr->m_Output)); - } -} - - - - - void cRoot::QueueExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallback & a_Output) { - // Put the command into a queue (Alleviates FS #363): - cCSLock Lock(m_CSPendingCommands); - m_PendingCommands.emplace_back(a_Cmd, &a_Output); -} - - - - - -void cRoot::QueueExecuteConsoleCommand(const AString & a_Cmd) -{ - // Put the command into a queue (Alleviates FS #363): - cCSLock Lock(m_CSPendingCommands); - m_PendingCommands.push_back(cCommand(a_Cmd, new cLogCommandDeleteSelfOutputCallback)); -} - - + const auto KickPlayers = [this] + { + // Kick all players from the server with custom disconnect message + bool SentDisconnect = false; + cRoot::Get()->ForEachPlayer( + [&](cPlayer & a_Player) + { + a_Player.GetClientHandlePtr()->Kick(m_Server->GetShutdownMessage()); + SentDisconnect = true; + return false; + } + ); + if (SentDisconnect) + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + }; -void cRoot::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallback & a_Output) -{ // Some commands are built-in: if (a_Cmd == "stop") { - StopServer(); + KickPlayers(); + cRoot::Stop(); return; } else if (a_Cmd == "restart") { - m_StopEvent.Set(); - m_InputThreadRunFlag.clear(); + KickPlayers(); + cRoot::Restart(); return; } @@ -670,6 +523,16 @@ void cRoot::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallback +void cRoot::QueueExecuteConsoleCommand(const AString & a_Cmd) +{ + // Put the command into a queue (Alleviates FS #363): + QueueExecuteConsoleCommand(a_Cmd, *new cLogCommandDeleteSelfOutputCallback); +} + + + + + void cRoot::KickUser(int a_ClientID, const AString & a_Reason) { m_Server->KickUser(a_ClientID, a_Reason); @@ -1068,3 +931,110 @@ AStringVector cRoot::GetPlayerTabCompletionMultiWorld(const AString & a_Text) ); return Results; } + + + + + +void cRoot::HandleInput() +{ + if (g_RunAsService) + { + // Ignore input when running as a service, cin was never opened in that case: + return; + } + + cLogCommandOutputCallback Output; + AString Command; + + while (s_NextState == NextState::Run) + { +#ifndef _WIN32 + timeval Timeout{ 0, 0 }; + Timeout.tv_usec = 100 * 1000; // 100 msec + + fd_set ReadSet; + FD_ZERO(&ReadSet); + FD_SET(STDIN_FILENO, &ReadSet); + + if (select(STDIN_FILENO + 1, &ReadSet, nullptr, nullptr, &Timeout) <= 0) + { + // Don't call getline because there's nothing to read + continue; + } +#endif + + if (!std::getline(std::cin, Command)) + { + cRoot::Stop(); + return; + } + + if (s_NextState != NextState::Run) + { + // Already shutting down, can't execute commands + break; + } + + if (!Command.empty()) + { + // Execute and clear command string when submitted + QueueExecuteConsoleCommand(TrimString(Command), Output); + } + } +} + + + + + +void cRoot::TransitionNextState(NextState a_NextState) +{ + { + auto Current = s_NextState.load(); + do + { + // Stopping is final, so stops override restarts: + if (Current == NextState::Stop) + { + return; + } + } + while (!s_NextState.compare_exchange_strong(Current, a_NextState)); + } + + if (s_NextState == NextState::Run) + { + return; + } + + s_StopEvent.Set(); + +#ifdef WIN32 + if (g_RunAsService) + { + return; + } + + DWORD Length; + INPUT_RECORD Record + { + KEY_EVENT, + { + { + TRUE, + 1, + VK_RETURN, + static_cast<WORD>(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 apart from CancelIoEx, but xoft wants Windows XP support + // Only thing I can think of for now. + VERIFY(WriteConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &Record, 1, &Length) == TRUE); +#endif +} |