0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-11-12 04:51:08 +01:00

ircd::run: Add another runlevel; eliminate main callback; simplify.

This commit is contained in:
Jason Volk 2019-10-02 16:49:30 -07:00
parent dbb3c55db5
commit e7c66d86d9
4 changed files with 118 additions and 160 deletions

View file

@ -36,7 +36,7 @@ bool write_avoid;
bool soft_assert; bool soft_assert;
bool nomatrix; bool nomatrix;
const char *execute; const char *execute;
std::array<bool, 6> smoketest; std::array<bool, 7> smoketest;
lgetopt opts[] 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 // Setup synchronization primitives on this stack for starting and stopping
// the application (matrix homeserver). Note this stack cannot actually use // the application (matrix homeserver). Note this stack cannot actually use
// these; they'll be used to synchronize the closures below running in // 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 // runlevel RUN. It is called again immediately after entering runlevel
// QUIT, but before any functionality of libircd destructs. This cues us // QUIT, but before any functionality of libircd destructs. This cues us
// to start and stop the homeserver. // 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; static ircd::context context;
// 2 This branch is taken the first time this function is called, // 2 This branch is taken the first time this function is called,
// and not taken the second time. // and not taken the second time.
if(!context) if(level == ircd::run::level::IDLE && !context)
{ {
// 3 Launch the homeserver context (asynchronous). // 3 Launch the homeserver context (asynchronous).
context = { "matrix", homeserver }; context = { "matrix", homeserver };
@ -259,6 +242,9 @@ noexcept try
return; return;
} }
if(level != ircd::run::level::QUIT)
return;
// 11 // 11
// Notify the waiting homeserver context to quit; this will // Notify the waiting homeserver context to quit; this will
// start shutting down the homeserver. // 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 // 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 // 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. // not actually take effect until it's processed in the ios.run() below.

View file

@ -30,40 +30,31 @@ namespace ircd::run
// Access to the current runlevel indicator. // Access to the current runlevel indicator.
extern const enum level &level; extern const enum level &level;
// Another callback list (albeit simply using util::callbacks) which is // Access to the desired runlevel. When this differs from the current
// called during level::START on the main context after all subsystems have // level, a command to change the runlevel has been given but not all
// constructed, and again in level::QUIT before all subsystems have // tasks have completed at the current runlevel.
// destructed. The purpose here is to allow extensions to the START and extern const enum level &chadburn;
// 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;
} }
/// An instance of this class registers itself to be called back when /// 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 /// Note: Its destructor will access a list in libircd; after a callback
/// for a HALT do not unload libircd.so until destructing this object. /// 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 struct ircd::run::changed
:instance_list<ircd::run::changed> :instance_list<ircd::run::changed>
,std::function<void (const enum level &)>
{ {
using handler = std::function<void (const enum level &)>; using handler = std::function<void (const enum level &)>;
// 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; static ctx::dock dock;
handler function;
/// The handler function will be called back for any run::level change while /// The handler function will be called back for any run::level change while
/// this instance remains in scope. /// this instance remains in scope.
changed(handler function); changed(handler function) noexcept;
~changed() noexcept;
}; };
/// The run::level allows all observers to know the coarse state of IRCd and to /// 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 /// * START indicates the daemon is executing its startup procedures. Leaving
/// the START state occurs internally when there is success or a fatal error. /// 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 /// * RUN is the service mode. Full client and application functionality exists
/// in this mode. Leaving the RUN mode is done with ircd::quit(); /// 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 enum class ircd::run::level
:int :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. READY = 1, ///< | | Ready for user to run ios event loop.
START = 2, ///< | | Starting up subsystems for service. START = 2, ///< | | Starting internal subsystems.
RUN = 3, ///< O | IRCd in service. IDLE = 3, ///< | | Ready for user to load application.
QUIT = 4, ///< --> ^ Clean shutdown in progress. RUN = 4, ///< O | IRCd in service.
FAULT = -1, ///< Unrecoverable fault. QUIT = 5, ///< --> ^ Clean shutdown in progress.
}; };
inline
ircd::run::changed::changed(handler function)
noexcept
:function(std::move(function))
{}

View file

