ircd: Simplify the async main init fiasco w/ continuation callback.
This commit is contained in:
parent
2e7ede7242
commit
9655a6311f
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
77
ircd/ircd.cc
77
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)
|
||||
{
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue