0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-06-24 12:58:21 +02:00

ircd: Improve and comment on runlevel / client interruption.

This commit is contained in:
Jason Volk 2017-11-06 12:14:50 -08:00
parent 87c9ebdbc3
commit d7136bb393
5 changed files with 152 additions and 75 deletions

View file

@ -129,7 +129,7 @@ try
{
switch(mode)
{
case ircd::runlevel::STOPPED:
case ircd::runlevel::HALT:
case ircd::runlevel::FAULT:
sigs.cancel();
break;
@ -253,7 +253,7 @@ handle_quit()
try
{
console_cancel();
ircd::stop();
ircd::quit();
}
catch(const std::exception &e)
{

View file

@ -208,7 +208,7 @@ try
if(line == "exit" || line == "die")
{
ircd::stop();
ircd::quit();
return false;
}

View file

@ -38,7 +38,7 @@ namespace ircd
{
struct init;
enum class runlevel;
enum class runlevel: uint;
using runlevel_handler = std::function<void (const enum runlevel &)>;
extern bool debugmode; ///< Toggled by command line to indicate debug behavior
@ -49,15 +49,34 @@ namespace ircd
void init(boost::asio::io_service &ios, const std::string &conf, runlevel_handler = {});
void init(boost::asio::io_service &ios, runlevel_handler = {});
bool stop() noexcept;
bool quit() noexcept;
}
/// The runlevel allows all observers to know the coarse state of IRCd and to
/// react accordingly. This can be used by the embedder of libircd to know
/// when it's safe to use or delete libircd resources. It is also used
/// similarly by the library and its modules.
///
/// Primary modes:
///
/// * HALT is the off mode. Nothing is/will be running in libircd until
/// an invocation of ircd::init();
///
/// * RUN is the service mode. Full client and application functionality
/// exists in this mode. Leaving the RUN mode is done with ircd::quit();
///
/// - Transitional modes: Modes which are working towards the next mode.
/// - Interventional modes: Modes which are *not* working towards the next
/// mode and may require some user action to continue.
///
enum class ircd::runlevel
:uint
{
FAULT = -1,
STOPPED = 0,
READY = 1,
START = 2,
RUN = 3,
STOP = 4,
HALT = 0x00, ///< [inter] IRCd Powered off.
READY = 0x01, ///< [inter] Ready for user to run ios event loop.
START = 0x02, ///< [trans] Starting up subsystems for service.
RUN = 0x04, ///< [inter] IRCd in service.
QUIT = 0x10, ///< [trans] Clean shutdown in progress
FAULT = 0xFF, ///< [trans] QUIT with exception (dirty shutdown)
};

View file

@ -70,17 +70,36 @@ ircd::client::init::init()
request.add(2);
}
ircd::client::init::~init()
noexcept
void
ircd::client::init::interrupt()
{
log::debug("Interrupting %zu requests; dropping %zu requests; disconnecting %zu clients.",
request.active(),
request.pending(),
client::clients.size());
if(request.active() || !client::clients.empty())
log::warning("Interrupting %zu requests; dropping %zu requests; disconnecting %zu clients...",
request.active(),
request.pending(),
client::clients.size());
request.interrupt();
disconnect_all();
}
ircd::client::init::~init()
noexcept
{
interrupt();
if(request.active() || request.size())
log::warning("Joining %zu active of %zu remaining request contexts...",
request.active(),
request.size());
request.join();
if(unlikely(!client::clients.empty()))
{
log::error("%zu clients are unterminated...", client::clients.size());
assert(client::clients.empty());
}
}
ircd::hostport

View file

@ -44,24 +44,43 @@ namespace ircd
void main();
}
/// Provides tcmalloc version information if tcmalloc is linked in to IRCd.
struct ircd::tc_version
{
int major, minor;
char patch[64];
std::string version;
tc_version();
};
int major{0}, minor{0};
char patch[64] {0};
std::string version {"unavailable"};
}
const ircd::tc_version;
/*
const char* tc_version(int* major, int* minor, const char** patch);
ircd::tc_version::tc_version()
:version{::tc_version(&major, &minor, reinterpret_cast<const char **>(&patch))}
{}
*/
/// Record of the ID of the thread static initialization took place on.
const std::thread::id
ircd::static_thread_id
{
std::this_thread::get_id()
};
/// "main" thread for IRCd; the one the main context landed on.
std::thread::id
ircd::thread_id
{};
/// Boost version indicator for compiled header files.
const uint
ircd::boost_version[3]
{
BOOST_VERSION / 100000,
BOOST_VERSION / 100 % 1000,
BOOST_VERSION % 100,
};
void
ircd::init(boost::asio::io_service &ios,
runlevel_handler function)
@ -69,7 +88,6 @@ ircd::init(boost::asio::io_service &ios,
init(ios, std::string{}, std::move(function));
}
///
/// Sets up the IRCd and its main context, then returns without blocking.
//
/// Pass your io_service instance, it will share it with the rest of your program.
@ -78,34 +96,64 @@ ircd::init(boost::asio::io_service &ios,
/// This function will setup the main program loop of libircd. The execution will
/// occur when your io_service.run() or poll() is further invoked.
///
/// init() can only be called from a runlevel::HALT state
void
ircd::init(boost::asio::io_service &ios,
const std::string &configfile,
runlevel_handler runlevel_changed)
try
{
if(runlevel != runlevel::HALT)
throw error("Cannot init() IRCd from runlevel %s",
reflect(runlevel));
// cores are not dumped without consent of the user to maintain the privacy
// of cryptographic key material in memory at the time of the crash.
if(RB_DEBUG_LEVEL || debugmode)
enable_coredumps();
assert(runlevel == runlevel::STOPPED);
// Samples the thread this context was executed on which should be where
// the user ran ios.run(). The user may have invoked ios.run() on multiple
// threads, but we consider this one thread a main thread for now...
ircd::thread_id = std::this_thread::get_id();
// Global ircd:: reference to the user's io_service and setup our main
// strand on that service.
ircd::ios = &ios;
ircd::strand = new struct strand(ios);
// Saves the user's runlevel_changed callback which we invoke.
ircd::runlevel_changed = std::move(runlevel_changed);
// The log is available, but it is console-only until conf opens files.
log::init();
log::mark("START");
log::mark("READY");
// Setup the main context, which is a new stack executing the function
// ircd::main(). The main_context is the first ircd::ctx to be spawned
// and will be the last to finish.
//
// The context::POST will delay this spawn until the next io_service
// event slice, so no context switch will occur here. Note that POST has
// to be used here because: A. This init() function is executing on the
// main stack, and context switches can only occur between context stacks,
// not between contexts and the main stack. B. The user's io_service may or
// may not even be running yet anyway.
//
ircd::context main_context
{
"main", 256_KiB, &ircd::main, context::POST
};
// The default behavior for ircd::context is to join the ctx on dtor. We
// can't have that here because this is strictly an asynchronous function
// on the main stack. Under normal circumstances, the mc will be entered
// and be able to delete this pointer itself when it finishes. Otherwise
// this must be manually deleted with assurance that mc will never enter.
ircd::main_context = main_context.detach();
ircd::runlevel_changed = std::move(runlevel_changed);
// This message flashes information about our dependencies which are being
// assumed for this execution.
log::info("%s. boost %u.%u.%u. rocksdb %s. sodium %s. %s.",
PACKAGE_STRING,
boost_version[0],
@ -115,6 +163,7 @@ try
nacl::version(),
openssl::version());
// This message flashes information about IRCd itself for this execution
log::info("%s %ld %s. configured: %s. compiled: %s %s",
BRANDING_VERSION,
__cplusplus,
@ -123,6 +172,9 @@ try
__TIMESTAMP__,
RB_DEBUG_LEVEL? "(DEBUG MODE)" : "");
// Finally, without prior exception, the commitment to runlevel::READY
// is made here. The user can now invoke their ios.run(), or, if they
// have already, IRCd will begin main execution shortly...
ircd::set_runlevel(runlevel::READY);
}
catch(const std::exception &e)
@ -132,21 +184,19 @@ catch(const std::exception &e)
throw;
}
///
/// Notifies IRCd to shutdown. A shutdown will occur asynchronously and this
/// function will return immediately. main_exit_cb will be called when IRCd
/// has no more work for the ios (main_exit_cb will be the last operation from
/// IRCd posted to the ios).
/// function will return immediately. A runlevel change to HALT will be
/// indicated when IRCd has no more work for the ios. When the HALT state
/// is observed the user is free to destruct all resources related to libircd.
///
/// This function is the proper way to shutdown libircd after an init(), and while
/// your io_service.run() is invoked without stopping your io_service shared by
/// other activities unrelated to libircd. If your io_service has no other activities
/// the run() will then return.
///
/// This is useful when your other activities prevent run() from returning.
/// the run() will then return immediately after IRCd posts its transition to
/// the HALT state.
///
bool
ircd::stop()
ircd::quit()
noexcept
{
if(runlevel != runlevel::RUN)
@ -170,7 +220,7 @@ noexcept
/// The status of this function and IRCd overall can be observed through
/// the ircd::runlevel. The ircd::runlevel_changed callback can be set
/// to be notified on a runlevel change. The user should wait for a runlevel
/// of STOPPED before destroying IRCd related resources and stopping their
/// of HALT before destroying IRCd related resources and stopping their
/// io_service from running more jobs.
///
void
@ -182,11 +232,11 @@ try
ircd::set_runlevel(runlevel::START);
// When this function completes, subsystems are done shutting down and IRCd
// transitions to STOPPED.
const unwind stopped{[]
// transitions to HALT.
const unwind halted{[]
{
at_main_exit();
set_runlevel(runlevel::STOPPED);
set_runlevel(runlevel::HALT);
}};
// These objects are the init()'s and fini()'s for each subsystem.
@ -210,14 +260,21 @@ try
_client_.interrupt();
}};
// IRCd will now transition to the RUN state indicating full functionality.
ircd::set_runlevel(runlevel::RUN);
// When the call to wait() below completes, IRCd exits from the RUN state
// and enters one of the two states below depending on whether the unwind
// is taking place normally or because of an exception.
const unwind::nominal nominal{std::bind(&ircd::set_runlevel, runlevel::STOP)};
const unwind::exceptional exceptional{std::bind(&ircd::set_runlevel, runlevel::FAULT)};
const unwind::nominal nominal
{
std::bind(&ircd::set_runlevel, runlevel::QUIT)
};
const unwind::exceptional exceptional
{
std::bind(&ircd::set_runlevel, runlevel::FAULT)
};
// IRCd will now transition to the RUN state indicating full functionality.
ircd::set_runlevel(runlevel::RUN);
// This call blocks until the main context is notified or interrupted etc.
// Waiting here will hold open this stack with all of the above objects
@ -246,6 +303,14 @@ noexcept
});
}
/// Sets the runlevel of IRCd and notifies users. This should never be called
/// manually/directly, as it doesn't trigger a runlevel change itself, it just
/// notifies of one.
///
/// The notification will be posted to the io_service. This is important to
/// prevent the callback from continuing execution on some ircd::ctx stack and
/// instead invoke their function on the main stack in their own io_service
/// event slice.
void
ircd::set_runlevel(const enum runlevel &new_runlevel)
try
@ -263,13 +328,13 @@ try
if(ircd::runlevel_changed)
ios->post([new_runlevel]
{
if(new_runlevel == runlevel::STOPPED)
if(new_runlevel == runlevel::HALT)
log::notice("IRCd %s", reflect(new_runlevel));
ircd::runlevel_changed(new_runlevel);
});
if(new_runlevel != runlevel::STOPPED)
if(new_runlevel != runlevel::HALT)
log::notice("IRCd %s", reflect(new_runlevel));
}
catch(const std::exception &e)
@ -286,43 +351,17 @@ ircd::reflect(const enum runlevel &level)
{
switch(level)
{
case runlevel::FAULT: return "FAULT";
case runlevel::STOPPED: return "STOPPED";
case runlevel::HALT: return "HALT";
case runlevel::READY: return "READY";
case runlevel::START: return "START";
case runlevel::RUN: return "RUN";
case runlevel::STOP: return "STOP";
case runlevel::QUIT: return "QUIT";
case runlevel::FAULT: return "FAULT";
}
return "??????";
}
/*
const char* tc_version(int* major, int* minor, const char** patch);
ircd::tc_version::tc_version()
:version{::tc_version(&major, &minor, reinterpret_cast<const char **>(&patch))}
{}
*/
ircd::tc_version::tc_version()
:major{0}
,minor{0}
,patch{0}
,version{"unavailable"}
{}
struct ircd::tc_version
const ircd::tc_version
{};
const uint
ircd::boost_version[3]
{
BOOST_VERSION / 100000,
BOOST_VERSION / 100 % 1000,
BOOST_VERSION % 100,
};
void
#ifdef HAVE_SYS_RESOURCE_H
ircd::enable_coredumps()