Streamline startup sequence
* Clean up cRoot & main * Move some OS-specifics into OSSupport
This commit is contained in:
382
src/Root.cpp
382
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,29 +479,44 @@ 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);
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
||||
// Some commands are built-in:
|
||||
if (a_Cmd == "stop")
|
||||
{
|
||||
KickPlayers();
|
||||
cRoot::Stop();
|
||||
return;
|
||||
}
|
||||
else if (a_Cmd == "restart")
|
||||
{
|
||||
KickPlayers();
|
||||
cRoot::Restart();
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("Executing console command: \"%s\"", a_Cmd.c_str());
|
||||
m_Server->ExecuteConsoleCommand(a_Cmd, a_Output);
|
||||
}
|
||||
|
||||
|
||||
@@ -639,31 +526,7 @@ void cRoot::QueueExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCall
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cRoot::ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallback & a_Output)
|
||||
{
|
||||
// Some commands are built-in:
|
||||
if (a_Cmd == "stop")
|
||||
{
|
||||
StopServer();
|
||||
return;
|
||||
}
|
||||
else if (a_Cmd == "restart")
|
||||
{
|
||||
m_StopEvent.Set();
|
||||
m_InputThreadRunFlag.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("Executing console command: \"%s\"", a_Cmd.c_str());
|
||||
m_Server->ExecuteConsoleCommand(a_Cmd, a_Output);
|
||||
QueueExecuteConsoleCommand(a_Cmd, *new cLogCommandDeleteSelfOutputCallback);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user