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:
parent
dbb3c55db5
commit
e7c66d86d9
4 changed files with 118 additions and 160 deletions
|
@ -36,7 +36,7 @@ bool write_avoid;
|
|||
bool soft_assert;
|
||||
bool nomatrix;
|
||||
const char *execute;
|
||||
std::array<bool, 6> smoketest;
|
||||
std::array<bool, 7> 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.
|
||||
|
|
|
@ -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<ircd::run::changed>
|
||||
,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;
|
||||
|
||||
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))
|
||||
{}
|
||||
|
|
186
ircd/ircd.cc
186
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<ircd::run::changed>::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";
|
||||
|
|
|
@ -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<fetch::init>();
|
||||
//_modules = std::make_unique<init::modules>();
|
||||
|
|
Loading…
Reference in a new issue