mirror of
https://github.com/worldforge/cyphesis
synced 2026-08-13 12:26:04 -04:00
182 lines
7.4 KiB
C++
182 lines
7.4 KiB
C++
/*
|
|
Copyright (C) 2018 Erik Ogenvik
|
|
|
|
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, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include "MainLoop.h"
|
|
|
|
#include "common/system.h"
|
|
#include "globals.h"
|
|
#include "OperationsDispatcher.h"
|
|
#include "compose.hpp"
|
|
#include "log.h"
|
|
#include <boost/asio/signal_set.hpp>
|
|
#include <boost/asio/deadline_timer.hpp>
|
|
|
|
void interactiveSignalsHandler(boost::asio::signal_set& this_, boost::system::error_code error, int signal_number)
|
|
{
|
|
if (!error) {
|
|
switch (signal_number) {
|
|
case SIGINT:
|
|
case SIGTERM:
|
|
case SIGHUP:
|
|
//If we've already received one call to shut down softly we should elevate
|
|
//it to a hard shutdown.
|
|
//This also happens if "soft" exit isn't enabled.
|
|
if (exit_flag_soft || !exit_soft_enabled) {
|
|
exit_flag = true;
|
|
} else {
|
|
exit_flag_soft = true;
|
|
}
|
|
break;
|
|
case SIGQUIT:
|
|
exit_flag = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
this_.async_wait([&this_](boost::system::error_code error, int signal_number){interactiveSignalsHandler(this_, error, signal_number);});
|
|
}
|
|
}
|
|
|
|
void daemonSignalsHandler(boost::asio::signal_set& this_, boost::system::error_code error, int signal_number)
|
|
{
|
|
if (!error) {
|
|
switch (signal_number) {
|
|
case SIGTERM:
|
|
//If we've already received one call to shut down softly we should elevate
|
|
//it to a hard shutdown.
|
|
//This also happens if "soft" exit isn't enabled.
|
|
if (exit_flag_soft || !exit_soft_enabled) {
|
|
exit_flag = true;
|
|
} else {
|
|
exit_flag_soft = true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
this_.async_wait([&this_](boost::system::error_code error, int signal_number){daemonSignalsHandler(this_, error, signal_number);});
|
|
}
|
|
}
|
|
|
|
void MainLoop::run(bool daemon, boost::asio::io_service& io_service, OperationsHandler& operationsHandler, const Callbacks& callbacks)
|
|
{
|
|
|
|
boost::asio::signal_set signalSet(io_service);
|
|
//If we're not running as a daemon we should use the interactive signal handler.
|
|
if (!daemon) {
|
|
signalSet.add(SIGINT);
|
|
signalSet.add(SIGTERM);
|
|
signalSet.add(SIGHUP);
|
|
signalSet.add(SIGQUIT);
|
|
|
|
signalSet.async_wait([&signalSet](boost::system::error_code error, int signal_number){interactiveSignalsHandler(signalSet, error, signal_number);});
|
|
} else {
|
|
signalSet.add(SIGTERM);
|
|
|
|
signalSet.async_wait([&signalSet](boost::system::error_code error, int signal_number){daemonSignalsHandler(signalSet, error, signal_number);});
|
|
}
|
|
|
|
|
|
bool soft_exit_in_progress = false;
|
|
|
|
|
|
//Make sure that the io_service never runs out of work.
|
|
boost::asio::io_service::work work(io_service);
|
|
//This timer is used to wake the io_service when next op needs to be handled.
|
|
boost::asio::deadline_timer nextOpTimer(io_service);
|
|
//This timer will set a deadline for any mind persistence during soft exits.
|
|
boost::asio::deadline_timer softExitTimer(io_service);
|
|
// Loop until the exit flag is set. The exit flag can be set anywhere in
|
|
// the code easily.
|
|
while (!exit_flag) {
|
|
try {
|
|
bool busy = operationsHandler.idle(10);
|
|
operationsHandler.markQueueAsClean();
|
|
//If the world is busy we should just poll.
|
|
if (busy) {
|
|
io_service.poll();
|
|
} else {
|
|
//If it's not busy however we should run until we get a task.
|
|
//We will either get an io task, or we will be triggered by the timer
|
|
//which is set to expire when the next op should be dispatched.
|
|
double secondsUntilNextOp = operationsHandler.secondsUntilNextOp();
|
|
if (secondsUntilNextOp <= 0.0) {
|
|
io_service.poll();
|
|
} else {
|
|
bool nextOpTimeExpired = false;
|
|
boost::posix_time::microseconds waitTime((int64_t)(secondsUntilNextOp * 1000000L));
|
|
nextOpTimer.expires_from_now(waitTime);
|
|
nextOpTimer.async_wait([&](boost::system::error_code ec) {
|
|
if (ec != boost::asio::error::operation_aborted) {
|
|
nextOpTimeExpired = true;
|
|
}
|
|
});
|
|
//Keep on running IO handlers until either the queue is dirty (i.e. we need to handle
|
|
//any new operation) or the timer has expired.
|
|
do {
|
|
io_service.run_one();
|
|
} while (!operationsHandler.isQueueDirty() && !nextOpTimeExpired &&
|
|
!exit_flag_soft && !exit_flag && !soft_exit_in_progress);
|
|
nextOpTimer.cancel();
|
|
}
|
|
}
|
|
if (soft_exit_in_progress) {
|
|
//If we're in soft exit mode and either the deadline has been exceeded
|
|
//or we've persisted all minds we should shut down normally.
|
|
if (!callbacks.softExitPoll || callbacks.softExitPoll()) {
|
|
exit_flag = true;
|
|
softExitTimer.cancel();
|
|
}
|
|
} else if (exit_flag_soft) {
|
|
exit_flag_soft = false;
|
|
soft_exit_in_progress = true;
|
|
if (callbacks.softExitStart) {
|
|
auto duration = callbacks.softExitStart();
|
|
softExitTimer.expires_from_now(duration);
|
|
softExitTimer.async_wait([&](boost::system::error_code ec) {
|
|
if (!ec) {
|
|
if (callbacks.softExitTimeout) {
|
|
callbacks.softExitTimeout();
|
|
}
|
|
exit_flag = true;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
// It is hoped that commonly thrown exception, particularly
|
|
// exceptions that can be caused by external influences
|
|
// should be caught close to where they are thrown. If
|
|
// an exception makes it here then it should be debugged.
|
|
} catch (const std::exception& e) {
|
|
log(ERROR, String::compose("Exception caught in main loop: %1", e.what()));
|
|
} catch (...) {
|
|
log(ERROR, "Exception caught in main()");
|
|
}
|
|
}
|
|
// exit flag has been set so we close down the databases, and indicate
|
|
// to the metaserver (if we are using one) that this server is going down.
|
|
// It is assumed that any preparation for the shutdown that is required
|
|
// by the game has been done before exit flag was set.
|
|
log(NOTICE, "Performing clean shutdown...");
|
|
|
|
|
|
|
|
signalSet.clear();
|
|
|
|
|
|
}
|