diff --git a/include/ircd/ctx/future.h b/include/ircd/ctx/future.h index b86472ae5..0a0a2b9aa 100644 --- a/include/ircd/ctx/future.h +++ b/include/ircd/ctx/future.h @@ -13,11 +13,13 @@ namespace ircd::ctx { - IRCD_OVERLOAD(use_future) - template class future; template<> class future; + template struct scoped_future; + + IRCD_EXCEPTION(future_error, future_already_retrieved) + IRCD_OVERLOAD(use_future) } template diff --git a/include/ircd/ctx/promise.h b/include/ircd/ctx/promise.h index c8af1a634..dca410c88 100644 --- a/include/ircd/ctx/promise.h +++ b/include/ircd/ctx/promise.h @@ -13,369 +13,170 @@ namespace ircd::ctx { + struct promise_base; template class promise; template<> class promise; - template class future; - template<> class future; - - IRCD_EXCEPTION(ircd::ctx::error, future_error) IRCD_EXCEPTION(future_error, no_state) IRCD_EXCEPTION(future_error, broken_promise) - IRCD_EXCEPTION(future_error, future_already_retrieved) IRCD_EXCEPTION(future_error, promise_already_satisfied) } -template -struct ircd::ctx::promise +/// Abstract base type for ctx::promise. This dedupes most of the promissory +/// functionality with non-template definitions residing in ctx.cc. +/// +/// In this system the promise is lightweight and maintains a pointer to the +/// shared_state object which generally resides within the future instance. +/// If the shared_state object moves or is destroyed the promise's pointer to +/// it must be updated. The shared_state object also has a pointer to the +/// promise; if the promise moves or is destroyed that pointer must be updated +/// as well. This is how the bi-directional relationship is maintained. +/// +/// To further complicate things, this promise maintains a second pointer +/// to another instance of a promise implementing a linked-list semantic. All +/// of these promises are making the same promise to the same shared_state; +/// the list allows for copy semantics which are important for some callback +/// systems (like boost::asio). This solution is far more optimal than +/// allocating the promise in a shared_ptr and refcounting... +struct ircd::ctx::promise_base { - shared_state *st {nullptr}; // Reference to the state resident in future - mutable promise *next {nullptr}; // Promise fwdlist to support copy semantics + static void remove(shared_state_base &, promise_base &); + static void update(promise_base &new_, promise_base &old); + static void append(promise_base &new_, promise_base &old); + + shared_state_base *st {nullptr}; + mutable promise_base *next {nullptr}; + + template const shared_state &state() const; + template shared_state &state(); + const shared_state_base &state() const; + shared_state_base &state(); + + void check_pending() const; + void make_ready(); public: - using value_type = typename shared_state::value_type; - using pointer_type = typename shared_state::pointer_type; - using reference_type = typename shared_state::reference_type; - - const shared_state &state() const { assert(valid()); return *st; } - shared_state &state() { assert(valid()); return *st; } - - bool valid() const { return bool(st); } - bool operator!() const { return !valid(); } - operator bool() const { return valid(); } + bool valid() const; + operator bool() const; + bool operator!() const; void set_exception(std::exception_ptr eptr); + + protected: + promise_base() = default; + promise_base(const promise_base &); + promise_base(promise_base &&) noexcept; + promise_base &operator=(const promise_base &); + promise_base &operator=(promise_base &&) noexcept; + ~promise_base() noexcept; +}; + +/// Value-oriented promise. The user creates an instance of this promise in +/// order to send a value to a future. After creating an instance of this +/// promise the user should construct a future with the matching template type +/// from this. The two will then be linked. +/// +/// Space for the value will reside within the future instance and not the +/// promise instance. When the value is set it will be copied (or movied) there. +/// +/// Full object semantics for this promise are supported; including copy +/// semantics. All copies of a promise are making the same promise thus +/// setting a value or exception for one invalidates all the copies. +/// +/// Instances of this promise can safely destruct at any time. When all copies +/// of a promise destruct without setting a value or exception the future is +/// notified with a broken_promise exception. +template +struct ircd::ctx::promise +:promise_base +{ + using value_type = typename shared_state::value_type; + using pointer_type = typename shared_state::pointer_type; + using reference_type = typename shared_state::reference_type; + + const shared_state &state() const; + shared_state &state(); + + public: void set_value(const T &val); void set_value(T&& val); - promise() = default; - promise(promise &&o) noexcept; - promise(const promise &); - promise &operator=(const promise &) = delete; - promise &operator=(promise &&) noexcept; - ~promise() noexcept; + using promise_base::promise_base; + using promise_base::operator=; }; +/// Valueless promise. The user creates an instance of this promise in +/// order to notify a future of a success or set an exception for failure. +/// +/// See docs for promise for more information. template<> struct ircd::ctx::promise +:promise_base { - shared_state *st {nullptr}; // Reference to the state resident in future - mutable promise *next {nullptr}; // Promise fwdlist to support copy semantics + using value_type = typename shared_state::value_type; + + const shared_state &state() const; + shared_state &state(); public: - using value_type = typename shared_state::value_type; - - const shared_state &state() const { assert(valid()); return *st; } - shared_state &state() { assert(valid()); return *st; } - - bool valid() const { return bool(st); } - bool operator!() const { return !valid(); } - operator bool() const { return valid(); } - - void set_exception(std::exception_ptr eptr); void set_value(); - promise() = default; - promise(promise &&o) noexcept; - promise(const promise &); - promise &operator=(const promise &) = delete; - promise &operator=(promise &&) noexcept; - ~promise() noexcept; + using promise_base::promise_base; + using promise_base::operator=; }; -namespace ircd::ctx -{ - template const shared_state &state(const promise &); - template shared_state &state(promise &); - template size_t refcount(const shared_state &); - template void update(shared_state &s); - template void invalidate(shared_state &); - template void remove(shared_state &, promise &); - template void update(promise &new_, promise &old); - template void append(promise &new_, promise &old); -} - -template -ircd::ctx::promise::promise(promise &&o) -noexcept -:st{std::move(o.st)} -,next{std::move(o.next)} -{ - if(st) - { - update(*this, o); - o.st = nullptr; - } -} - -inline -ircd::ctx::promise::promise(promise &&o) -noexcept -:st{std::move(o.st)} -,next{std::move(o.next)} -{ - if(st) - { - update(*this, o); - o.st = nullptr; - } -} - -template -ircd::ctx::promise::promise(const promise &o) -:st{o.st} -,next{nullptr} -{ - append(*this, const_cast &>(o)); -} - -inline -ircd::ctx::promise::promise(const promise &o) -:st{o.st} -,next{nullptr} -{ - append(*this, const_cast &>(o)); -} - -template -ircd::ctx::promise & -ircd::ctx::promise::operator=(promise &&o) -noexcept -{ - this->~promise(); - st = std::move(o.st); - next = std::move(o.next); - if(!st) - return *this; - - update(*this, o); - o.st = nullptr; - return *this; -} - -inline -ircd::ctx::promise & -ircd::ctx::promise::operator=(promise &&o) -noexcept -{ - this->~promise(); - st = std::move(o.st); - next = std::move(o.next); - if(!st) - return *this; - - update(*this, o); - o.st = nullptr; - return *this; -} - -template -ircd::ctx::promise::~promise() -noexcept -{ - if(!valid()) - return; - - if(refcount(state()) == 1) - set_exception(std::make_exception_ptr(broken_promise())); - else - remove(state(), *this); -} - -inline -ircd::ctx::promise::~promise() -noexcept -{ - if(!valid()) - return; - - if(refcount(state()) == 1) - set_exception(std::make_exception_ptr(broken_promise())); - else - remove(state(), *this); -} +// +// promise +// template void ircd::ctx::promise::set_value(T&& val) { - assert(valid()); - if(unlikely(!is(state(), future_state::PENDING))) - throw promise_already_satisfied{}; - - st->val = std::move(val); - auto *const st{this->st}; - invalidate(*st); - set(*st, future_state::READY); - notify(*st); - assert(!valid()); + check_pending(); + state().val = std::move(val); + make_ready(); } template void ircd::ctx::promise::set_value(const T &val) { - assert(valid()); - if(unlikely(!is(state(), future_state::PENDING))) - throw promise_already_satisfied{}; - - st->val = val; - auto *const st{this->st}; - invalidate(*st); - set(*st, future_state::READY); - notify(*st); - assert(!valid()); -} - -inline void -ircd::ctx::promise::set_value() -{ - assert(valid()); - if(unlikely(!is(state(), future_state::PENDING))) - throw promise_already_satisfied{}; - - auto *const st{this->st}; - invalidate(*st); - set(*st, future_state::READY); - notify(*st); - assert(!valid()); -} - -template -void -ircd::ctx::promise::set_exception(std::exception_ptr eptr) -{ - assert(valid()); - if(unlikely(!is(state(), future_state::PENDING))) - throw promise_already_satisfied{}; - - st->eptr = std::move(eptr); - auto *const st{this->st}; - invalidate(*st); - set(*st, future_state::READY); - notify(*st); - assert(!valid()); -} - -inline void -ircd::ctx::promise::set_exception(std::exception_ptr eptr) -{ - assert(valid()); - if(unlikely(!is(state(), future_state::PENDING))) - throw promise_already_satisfied{}; - - st->eptr = std::move(eptr); - auto *const st{this->st}; - invalidate(*st); - set(*st, future_state::READY); - notify(*st); - assert(!valid()); -} - -template -void -ircd::ctx::append(promise &new_, - promise &old) -{ - if(!old.next) - { - old.next = &new_; - return; - } - - promise *next{old.next}; - for(; next->next; next = next->next); - next->next = &new_; -} - -template -void -ircd::ctx::update(promise &new_, - promise &old) -{ - assert(old.st); - auto &st{*old.st}; - if(!is(st, future_state::PENDING)) - return; - - if(st.p == &old) - { - st.p = &new_; - return; - } - - promise *last{st.p}; - for(promise *next{last->next}; next; last = next, next = last->next) - if(next == &old) - { - last->next = &new_; - break; - } -} - -template -void -ircd::ctx::remove(shared_state &st, - promise &p) -{ - if(!is(st, future_state::PENDING)) - return; - - if(st.p == &p) - { - st.p = p.next; - return; - } - - promise *last{st.p}; - for(promise *next{last->next}; next; last = next, next = last->next) - if(next == &p) - { - last->next = p.next; - break; - } -} - -template -void -ircd::ctx::invalidate(shared_state &st) -{ - if(is(st, future_state::PENDING)) - for(promise *p{st.p}; p; p = p->next) - p->st = nullptr; -} - -template -void -ircd::ctx::update(shared_state &st) -{ - if(is(st, future_state::PENDING)) - for(promise *p{st.p}; p; p = p->next) - p->st = &st; -} - -template -size_t -ircd::ctx::refcount(const shared_state &st) -{ - size_t ret{0}; - if(is(st, future_state::PENDING)) - for(const promise *p{st.p}; p; p = p->next) - ++ret; - - return ret; + check_pending(); + state().val = val; + make_ready(); } template ircd::ctx::shared_state & -ircd::ctx::state(promise &promise) +ircd::ctx::promise::state() { - return promise.state(); + return promise_base::state(); } template const ircd::ctx::shared_state & -ircd::ctx::state(const promise &promise) +ircd::ctx::promise::state() +const { - return promise.state(); + return promise_base::state(); +} + +// +// promise_base +// + +template +ircd::ctx::shared_state & +ircd::ctx::promise_base::state() +{ + return static_cast &>(state()); +} + +template +const ircd::ctx::shared_state & +ircd::ctx::promise_base::state() +const +{ + return static_cast &>(state()); } diff --git a/include/ircd/ctx/shared_state.h b/include/ircd/ctx/shared_state.h index 36cb3e6ef..c53be1e32 100644 --- a/include/ircd/ctx/shared_state.h +++ b/include/ircd/ctx/shared_state.h @@ -13,18 +13,22 @@ namespace ircd::ctx { - struct shared_state_base; - template struct shared_state; - template<> struct shared_state; - template struct promise; - template<> struct promise; enum class future_state :uintptr_t; + struct shared_state_base; + struct promise_base; - template future_state state(const shared_state &); - template bool is(const shared_state &, const future_state &); - template void set(shared_state &, const future_state &); - template<> void set(shared_state &, const future_state &); - template void notify(shared_state &); + template struct shared_state; + template<> struct shared_state; + + IRCD_EXCEPTION(ircd::ctx::error, future_error) + + future_state state(const shared_state_base &); + bool is(const shared_state_base &, const future_state &); + void set(shared_state_base &, const future_state &); + size_t refcount(const shared_state_base &); + void invalidate(shared_state_base &); + void update(shared_state_base &); + void notify(shared_state_base &); } /// Internal state enumeration for the promise / future / related. These can @@ -33,11 +37,11 @@ namespace ircd::ctx enum class ircd::ctx::future_state :uintptr_t { - INVALID = 0, ///< Null. - PENDING, ///< Promise is attached and busy. - READY, ///< Result ready; promise is gone. - OBSERVED, ///< Special case for when_*(); not a state; promise is gone. - RETRIEVED, ///< User retrieved future value; promise is gone. + INVALID = 0, ///< Null. + PENDING = 1, ///< Promise is attached and busy. + READY = 2, ///< Result ready; promise is gone. + OBSERVED = 3, ///< Special case for when_*(); not a state; promise is gone. + RETRIEVED = 4, ///< User retrieved future value; promise is gone. }; /// Internal Non-template base of the state object shared by promise and @@ -48,8 +52,14 @@ struct ircd::ctx::shared_state_base mutable dock cond; std::exception_ptr eptr; std::function then; + union + { + promise_base *p {nullptr}; + future_state st; + }; - shared_state_base(); + shared_state_base(promise_base &p); + shared_state_base() = default; shared_state_base(shared_state_base &&) = default; shared_state_base(const shared_state_base &) = delete; shared_state_base &operator=(shared_state_base &&) = default; @@ -62,22 +72,14 @@ template struct ircd::ctx::shared_state :shared_state_base { - using value_type = T; - using pointer_type = T *; - using reference_type = T &; + using value_type = T; + using pointer_type = T *; + using reference_type = T &; - union - { - promise *p {nullptr}; - future_state st; - }; T val; - shared_state(promise &p) - :p{&p} - {} - - shared_state() = default; + using shared_state_base::shared_state_base; + using shared_state_base::operator=; }; /// Internal shared state between future and promise when there is no future @@ -86,119 +88,8 @@ template<> struct ircd::ctx::shared_state :shared_state_base { - using value_type = void; + using value_type = void; - union - { - promise *p {nullptr}; - future_state st; - }; - - shared_state(promise &p) - :p{&p} - {} - - shared_state() = default; + using shared_state_base::shared_state_base; + using shared_state_base::operator=; }; - -/// Internal use -template -void -ircd::ctx::notify(shared_state &st) -{ - if(!st.then) - { - st.cond.notify_all(); - return; - } - - if(!current) - { - st.cond.notify_all(); - assert(bool(st.then)); - st.then(st); - return; - } - - const stack_usage_assertion sua; - st.cond.notify_all(); - assert(bool(st.then)); - st.then(st); -} - -/// Internal use -template -void -ircd::ctx::set(shared_state &st, - const future_state &state) -{ - switch(state) - { - case future_state::INVALID: assert(0); return; - case future_state::PENDING: assert(0); return; - case future_state::OBSERVED: - case future_state::READY: - case future_state::RETRIEVED: - default: - st.st = state; - return; - } -} - -/// Internal use -template<> -inline void -ircd::ctx::set(shared_state &st, - const future_state &state) -{ - switch(state) - { - case future_state::INVALID: assert(0); return; - case future_state::PENDING: assert(0); return; - case future_state::READY: - case future_state::OBSERVED: - case future_state::RETRIEVED: - default: - st.st = state; - return; - } -} - -/// Internal; check if the current state is something; safe but unnecessary -/// for public use. -template -bool -ircd::ctx::is(const shared_state &st, - const future_state &state_) -{ - switch(st.st) - { - case future_state::READY: - case future_state::OBSERVED: - case future_state::RETRIEVED: - return state_ == st.st; - - default: switch(state_) - { - case future_state::INVALID: - return st.p == nullptr; - - case future_state::PENDING: - return uintptr_t(st.p) >= 0x1000; - - default: - return false; - } - } -} - -/// Internal; get the current state of the shared_state; safe but unnecessary -/// for public use. -template -ircd::ctx::future_state -ircd::ctx::state(const shared_state &st) -{ - return uintptr_t(st.p) >= 0x1000? - future_state::PENDING: - st.st; -} diff --git a/include/ircd/ctx/when.h b/include/ircd/ctx/when.h index a87381c7e..eb6511286 100644 --- a/include/ircd/ctx/when.h +++ b/include/ircd/ctx/when.h @@ -129,7 +129,7 @@ ircd::ctx::when::all_then(promise &p) if(refcount(p.state()) < 2) return p.set_value(); - return remove(p.state(), p); + return p.remove(p.state(), p); } /// In order for this template to be reusable with std::set iterations we diff --git a/ircd/ctx.cc b/ircd/ctx.cc index 7ed750074..8111f1de2 100644 --- a/ircd/ctx.cc +++ b/ircd/ctx.cc @@ -1478,13 +1478,382 @@ ircd::ctx::ole::pop() return std::move(c); } +/////////////////////////////////////////////////////////////////////////////// +// +// ctx/promise.h +// + +// +// promise +// + +void +ircd::ctx::promise::set_value() +{ + check_pending(); + make_ready(); +} + +ircd::ctx::shared_state & +ircd::ctx::promise::state() +{ + return promise_base::state(); +} + +const ircd::ctx::shared_state & +ircd::ctx::promise::state() +const +{ + return promise_base::state(); +} + +// +// promise_base::promise_base +// + +ircd::ctx::promise_base::promise_base(promise_base &&o) +noexcept +:st{std::move(o.st)} +,next{std::move(o.next)} +{ + if(st) + { + update(*this, o); + o.st = nullptr; + } +} + +ircd::ctx::promise_base::promise_base(const promise_base &o) +:st{o.st} +,next{nullptr} +{ + append(*this, const_cast(o)); +} + +ircd::ctx::promise_base & +ircd::ctx::promise_base::operator=(promise_base &&o) +noexcept +{ + this->~promise_base(); + st = std::move(o.st); + next = std::move(o.next); + if(!st) + return *this; + + update(*this, o); + o.st = nullptr; + return *this; +} + +ircd::ctx::promise_base::~promise_base() +noexcept +{ + if(!valid()) + return; + + if(refcount(state()) == 1) + set_exception(std::make_exception_ptr(broken_promise())); + else + remove(state(), *this); +} + +void +ircd::ctx::promise_base::set_exception(std::exception_ptr eptr) +{ + check_pending(); + state().eptr = std::move(eptr); + make_ready(); +} + +void +ircd::ctx::promise_base::make_ready() +{ + auto &st(state()); + + // First we have to chase the linked list of promises reachable + // from this shared_state. invalidate() will null their pointer + // to the shared_state indicating the promise was already satisfied. + // This is done first because the set() to the READY writes to the + // same union as the promise pointer (see shared_state.h). + invalidate(st); + + // Now set the shared_state to READY. We know the location of the + // shared state by saving it in this frame earlier, otherwise invalidate() + // would have nulled it. + set(st, future_state::READY); + + // Finally call the notify() routine which will tell the future the promise + // was satisfied and the value/exception is ready for them. This call may + // notify an ircd::ctx and/or post a function to the ircd::ios for a then() + // callback etc. + notify(st); + + // At this point the future should not longer be considered valid; no longer + // referring to the shared_state. + assert(!valid()); +} + +void +ircd::ctx::promise_base::check_pending() +const +{ + assert(valid()); + if(unlikely(!is(state(), future_state::PENDING))) + throw promise_already_satisfied{}; +} + +bool +ircd::ctx::promise_base::operator!() +const +{ + return !valid(); +} + +ircd::ctx::promise_base::operator bool() +const +{ + return valid(); +} + +bool +ircd::ctx::promise_base::valid() +const +{ + return bool(st); +} + +ircd::ctx::shared_state_base & +ircd::ctx::promise_base::state() +{ + assert(valid()); + return *st; +} + +const ircd::ctx::shared_state_base & +ircd::ctx::promise_base::state() +const +{ + assert(valid()); + return *st; +} + +/// Internal semantics; chases the linked list of promises and adds a reference +/// to a new copy at the end (for copy semantic). +void +ircd::ctx::promise_base::append(promise_base &new_, + promise_base &old) +{ + if(!old.next) + { + old.next = &new_; + return; + } + + promise_base *next{old.next}; + for(; next->next; next = next->next); + next->next = &new_; +} + +/// Internal semantics; updates the location of a promise within the linked +/// list of related promises (for move semantic). +void +ircd::ctx::promise_base::update(promise_base &new_, + promise_base &old) +{ + assert(old.st); + auto &st{*old.st}; + if(!is(st, future_state::PENDING)) + return; + + if(st.p == &old) + { + st.p = &new_; + return; + } + + promise_base *last{st.p}; + for(promise_base *next{last->next}; next; last = next, next = last->next) + if(next == &old) + { + last->next = &new_; + break; + } +} + +/// Internal semantics; removes the promise from the linked list of promises. +/// Because the linked list of promises is a forward singly-linked list this +/// operation requires a reference to the list's head in shared_state_base +/// (for dtor semantic). +void +ircd::ctx::promise_base::remove(shared_state_base &st, + promise_base &p) +{ + if(!is(st, future_state::PENDING)) + return; + + if(st.p == &p) + { + st.p = p.next; + return; + } + + promise_base *last{st.p}; + for(promise_base *next{last->next}; next; last = next, next = last->next) + if(next == &p) + { + last->next = p.next; + break; + } +} + /////////////////////////////////////////////////////////////////////////////// // // ctx/shared_shared.h // -// Linkage -ircd::ctx::shared_state_base::shared_state_base() +/// Internal use +void +ircd::ctx::notify(shared_state_base &st) +{ + if(!st.then) + { + st.cond.notify_all(); + return; + } + + if(!current) + { + st.cond.notify_all(); + assert(bool(st.then)); + st.then(st); + return; + } + + const stack_usage_assertion sua; + st.cond.notify_all(); + assert(bool(st.then)); + st.then(st); +} + +/// Internal use; chases the linked list of promises starting from the head +/// in the shared_state and invalidates all of their references to the shared +/// state. This will cause the promise to no longer be valid(). +/// +void +ircd::ctx::invalidate(shared_state_base &st) +{ + if(is(st, future_state::PENDING)) + for(promise_base *p{st.p}; p; p = p->next) + p->st = nullptr; +} + +/// Internal use; chases the linked list of promises starting from the head in +/// the shared_state and updates the location of the shared_state within each +/// promise. This is used to tell the promises when the shared_state itself +/// has relocated. +/// +void +ircd::ctx::update(shared_state_base &st) +{ + if(is(st, future_state::PENDING)) + for(promise_base *p{st.p}; p; p = p->next) + p->st = &st; +} + +/// Internal use; returns the number of copies of the promise reachable from +/// the linked list headed by the shared state. This is used to indicate when +/// the last copy has destructed which may result in a broken_promise exception +/// being sent to the future. +size_t +ircd::ctx::refcount(const shared_state_base &st) +{ + size_t ret{0}; + if(is(st, future_state::PENDING)) + for(const promise_base *p{st.p}; p; p = p->next) + ++ret; + + return ret; +} + +/// Internal use; sets the state indicator within the shared_state object. Take +/// special note that this data is unionized. Setting a state here will clobber +/// the shared_state's reference to its promise. +void +ircd::ctx::set(shared_state_base &st, + const future_state &state) +{ + switch(state) + { + case future_state::INVALID: assert(0); return; + case future_state::PENDING: assert(0); return; + case future_state::OBSERVED: + case future_state::READY: + case future_state::RETRIEVED: + default: + st.st = state; + return; + } +} + +/// Internal; check if the current state is something; safe but unnecessary +/// for public use. Take special note here that the state value is unionized. +/// +/// A PENDING state returned here does not mean the state contains the +/// enumerated PENDING value itself, but instead contains a valid pointer +/// to a promise. +/// +/// An INVALID state shares a zero/null value in the unionized data. +bool +ircd::ctx::is(const shared_state_base &st, + const future_state &state_) +{ + switch(st.st) + { + case future_state::READY: + case future_state::OBSERVED: + case future_state::RETRIEVED: + return state_ == st.st; + + default: switch(state_) + { + case future_state::INVALID: + return st.p == nullptr; + + case future_state::PENDING: + return uintptr_t(st.p) >= 0x1000; + + default: + return false; + } + } +} + +/// Internal; get the current state of the shared_state; safe but unnecessary +/// for public use. +/// +/// NOTE: This operates over a union of a pointer and an enum class. The +/// way we determine whether the data is a pointer or an enum value is +/// with a test of the value being >= the system's page size. This assumes +/// the system does not use the first page of a process's address space +/// to fault on null pointer dereference. This assumption may not hold on +/// all systems or in all environments. +/// +/// Alternatively, we can switch this to checking whether the value is simply +/// above the few low-numbered enum values. +ircd::ctx::future_state +ircd::ctx::state(const shared_state_base &st) +{ + return uintptr_t(st.p) >= ircd::info::page_size? + future_state::PENDING: + st.st; +} + +// +// shared_state_base::shared_state_base +// + +ircd::ctx::shared_state_base::shared_state_base(promise_base &p) +:p{&p} { }