diff --git a/construct/construct.cc b/construct/construct.cc index 7a7e7c38a..55057fc8b 100644 --- a/construct/construct.cc +++ b/construct/construct.cc @@ -39,7 +39,7 @@ bool norun; bool read_only; bool write_avoid; bool slave; -std::array smoketest; +std::array smoketest; bool soft_assert; bool nomatrix; bool matrix {true}; // matrix server by default. @@ -202,25 +202,19 @@ noexcept try } }; - // 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 - // different contexts. - ircd::ctx::latch start(2), quit(2); - std::exception_ptr eptr; - // Setup the matrix homeserver application. This will be executed on an // ircd::context (dedicated stack). We construct several objects on the // stack which are the basis for our matrix homeserver. When the stack // unwinds, the homeserver will go out of service. - const auto homeserver{[&origin, &server_name, &start, &quit, &eptr] + const auto homeserver{[&origin, &server_name] + (const ircd::main_continuation &run) { - try - { - // 5 Load the matrix library dynamic shared object - ircd::matrix matrix; + assert(ircd::run::level == ircd::run::level::START); - // 6 Run a primary homeserver based on the program options given. + // Load the matrix library dynamic shared object + ircd::matrix matrix; try + { + // Setup a primary homeserver based on the program options given. struct ircd::m::homeserver::opts opts; opts.origin = origin; opts.server_name = server_name; @@ -229,86 +223,29 @@ noexcept try opts.autoapps = !noautoapps; const ircd::custom_ptr homeserver { - // 6.1 matrix.init(&opts), [&matrix] (ircd::m::homeserver *const homeserver) { - // 11.1 matrix.fini(homeserver); } }; - // 7 Notify the loader the homeserver is ready for service. - start.count_down_and_wait(); - - // 9 Yield until the loader notifies us; this stack will then unwind. - quit.count_down_and_wait(); + // Run the homeserver (call main()'s continuation). This function + // returns (or potentially throws) to unload/quit. + run(); } - catch(...) + catch(const std::exception &e) { - // ctx::exception_handler allows us to yield an ircd::ctx (stack - // switch) inside a catch block, which we'll need in this scope. - const ircd::ctx::exception_handler eh; - - // Copy a reference to the current exception which was saved by eh. - // Note it is not safe to call std::current_exception() here. - eptr = eh; - - // 7' Notify the loader the homeserver failed to start - start.count_down_and_wait(); - - // 9' Yield until the loader notifies us; this stack will then unwind. - quit.count_down_and_wait(); - } - - // 11 - }}; - - // This object registers a callback for a specific event in libircd; the - // closure is called from the main context (#1) running ircd::main(). - // after libircd is ready for service in runlevel LOAD but before entering - // 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 ircd::run::changed loader - { - [&homeserver, &start, &quit, &eptr](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(level == ircd::run::level::LOAD && !context && !nomatrix) + // Rethrow as ircd::error because we're about to unload the module + // and all m:: type exceptions won't exist anymore... + throw ircd::error { - using namespace ircd::util; - - // 3 Launch the homeserver context (asynchronous). - context = { "matrix", 1_MiB, ircd::context::POST, homeserver }; - - // 4 Yield until the homeserver function notifies `start`; waiting - // here prevents ircd::main() from entering runlevel RUN. - start.count_down_and_wait(); - - // 8 Check if error on start; rethrowing that here propagates - // to ircd::main() and triggers a runlevel QUIT sequence. - if(!!eptr) - std::rethrow_exception(eptr); - - // 8.1 - return; - } - - if(level != ircd::run::level::UNLOAD || !context) - return; - - // 10 Notify the waiting homeserver context to quit; this will - // start shutting down the homeserver. - quit.count_down_and_wait(); - - // 12 Wait for the homeserver context to finish before we return. - context.join(); + "%s", e.what() + }; } - }; + + assert(ircd::run::level == ircd::run::level::QUIT); + }}; // This is the sole io_context for Construct, and the ios.run() below is the // the only place where the program actually blocks. @@ -325,7 +262,7 @@ noexcept try // 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.get_executor()); + ircd::init(ios.get_executor(), homeserver); // 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 @@ -343,9 +280,9 @@ noexcept try if(norun) return EXIT_SUCCESS; - // 1 // Execution. - // Blocks until a clean exit from a quit() or an exception comes out of it. + // Loops until a clean exit from a quit() or an exception comes out of it. + // Alternatively, ios.run() could be otherwise used here. size_t epoch{0}; for(; !ios.stopped(); ++epoch) { diff --git a/include/ircd/ircd.h b/include/ircd/ircd.h index 9dfc9e67a..035a74c93 100644 --- a/include/ircd/ircd.h +++ b/include/ircd/ircd.h @@ -129,6 +129,18 @@ namespace ircd extern conf::item write_avoid; // implies maintenance extern conf::item read_only; // implies write_avoid extern conf::item defaults; +} + +namespace ircd +{ + /// Prototype of the continuation supplied to the user's main function. + using main_continuation = void (*)(); + + /// Prototype of the user's main function. + using user_function = void (main_continuation); + + /// User's main function + using user_main = std::function; // Informational seconds uptime(); @@ -136,7 +148,7 @@ namespace ircd // Control panel void cont() noexcept; bool quit() noexcept; - void init(boost::asio::executor &&); + void init(boost::asio::executor &&, user_main); } #endif // HAVE_IRCD_IRCD_H diff --git a/include/ircd/run.h b/include/ircd/run.h index 99c87ada4..014b868d6 100644 --- a/include/ircd/run.h +++ b/include/ircd/run.h @@ -66,13 +66,11 @@ enum class ircd::run::level :int { FAULT = -1, ///< Unrecoverable fault. - HALT = 0, ///< x <-- IRCd Powered off. + HALT = 0, ///< X <-- IRCd Powered off. READY = 1, ///< | | Ready for user to run ios event loop. START = 2, ///< | | Starting internal subsystems. - LOAD = 3, ///< | | Load user application. - RUN = 4, ///< O | IRCd in service. - QUIT = 5, ///< | | Clean shutdown starting. - UNLOAD = 6, ///< --> ^ Unload user application. + RUN = 3, ///< O | IRCd in service. + QUIT = 4, ///< >---^ Clean shutdown starting. }; /// An instance of this class registers itself to be called back when diff --git a/ircd/ircd.cc b/ircd/ircd.cc index 57455123f..adf1fe99b 100644 --- a/ircd/ircd.cc +++ b/ircd/ircd.cc @@ -17,7 +17,7 @@ namespace ircd // Main function. This frame anchors the initialization and destruction of // all non-static assets provided by the library. - static void main() noexcept; + static void main(user_main) noexcept; } // internal interface to ircd::run (see ircd/run.h, ircd/run.cc) @@ -174,7 +174,8 @@ ircd::main_context; /// /// init() can only be called from a run::level::HALT state void -ircd::init(boost::asio::executor &&executor) +ircd::init(boost::asio::executor &&executor, + user_main user) try { // This function must only be called from a HALT state. @@ -211,7 +212,10 @@ try // context main_context { - "main", 256_KiB, &ircd::main, context::POST | context::SLICE_EXEMPT + "main", + 512_KiB, + std::bind(&ircd::main, std::move(user)), + context::POST | context::SLICE_EXEMPT }; // The default behavior for ircd::context is to join the ctx on dtor. We @@ -266,7 +270,6 @@ noexcept } case run::level::START: - case run::level::LOAD: { ctx::terminate(*main_context); main_context = nullptr; @@ -280,7 +283,6 @@ noexcept return true; } - case run::level::UNLOAD: case run::level::QUIT: case run::level::HALT: case run::level::FAULT: @@ -309,7 +311,6 @@ noexcept switch(run::level) { - case run::level::LOAD: case run::level::RUN: break; @@ -339,22 +340,24 @@ noexcept /// io_context from running more jobs. /// void -ircd::main() +ircd::main(user_main user) noexcept try { - // When this function completes without exception, subsystems are done shutting down and IRCd - // transitions to HALT. + // When this function completes without exception, subsystems are done + // shutting down and IRCd transitions to HALT. const unwind_defer halted{[] { run::set(run::level::HALT); }}; + // We block interruption/termination of the main context by default; + // this covers most of the functionality performed by this function and + // its callees. This provides consistent and complete runlevel transitions. + const ctx::uninterruptible::nothrow disable_interruption {true}; + // When this function is entered IRCd will transition to START indicating // that subsystems are initializing. - { - const ctx::uninterruptible ui; - run::set(run::level::START); - } + run::set(run::level::START); // These objects are the init()'s and fini()'s for each subsystem. // Appearing here ties their life to the main context. Initialization can @@ -372,31 +375,39 @@ noexcept try server::init _server_; // Server related js::init _js_; // SpiderMonkey - // Transition to the QUIT and UNLOAD states on unwind. - const unwind quit{[] + // Continuation passed to the user's main function. + const auto continuation{[] { - const ctx::uninterruptible::nothrow ui; - ircd::run::set(run::level::QUIT); - ircd::run::set(run::level::UNLOAD); + // Transition to the QUIT state on unwind. + const unwind quit{[] + { + const ctx::uninterruptible::nothrow disable_interruption {true}; + ircd::run::set(run::level::QUIT); + }}; + + // Block interruptions again for runlevel transitions. + const ctx::uninterruptible disable_interruption {true}; + + // IRCd will now transition to the RUN state indicating full functionality. + run::set(run::level::RUN); + + // Allow interrupts while running so we can be notified to quit. + const ctx::uninterruptible reenable_interruption {false}; + + // wait() 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. + ctx::wait(); }}; - // IRCd will now transition to the LOAD state allowing library user's to - // load their applications using the run::changed callback. - { - const ctx::uninterruptible ui; - run::set(run::level::LOAD); - } + if(!user) + return continuation(); - // IRCd will now transition to the RUN state indicating full functionality. - { - const ctx::uninterruptible ui; - run::set(run::level::RUN); - } + // Allow interrupts again by default for the duration of the user. + const ctx::uninterruptible reenable_interruption {false}; - // 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. - ctx::wait(); + // Call user. + user(continuation); } catch(const std::exception &e) { diff --git a/ircd/run.cc b/ircd/run.cc index e238796bb..495744488 100644 --- a/ircd/run.cc +++ b/ircd/run.cc @@ -142,8 +142,7 @@ try { case level::HALT: break; case level::QUIT: break; - case level::UNLOAD: break; - case level::LOAD: throw; + case level::START: throw; default: throw; } @@ -177,8 +176,8 @@ catch(const std::exception &e) { switch(new_level) { - case level::LOAD: throw; - default: break; + case level::START: throw; + default: break; } log::critical @@ -199,10 +198,8 @@ 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::LOAD: return "LOAD"; case level::RUN: return "RUN"; case level::QUIT: return "QUIT"; - case level::UNLOAD: return "UNLOAD"; case level::FAULT: return "FAULT"; } diff --git a/matrix/matrix.cc b/matrix/matrix.cc index 7cdc6a77b..751b23c7f 100644 --- a/matrix/matrix.cc +++ b/matrix/matrix.cc @@ -196,7 +196,7 @@ void ircd::m::on_load() try { - assert(ircd::run::level == run::level::LOAD); + assert(ircd::run::level == run::level::START); } catch(const m::error &e) {