@ -8,6 +8,13 @@
// copyright notice and this permission notice is present in all copies. The // copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file. // 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 namespace ircd
{ {
// Fundamental context #1; all subsystems live as objects on this stack. // Fundamental context #1; all subsystems live as objects on this stack.
@ -20,22 +27,6 @@ namespace ircd
static void main() noexcept; 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) decltype(ircd::version_api)
ircd::version_api ircd::version_api
{ {
@ -191,11 +182,11 @@ noexcept
return true; return true;
} }
case run::level::IDLE:
case run::level::START: case run::level::START:
{ {
ctx::terminate(*main_context); ctx::terminate(*main_context);
main_context = nullptr; main_context = nullptr;
ircd::run::set(run::level::QUIT);
return true; return true;
} }
@ -242,6 +233,7 @@ noexcept
case run::level::QUIT: case run::level::QUIT:
return; return;
case run::level::IDLE:
case run::level::RUN: case run::level::RUN:
break; break;
} }
@ -301,10 +293,6 @@ noexcept try
server::init _server_; // Server related server::init _server_; // Server related
client::init _client_; // Client related client::init _client_; // Client related
js::init _js_; // SpiderMonkey 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. // Transition to the QUIT state on unwind.
const unwind quit{[] const unwind quit{[]
@ -312,13 +300,17 @@ noexcept try
ircd::run::set(run::level::QUIT); 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. // 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 // Waiting here will hold open this stack with all of the above objects
// living on it. // living on it.
run::changed::dock.wait([] ctx::wait();
{
return !main_context;
});
} }
catch(const http::error &e) // <-- m::error catch(const http::error &e) // <-- m::error
{ {
@ -360,7 +352,7 @@ ircd::uptime()
namespace ircd::run namespace ircd::run
{ {
static enum level _level; static enum level _level, _chadburn;
} }
decltype(ircd::run::level) decltype(ircd::run::level)
@ -369,26 +361,11 @@ ircd::run::level
_level _level
}; };
decltype(ircd::run::main) decltype(ircd::run::chadburn)
ircd::run::main; ircd::run::chadburn
//
// run::main_event
//
ircd::run::main_event::main_event()
{ {
assert(level == level::START); _chadburn
run::main(); };
}
ircd::run::main_event::~main_event()
noexcept
{
assert(level != level::RUN);
assert(level == level::QUIT);
run::main();
}
// //
// run::changed // run::changed
@ -409,23 +386,6 @@ ircd::util::instance_list<ircd::run::changed>::list
decltype(ircd::run::changed::dock) decltype(ircd::run::changed::dock)
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 /// 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 /// 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 /// instead invoke their function on the main stack in their own io_context
@ -434,47 +394,57 @@ bool
ircd::run::set(const enum level &new_level) ircd::run::set(const enum level &new_level)
try try
{ {
if(level == new_level) if(new_level == chadburn)
return false; return false;
log::debug if(!ctx::current && level != chadburn)
throw panic
{ {
"IRCd level transition from '%s' to '%s' (notifying %zu)", "Transition '%s' -> '%s' already in progress; cannot wait without ctx",
reflect(chadburn),
reflect(level), reflect(level),
reflect(new_level),
changed::list.size()
}; };
// Wait for
changed::dock.wait([]
{
return level == chadburn;
});
_level = new_level; _level = new_level;
const unwind chadburn_{[]
// 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 _chadburn = level;
}};
changed::dock.notify_all();
log::debug
{
"IRCd level transition from '%s' to '%s' (dock:%zu callbacks:%zu)",
reflect(chadburn),
reflect(level),
changed::dock.size(),
changed::list.size(),
}; };
// 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 log::notice
{ {
"IRCd %s", reflect(new_level) "IRCd %s", reflect(level)
}; };
log::flush(); log::flush();
changed::dock.notify_all();
for(const auto &handler : changed::list) try for(const auto &handler : changed::list) try
{ {
(*handler)(new_level); handler->function(new_level);
} }
catch(const std::exception &e) catch(const std::exception &e)
{ {
switch(level)
{
case level::IDLE: throw;
default: break;
}
log::critical log::critical
{ {
"Runlevel change to %s handler(%p) :%s", "Runlevel change to %s handler(%p) :%s",
@ -484,23 +454,6 @@ try
}; };
} }
if(latching)
latch.count_down();
}};
static ios::descriptor descriptor
{
"ircd::run::set"
};
if(changed::list.size() && ctx::current)
ircd::post(descriptor, call_users);
else
call_users();
if(ctx::current)
latch.wait();
return true; return true;
} }
catch(const std::exception &e) catch(const std::exception &e)
@ -524,6 +477,7 @@ ircd::run::reflect(const enum run::level &level)
case level::HALT: return "HALT"; case level::HALT: return "HALT";
case level::READY: return "READY"; case level::READY: return "READY";
case level::START: return "START"; case level::START: return "START";
case level::IDLE: return "IDLE";
case level::RUN: return "RUN"; case level::RUN: return "RUN";
case level::QUIT: return "QUIT"; case level::QUIT: return "QUIT";
case level::FAULT: return "FAULT"; case level::FAULT: return "FAULT";

View file

@ -190,7 +190,7 @@ void
ircd::m::on_load() ircd::m::on_load()
try try
{ {
assert(ircd::run::level == run::level::START); assert(ircd::run::level == run::level::IDLE);
//reload_conf(); //reload_conf();
_fetch = std::make_unique<fetch::init>(); _fetch = std::make_unique<fetch::init>();
//_modules = std::make_unique<init::modules>(); //_modules = std::make_unique<init::modules>();