From d7136bb393018af1fa1a66bfe85b9546ca30065c Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Mon, 6 Nov 2017 12:14:50 -0800 Subject: [PATCH] ircd: Improve and comment on runlevel / client interruption. --- charybdis/charybdis.cc | 4 +- charybdis/console.cc | 2 +- include/ircd/ircd.h | 35 +++++++--- ircd/client.cc | 31 +++++++-- ircd/ircd.cc | 155 ++++++++++++++++++++++++++--------------- 5 files changed, 152 insertions(+), 75 deletions(-) diff --git a/charybdis/charybdis.cc b/charybdis/charybdis.cc index 1fc71b88e..e71701608 100644 --- a/charybdis/charybdis.cc +++ b/charybdis/charybdis.cc @@ -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) { diff --git a/charybdis/console.cc b/charybdis/console.cc index df5f281ac..47e8d2dc6 100644 --- a/charybdis/console.cc +++ b/charybdis/console.cc @@ -208,7 +208,7 @@ try if(line == "exit" || line == "die") { - ircd::stop(); + ircd::quit(); return false; } diff --git a/include/ircd/ircd.h b/include/ircd/ircd.h index 82b6e6d88..413b29a54 100644 --- a/include/ircd/ircd.h +++ b/include/ircd/ircd.h @@ -38,7 +38,7 @@ namespace ircd { struct init; - enum class runlevel; + enum class runlevel: uint; using runlevel_handler = std::function; 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) }; diff --git a/ircd/client.cc b/ircd/client.cc index 43371fda6..a84f1731f 100644 --- a/ircd/client.cc +++ b/ircd/client.cc @@ -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 diff --git a/ircd/ircd.cc b/ircd/ircd.cc index 49a4dc3e7..ac9fe0366 100644 --- a/ircd/ircd.cc +++ b/ircd/ircd.cc @@ -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(&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(&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()