diff --git a/include/ircd/ctx/ctx.h b/include/ircd/ctx/ctx.h index 829be48aa..665820212 100644 --- a/include/ircd/ctx/ctx.h +++ b/include/ircd/ctx/ctx.h @@ -63,6 +63,7 @@ namespace ircd::ctx { inline namespace this_ctx /// Interface to the currently running context { struct critical_assertion; // Assert no yielding for a section + struct exception_handler; // Must be present to yield in a handler /// Points to the currently running context or null for main stack (do not modify) extern __thread struct ctx *current; @@ -147,6 +148,27 @@ class ircd::ctx::this_ctx::critical_assertion ~critical_assertion() noexcept; }; +/// An instance of exception_handler must be present to allow a context +/// switch inside a catch block. This is due to ABI limitations that stack +/// exceptions with thread-local assumptions and don't expect catch blocks +/// on the same thread to interleave when we switch the stack. +/// +/// We first increment the refcount for the caught exception so it remains +/// intuitively accessible for the rest of the catch block. Then the presence +/// of this object makes the ABI believe the catch block has ended. +/// +/// The exception cannot then be rethrown. DO NOT RETHROW THE EXCEPTION. +/// +struct ircd::ctx::this_ctx::exception_handler +:std::exception_ptr +{ + exception_handler() noexcept; + exception_handler(exception_handler &&) = delete; + exception_handler(const exception_handler &) = delete; + exception_handler &operator=(exception_handler &&) = delete; + exception_handler &operator=(const exception_handler &) = delete; +}; + /// This overload matches ::sleep() and acts as a drop-in for ircd contexts. /// interruption point. inline void diff --git a/ircd/ctx.cc b/ircd/ctx.cc index f62d74828..4f645b0b7 100644 --- a/ircd/ctx.cc +++ b/ircd/ctx.cc @@ -9,6 +9,7 @@ // full license for this software is available in the LICENSE file. #include #include /// Internal context implementation @@ -480,6 +481,20 @@ ircd::ctx::id(const ctx &ctx) return ctx.id; } +// +// exception_handler +// + +ircd::ctx::this_ctx::exception_handler::exception_handler() +noexcept +:std::exception_ptr{std::current_exception()} +{ + assert(bool(*this)); + assert(!std::uncaught_exceptions()); + __cxa_end_catch(); + assert(!std::current_exception()); +} + /////////////////////////////////////////////////////////////////////////////// // // ctx/continuation.h @@ -519,6 +534,9 @@ ircd::ctx::continuation::continuation(ctx *const &self) assert(self != nullptr); assert(self->notes <= 1); + // Note: Construct an instance of ctx::exception_handler to enable yielding + // in your catch block. + // // GNU cxxabi uses a singly-linked forward list (aka the 'exception // stack') for pending exception activities. Due to this limitation we // cannot interleave _cxa_begin_catch() and __cxa_end_catch() by yielding