From e7c66d86d930a6476cfc5be0d1e91e843a7d4142 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Wed, 2 Oct 2019 16:49:30 -0700 Subject: [PATCH] ircd::run: Add another runlevel; eliminate main callback; simplify. --- construct/construct.cc | 45 +++++----- include/ircd/run.h | 45 +++++----- ircd/ircd.cc | 186 ++++++++++++++++------------------------- matrix/matrix.cc | 2 +- 4 files changed, 118 insertions(+), 160 deletions(-) diff --git a/construct/construct.cc b/construct/construct.cc index 85a980b62..e5b365671 100644 --- a/construct/construct.cc +++ b/construct/construct.cc @@ -36,7 +36,7 @@ bool write_avoid; bool soft_assert; bool nomatrix; const char *execute; -std::array smoketest; +std::array smoketest; lgetopt opts[] { @@ -170,23 +170,6 @@ noexcept try } }; - // This is the sole io_context for Construct, and the ios.run() below is the - // the only place where the program actually blocks. - boost::asio::io_context ios; - - // libircd does no signal handling (or at least none that you ever have to - // care about); reaction to all signals happens out here instead. Handling - // is done properly through the io_context which registers the handler for - // the platform and then safely posts the received signal to the io_context - // event loop. This means we lose the true instant hardware-interrupt gratitude - // of signals but with the benefit of unconditional safety and cross- - // platformness with windows etc. - const construct::signals signals{ios}; - - // Associates libircd with our io_context and posts the initial routines - // to that io_context. Execution of IRCd will then occur during ios::run() - ircd::init(ios); - // Setup synchronization primitives on this stack for starting and stopping // the application (matrix homeserver). Note this stack cannot actually use // these; they'll be used to synchronize the closures below running in @@ -238,15 +221,15 @@ noexcept try // runlevel RUN. It is called again immediately after entering runlevel // QUIT, but before any functionality of libircd destructs. This cues us // to start and stop the homeserver. - const decltype(ircd::run::main)::callback loader + const ircd::run::changed loader { - ircd::run::main, [&homeserver, &start, &quit] + [&homeserver, &start, &quit](const auto &level) { static ircd::context context; // 2 This branch is taken the first time this function is called, // and not taken the second time. - if(!context) + if(level == ircd::run::level::IDLE && !context) { // 3 Launch the homeserver context (asynchronous). context = { "matrix", homeserver }; @@ -259,6 +242,9 @@ noexcept try return; } + if(level != ircd::run::level::QUIT) + return; + // 11 // Notify the waiting homeserver context to quit; this will // start shutting down the homeserver. @@ -270,6 +256,23 @@ noexcept try } }; + // This is the sole io_context for Construct, and the ios.run() below is the + // the only place where the program actually blocks. + boost::asio::io_context ios; + + // libircd does no signal handling (or at least none that you ever have to + // care about); reaction to all signals happens out here instead. Handling + // is done properly through the io_context which registers the handler for + // the platform and then safely posts the received signal to the io_context + // event loop. This means we lose the true instant hardware-interrupt gratitude + // of signals but with the benefit of unconditional safety and cross- + // platformness with windows etc. + const construct::signals signals{ios}; + + // Associates libircd with our io_context and posts the initial routines + // to that io_context. Execution of IRCd will then occur during ios::run() + ircd::init(ios); + // If the user wants to immediately drop to an interactive command line // without having to send a ctrl-c for it, that is provided here. This does // not actually take effect until it's processed in the ios.run() below. diff --git a/include/ircd/run.h b/include/ircd/run.h index db731787e..ca9aa39e5 100644 --- a/include/ircd/run.h +++ b/include/ircd/run.h @@ -30,40 +30,31 @@ namespace ircd::run // Access to the current runlevel indicator. extern const enum level &level; - // Another callback list (albeit simply using util::callbacks) which is - // called during level::START on the main context after all subsystems have - // constructed, and again in level::QUIT before all subsystems have - // destructed. The purpose here is to allow extensions to the START and - // QUIT runlevels as if another subsystem was being listed in ircd::main(). - // These callbacks are intended to block the main context from moving to - // the next runlevel until each one completes (run::changed doesn't quite - // work this way). - extern callbacks main; + // Access to the desired runlevel. When this differs from the current + // level, a command to change the runlevel has been given but not all + // tasks have completed at the current runlevel. + extern const enum level &chadburn; } /// An instance of this class registers itself to be called back when -/// the ircd::run::level has changed. +/// the ircd::run::level has changed. The context for this callback differs +/// based on the level argument; not all invocations are on an ircd::ctx, etc. /// /// Note: Its destructor will access a list in libircd; after a callback /// for a HALT do not unload libircd.so until destructing this object. /// -/// A static ctx::dock is also available for contexts to wait for a run::level -/// change notification. -/// struct ircd::run::changed :instance_list -,std::function { using handler = std::function; - // Users on an ircd::ctx who wish to use the dock interface to wait for - // a run::level change can directly access this static instance. static ctx::dock dock; + handler function; + /// The handler function will be called back for any run::level change while /// this instance remains in scope. - changed(handler function); - ~changed() noexcept; + changed(handler function) noexcept; }; /// The run::level allows all observers to know the coarse state of IRCd and to @@ -82,6 +73,9 @@ struct ircd::run::changed /// * START indicates the daemon is executing its startup procedures. Leaving /// the START state occurs internally when there is success or a fatal error. /// +/// * IDLE indicates the library is ready for use. The user can load their +/// application in this mode before transitioning to RUN. +/// /// * RUN is the service mode. Full client and application functionality exists /// in this mode. Leaving the RUN mode is done with ircd::quit(); /// @@ -94,10 +88,17 @@ struct ircd::run::changed enum class ircd::run::level :int { + FAULT = -1, ///< Unrecoverable fault. HALT = 0, ///< x <-- IRCd Powered off. READY = 1, ///< | | Ready for user to run ios event loop. - START = 2, ///< | | Starting up subsystems for service. - RUN = 3, ///< O | IRCd in service. - QUIT = 4, ///< --> ^ Clean shutdown in progress. - FAULT = -1, ///< Unrecoverable fault. + START = 2, ///< | | Starting internal subsystems. + IDLE = 3, ///< | | Ready for user to load application. + RUN = 4, ///< O | IRCd in service. + QUIT = 5, ///< --> ^ Clean shutdown in progress. }; + +inline +ircd::run::changed::changed(handler function) +noexcept +:function(std::move(function)) +{} diff --git a/ircd/ircd.cc b/ircd/ircd.cc index ac8f5b74c..389d0a232 100644 --- a/ircd/ircd.cc +++ b/ircd/ircd.cc @@ -8,6 +8,13 @@ // copyright notice and this permission notice is present in all copies. The // full license for this software is available in the LICENSE file. +// internal interface to ircd::run (see ircd/run.h) +namespace ircd::run +{ + // change the current runlevel + static bool set(const enum level &); +} + namespace ircd { // Fundamental context #1; all subsystems live as objects on this stack. @@ -20,22 +27,6 @@ namespace ircd static void main() noexcept; } -// internal interface to ircd::run (see ircd/run.h) -namespace ircd::run -{ - struct main_event; - - // change the current runlevel - static bool set(const enum level &); -} - -/// Placed on the main stack after all subsystems; this object represents -/// a user's extension to the main stack via callbacks (see ircd/run.h) -struct ircd::run::main_event -{ - main_event(), ~main_event() noexcept; -}; - decltype(ircd::version_api) ircd::version_api { @@ -191,11 +182,11 @@ noexcept return true; } + case run::level::IDLE: case run::level::START: { ctx::terminate(*main_context); main_context = nullptr; - ircd::run::set(run::level::QUIT); return true; } @@ -242,6 +233,7 @@ noexcept case run::level::QUIT: return; + case run::level::IDLE: case run::level::RUN: break; } @@ -301,10 +293,6 @@ noexcept try server::init _server_; // Server related client::init _client_; // Client related js::init _js_; // SpiderMonkey - run::main_event _me_; // Additional user callbacks - - // IRCd will now transition to the RUN state indicating full functionality. - run::set(run::level::RUN); // Transition to the QUIT state on unwind. const unwind quit{[] @@ -312,13 +300,17 @@ noexcept try ircd::run::set(run::level::QUIT); }}; + // IRCd will now transition to the IDLE state allowing library user's to + // load their applications using the run::changed callback. + run::set(run::level::IDLE); + + // IRCd will now transition to the RUN state indicating full functionality. + run::set(run::level::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 // living on it. - run::changed::dock.wait([] - { - return !main_context; - }); + ctx::wait(); } catch(const http::error &e) // <-- m::error { @@ -360,7 +352,7 @@ ircd::uptime() namespace ircd::run { - static enum level _level; + static enum level _level, _chadburn; } decltype(ircd::run::level) @@ -369,26 +361,11 @@ ircd::run::level _level }; -decltype(ircd::run::main) -ircd::run::main; - -// -// run::main_event -// - -ircd::run::main_event::main_event() +decltype(ircd::run::chadburn) +ircd::run::chadburn { - assert(level == level::START); - run::main(); -} - -ircd::run::main_event::~main_event() -noexcept -{ - assert(level != level::RUN); - assert(level == level::QUIT); - run::main(); -} + _chadburn +}; // // run::changed @@ -409,23 +386,6 @@ ircd::util::instance_list::list decltype(ircd::run::changed::dock) ircd::run::changed::dock; -// -// run::changed::changed -// - -ircd::run::changed::changed(handler function) -:handler -{ - std::move(function) -} -{ -} - -ircd::run::changed::~changed() -noexcept -{ -} - /// The notification will be posted to the io_context. 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_context @@ -434,72 +394,65 @@ bool ircd::run::set(const enum level &new_level) try { - if(level == new_level) + if(new_level == chadburn) return false; - log::debug - { - "IRCd level transition from '%s' to '%s' (notifying %zu)", - reflect(level), - reflect(new_level), - changed::list.size() - }; - - _level = new_level; - - // This latch is used to block this call when setting the level - // from an ircd::ctx. If the level is set from the main stack then - // the caller will have to do synchronization themselves. - ctx::latch latch - { - bool(ctx::current) // latch has count of 1 if we're on an ircd::ctx - }; - - // This function will notify the user of the change to IRCd. When there - // are listeners, function is posted to the io_context ensuring THERE IS - // NO CONTINUATION ON THIS STACK by the user. - const auto call_users{[new_level, &latch, latching(!latch.is_ready())] - { - assert(new_level == run::level); - - log::notice + if(!ctx::current && level != chadburn) + throw panic { - "IRCd %s", reflect(new_level) + "Transition '%s' -> '%s' already in progress; cannot wait without ctx", + reflect(chadburn), + reflect(level), }; - log::flush(); - changed::dock.notify_all(); - for(const auto &handler : changed::list) try - { - (*handler)(new_level); - } - catch(const std::exception &e) - { - log::critical - { - "Runlevel change to %s handler(%p) :%s", - reflect(new_level), - handler, - e.what() - }; - } + // Wait for + changed::dock.wait([] + { + return level == chadburn; + }); - if(latching) - latch.count_down(); + _level = new_level; + const unwind chadburn_{[] + { + _chadburn = level; }}; - static ios::descriptor descriptor + changed::dock.notify_all(); + log::debug { - "ircd::run::set" + "IRCd level transition from '%s' to '%s' (dock:%zu callbacks:%zu)", + reflect(chadburn), + reflect(level), + changed::dock.size(), + changed::list.size(), }; - if(changed::list.size() && ctx::current) - ircd::post(descriptor, call_users); - else - call_users(); + log::notice + { + "IRCd %s", reflect(level) + }; - if(ctx::current) - latch.wait(); + log::flush(); + for(const auto &handler : changed::list) try + { + handler->function(new_level); + } + catch(const std::exception &e) + { + switch(level) + { + case level::IDLE: throw; + default: break; + } + + log::critical + { + "Runlevel change to %s handler(%p) :%s", + reflect(new_level), + handler, + e.what() + }; + } return true; } @@ -524,6 +477,7 @@ ircd::run::reflect(const enum run::level &level) case level::HALT: return "HALT"; case level::READY: return "READY"; case level::START: return "START"; + case level::IDLE: return "IDLE"; case level::RUN: return "RUN"; case level::QUIT: return "QUIT"; case level::FAULT: return "FAULT"; diff --git a/matrix/matrix.cc b/matrix/matrix.cc index 39f13756d..13b605d7e 100644 --- a/matrix/matrix.cc +++ b/matrix/matrix.cc @@ -190,7 +190,7 @@ void ircd::m::on_load() try { - assert(ircd::run::level == run::level::START); + assert(ircd::run::level == run::level::IDLE); //reload_conf(); _fetch = std::make_unique(); //_modules = std::make_unique();