diff --git a/include/ircd/ctx/ctx.h b/include/ircd/ctx/ctx.h index 665820212..1831215f3 100644 --- a/include/ircd/ctx/ctx.h +++ b/include/ircd/ctx/ctx.h @@ -59,42 +59,7 @@ namespace ircd::ctx void yield(ctx &); // Direct context switch to arg } -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; - - ctx &cur(); ///< Assumptional reference to *current - - const uint64_t &id(); // Unique ID for cur ctx - string_view name(); // Optional label for cur ctx - - void wait(); // Returns when context is woken up. - void yield(); // Allow other contexts to run before returning. - - void interruption_point(); // throws interrupted if interruption_requested() - bool interruption_requested(); // interruption(cur()) - - // Return remaining time if notified; or <= 0 if not, and timeout thrown on throw overloads - microseconds wait(const microseconds &, const std::nothrow_t &); - template nothrow_overload wait(const duration &); - template throw_overload wait(const duration &); - - // Returns false if notified; true if time point reached, timeout thrown on throw_overloads - bool wait_until(const time_point &tp, const std::nothrow_t &); - template nothrow_overload wait_until(const time_point &tp); - template throw_overload wait_until(const time_point &tp); - - // Ignores notes. Throws if interrupted. - void sleep_until(const time_point &tp); - template void sleep(const duration &); - void sleep(const int &secs); -}} - +#include "this_ctx.h" #include "context.h" #include "prof.h" #include "list.h" @@ -129,120 +94,3 @@ namespace ircd using ctx::critical_assertion; } - -/// An instance of critical_assertion detects an attempt to context switch. -/// -/// For when the developer specifically does not want any yielding in a -/// section or anywhere up the stack from it. This device does not prevent -/// a switch and may carry no meaning outside of debug-mode compilation. It is -/// good practice to use this device even when it appears obvious the -/// section's callgraph has no chance of yielding: code changes, and everything -/// up the graph can change without taking notice of your section. -/// -class ircd::ctx::this_ctx::critical_assertion -{ - bool theirs; - - public: - 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 -ircd::ctx::this_ctx::sleep(const int &secs) -{ - sleep(seconds(secs)); -} - -/// Yield the context for a period of time and ignore notifications. sleep() -/// is like wait() but it only returns after the timeout and not because of a -/// note. -/// interruption point. -template -void -ircd::ctx::this_ctx::sleep(const duration &d) -{ - sleep_until(steady_clock::now() + d); -} - -/// Wait for a notification until a point in time. If there is a notification -/// then context continues normally. If there's never a notification then an -/// exception (= timeout) is thrown. -/// interruption point. -template -ircd::throw_overload -ircd::ctx::this_ctx::wait_until(const time_point &tp) -{ - if(wait_until(tp)) - throw E(); -} - -/// Wait for a notification until a point in time. If there is a notification -/// then returns true. If there's never a notification then returns false. -/// interruption point. this is not noexcept. -template -ircd::nothrow_overload -ircd::ctx::this_ctx::wait_until(const time_point &tp) -{ - return wait_until(tp, std::nothrow); -} - -/// Wait for a notification for at most some amount of time. If the duration is -/// reached without a notification then E (= timeout) is thrown. Otherwise, -/// returns the time remaining on the duration. -/// interruption point -template -ircd::throw_overload -ircd::ctx::this_ctx::wait(const duration &d) -{ - const auto ret(wait(d)); - return ret <= duration(0)? throw E() : ret; -} - -/// Wait for a notification for some amount of time. This function returns -/// when a context is notified. It always returns the duration remaining which -/// will be <= 0 to indicate a timeout without notification. -/// interruption point. this is not noexcept. -template -ircd::nothrow_overload -ircd::ctx::this_ctx::wait(const duration &d) -{ - using std::chrono::duration_cast; - - const auto ret(wait(duration_cast(d), std::nothrow)); - return duration_cast(ret); -} - -/// Reference to the currently running context. Call if you expect to be in a -/// context. Otherwise use the ctx::current pointer. -inline ircd::ctx::ctx & -ircd::ctx::cur() -{ - assert(current); - return *current; -} diff --git a/include/ircd/ctx/this_ctx.h b/include/ircd/ctx/this_ctx.h new file mode 100644 index 000000000..6a16fc546 --- /dev/null +++ b/include/ircd/ctx/this_ctx.h @@ -0,0 +1,165 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +#pragma once +#define HAVE_IRCD_CTX_THIS_CTX_H + +namespace ircd::ctx { +/// Interface to the currently running context +inline namespace this_ctx +{ + 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; + struct ctx &cur(); ///< Assumptional reference to *current + + const uint64_t &id(); // Unique ID for cur ctx + string_view name(); // Optional label for cur ctx + + void wait(); // Returns when context is woken up. + void yield(); // Allow other contexts to run before returning. + + void interruption_point(); // throws interrupted if interruption_requested() + bool interruption_requested(); // interruption(cur()) + + // Return remaining time if notified; or <= 0 if not, and timeout thrown on throw overloads + microseconds wait(const microseconds &, const std::nothrow_t &); + template nothrow_overload wait(const duration &); + template throw_overload wait(const duration &); + + // Returns false if notified; true if time point reached, timeout thrown on throw_overloads + bool wait_until(const time_point &tp, const std::nothrow_t &); + template nothrow_overload wait_until(const time_point &tp); + template throw_overload wait_until(const time_point &tp); + + // Ignores notes. Throws if interrupted. + void sleep_until(const time_point &tp); + template void sleep(const duration &); + void sleep(const int &secs); +}} + +/// An instance of critical_assertion detects an attempt to context switch. +/// +/// For when the developer specifically does not want any yielding in a +/// section or anywhere up the stack from it. This device does not prevent +/// a switch and may carry no meaning outside of debug-mode compilation. It is +/// good practice to use this device even when it appears obvious the +/// section's callgraph has no chance of yielding: code changes, and everything +/// up the graph can change without taking notice of your section. +/// +class ircd::ctx::this_ctx::critical_assertion +{ + bool theirs; + + public: + 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 +ircd::ctx::this_ctx::sleep(const int &secs) +{ + sleep(seconds(secs)); +} + +/// Yield the context for a period of time and ignore notifications. sleep() +/// is like wait() but it only returns after the timeout and not because of a +/// note. +/// interruption point. +template +void +ircd::ctx::this_ctx::sleep(const duration &d) +{ + sleep_until(steady_clock::now() + d); +} + +/// Wait for a notification until a point in time. If there is a notification +/// then context continues normally. If there's never a notification then an +/// exception (= timeout) is thrown. +/// interruption point. +template +ircd::throw_overload +ircd::ctx::this_ctx::wait_until(const time_point &tp) +{ + if(wait_until(tp)) + throw E(); +} + +/// Wait for a notification until a point in time. If there is a notification +/// then returns true. If there's never a notification then returns false. +/// interruption point. this is not noexcept. +template +ircd::nothrow_overload +ircd::ctx::this_ctx::wait_until(const time_point &tp) +{ + return wait_until(tp, std::nothrow); +} + +/// Wait for a notification for at most some amount of time. If the duration is +/// reached without a notification then E (= timeout) is thrown. Otherwise, +/// returns the time remaining on the duration. +/// interruption point +template +ircd::throw_overload +ircd::ctx::this_ctx::wait(const duration &d) +{ + const auto ret(wait(d)); + return ret <= duration(0)? throw E() : ret; +} + +/// Wait for a notification for some amount of time. This function returns +/// when a context is notified. It always returns the duration remaining which +/// will be <= 0 to indicate a timeout without notification. +/// interruption point. this is not noexcept. +template +ircd::nothrow_overload +ircd::ctx::this_ctx::wait(const duration &d) +{ + using std::chrono::duration_cast; + + const auto ret(wait(duration_cast(d), std::nothrow)); + return duration_cast(ret); +} + +/// Reference to the currently running context. Call if you expect to be in a +/// context. Otherwise use the ctx::current pointer. +inline ircd::ctx::ctx & +ircd::ctx::this_ctx::cur() +{ + assert(current); + return *current; +} diff --git a/ircd/ctx.cc b/ircd/ctx.cc index 4f645b0b7..9ec41945f 100644 --- a/ircd/ctx.cc +++ b/ircd/ctx.cc @@ -262,115 +262,6 @@ ircd::ctx::ctx::interruption_point(std::nothrow_t) // ctx/ctx.h // -__thread ircd::ctx::ctx *ircd::ctx::current; - -/// Yield the currently running context until `time_point` ignoring notes -void -ircd::ctx::this_ctx::sleep_until(const std::chrono::steady_clock::time_point &tp) -{ - while(!wait_until(tp, std::nothrow)); -} - -/// Yield the currently running context until notified or `time_point`. -/// -/// Returns true if this function returned because `time_point` was hit or -/// false because this context was notified. -bool -ircd::ctx::this_ctx::wait_until(const std::chrono::steady_clock::time_point &tp, - const std::nothrow_t &) -{ - auto &c(cur()); - c.alarm.expires_at(tp); - c.wait(); // now you're yielding with portals - - return std::chrono::steady_clock::now() >= tp; -} - -/// Yield the currently running context for `duration` or until notified. -/// -/// Returns the duration remaining if notified, or <= 0 if suspended for -/// the full duration, or unchanged if no suspend ever took place. -std::chrono::microseconds -ircd::ctx::this_ctx::wait(const std::chrono::microseconds &duration, - const std::nothrow_t &) -{ - auto &c(cur()); - c.alarm.expires_from_now(duration); - c.wait(); // now you're yielding with portals - const auto ret(c.alarm.expires_from_now()); - - // return remaining duration. - // this is > 0 if notified - // this is unchanged if a note prevented any wait at all - return std::chrono::duration_cast(ret); -} - -/// Yield the currently running context until notified. -void -ircd::ctx::this_ctx::wait() -{ - auto &c(cur()); - c.alarm.expires_at(std::chrono::steady_clock::time_point::max()); - c.wait(); // now you're yielding with portals -} - -/// Post the currently running context to the event queue and then suspend to -/// allow other contexts in the queue to run. -/// -/// Until we have our own queue the ios queue makes no guarantees if the queue -/// is FIFO or LIFO etc :-/ It is generally bad practice to use this function, -/// as one should make the effort to devise a specific cooperative strategy for -/// how context switching occurs rather than this coarse/brute technique. -void -ircd::ctx::this_ctx::yield() -{ - bool done(false); - const auto restore([&done, &me(cur())] - { - done = true; - notify(me); - }); - - // All spurious notifications are ignored until `done` - ios->post(restore); do - { - wait(); - } - while(!done); -} - -/// Throws interrupted if the currently running context was interrupted -/// and clears the interrupt flag. -void -ircd::ctx::this_ctx::interruption_point() -{ - return cur().interruption_point(); -} - -/// Returns true if the currently running context was interrupted and clears -/// the interrupt flag. -bool -ircd::ctx::this_ctx::interruption_requested() -{ - return interruption(cur()); -} - -/// Returns unique ID of currently running context -const uint64_t & -ircd::ctx::this_ctx::id() -{ - static const uint64_t zero{0}; - return current? id(cur()) : zero; -} - -/// Returns optional developer-given name for currently running context -ircd::string_view -ircd::ctx::this_ctx::name() -{ - static const string_view nada{"*"}; - return current? name(cur()) : nada; -} - /// Yield to context `ctx`. /// /// @@ -481,6 +372,121 @@ ircd::ctx::id(const ctx &ctx) return ctx.id; } +/////////////////////////////////////////////////////////////////////////////// +// +// ctx/this_ctx.h +// + +__thread ircd::ctx::ctx * +ircd::ctx::this_ctx::current; + +/// Yield the currently running context until `time_point` ignoring notes +void +ircd::ctx::this_ctx::sleep_until(const std::chrono::steady_clock::time_point &tp) +{ + while(!wait_until(tp, std::nothrow)); +} + +/// Yield the currently running context until notified or `time_point`. +/// +/// Returns true if this function returned because `time_point` was hit or +/// false because this context was notified. +bool +ircd::ctx::this_ctx::wait_until(const std::chrono::steady_clock::time_point &tp, + const std::nothrow_t &) +{ + auto &c(cur()); + c.alarm.expires_at(tp); + c.wait(); // now you're yielding with portals + + return std::chrono::steady_clock::now() >= tp; +} + +/// Yield the currently running context for `duration` or until notified. +/// +/// Returns the duration remaining if notified, or <= 0 if suspended for +/// the full duration, or unchanged if no suspend ever took place. +std::chrono::microseconds +ircd::ctx::this_ctx::wait(const std::chrono::microseconds &duration, + const std::nothrow_t &) +{ + auto &c(cur()); + c.alarm.expires_from_now(duration); + c.wait(); // now you're yielding with portals + const auto ret(c.alarm.expires_from_now()); + + // return remaining duration. + // this is > 0 if notified + // this is unchanged if a note prevented any wait at all + return std::chrono::duration_cast(ret); +} + +/// Yield the currently running context until notified. +void +ircd::ctx::this_ctx::wait() +{ + auto &c(cur()); + c.alarm.expires_at(std::chrono::steady_clock::time_point::max()); + c.wait(); // now you're yielding with portals +} + +/// Post the currently running context to the event queue and then suspend to +/// allow other contexts in the queue to run. +/// +/// Until we have our own queue the ios queue makes no guarantees if the queue +/// is FIFO or LIFO etc :-/ It is generally bad practice to use this function, +/// as one should make the effort to devise a specific cooperative strategy for +/// how context switching occurs rather than this coarse/brute technique. +void +ircd::ctx::this_ctx::yield() +{ + bool done(false); + const auto restore([&done, &me(cur())] + { + done = true; + notify(me); + }); + + // All spurious notifications are ignored until `done` + ios->post(restore); do + { + wait(); + } + while(!done); +} + +/// Throws interrupted if the currently running context was interrupted +/// and clears the interrupt flag. +void +ircd::ctx::this_ctx::interruption_point() +{ + return cur().interruption_point(); +} + +/// Returns true if the currently running context was interrupted and clears +/// the interrupt flag. +bool +ircd::ctx::this_ctx::interruption_requested() +{ + return interruption(cur()); +} + +/// Returns unique ID of currently running context +const uint64_t & +ircd::ctx::this_ctx::id() +{ + static const uint64_t zero{0}; + return current? id(cur()) : zero; +} + +/// Returns optional developer-given name for currently running context +ircd::string_view +ircd::ctx::this_ctx::name() +{ + static const string_view nada{"*"}; + return current? name(cur()) : nada; +} + // // exception_handler // @@ -495,13 +501,8 @@ noexcept assert(!std::current_exception()); } -/////////////////////////////////////////////////////////////////////////////// // -// ctx/continuation.h -// - -// -// Support for critical_assertion (ctx.h) +// critical_assertion // namespace ircd::ctx @@ -522,6 +523,11 @@ noexcept critical_asserted = theirs; } +/////////////////////////////////////////////////////////////////////////////// +// +// ctx/continuation.h +// + // // continuation //