ircd: Simplify the async main init fiasco w/ continuation callback.

This commit is contained in:
Jason Volk 2020-12-18 16:37:05 -08:00
parent 2e7ede7242
commit 9655a6311f
6 changed files with 87 additions and 132 deletions

View File

@ -39,7 +39,7 @@ bool norun;
bool read_only;
bool write_avoid;
bool slave;
std::array<bool, 8> smoketest;
std::array<bool, 6> 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<ircd::m::homeserver> 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)
{

View File

@ -129,6 +129,18 @@ namespace ircd
extern conf::item<bool> write_avoid; // implies maintenance
extern conf::item<bool> read_only; // implies write_avoid
extern conf::item<bool> 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<user_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

View File

@ -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

View File

@ -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)
{

View File

@ -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";
}

View File

@ -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)
{