diff --git a/include/ircd/ctx.h b/include/ircd/ctx.h index 642e1e742..ad741b409 100644 --- a/include/ircd/ctx.h +++ b/include/ircd/ctx.h @@ -107,10 +107,12 @@ namespace ircd::ctx { inline namespace this_ctx #include "ctx/context.h" #include "ctx/prof.h" #include "ctx/dock.h" -#include "ctx/view.h" #include "ctx/queue.h" #include "ctx/mutex.h" #include "ctx/shared_mutex.h" +#include "ctx/peek.h" +#include "ctx/view.h" +#include "ctx/shared_view.h" #include "ctx/shared_state.h" #include "ctx/promise.h" #include "ctx/future.h" diff --git a/include/ircd/ctx/mutex.h b/include/ircd/ctx/mutex.h index d6a3aced5..feaf6cbe5 100644 --- a/include/ircd/ctx/mutex.h +++ b/include/ircd/ctx/mutex.h @@ -66,6 +66,8 @@ noexcept inline void ircd::ctx::mutex::unlock() { + assert(m); + ctx *next; do { if(!q.empty()) diff --git a/include/ircd/ctx/peek.h b/include/ircd/ctx/peek.h new file mode 100644 index 000000000..250fabda9 --- /dev/null +++ b/include/ircd/ctx/peek.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2016 Charybdis Development Team + * Copyright (C) 2016 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once +#define HAVE_IRCD_CTX_PEEK_H + +namespace ircd::ctx +{ + template class peek; +} + +/// Device for a context to share data on its stack with others while yielding +/// +/// The peek yields a context while other contexts examine the object pointed +/// to in the peek. This allows a producing context to construct something +/// on its stack and then wait for the consuming contexts to do something with +/// that data before the producer resumes and potentially destroys the data. +/// This creates a very simple and lightweight single-producer/multi-consumer +/// queue mechanism using only context switching. +/// +/// Consumers get one chance to safely peek the data when a call to wait() +/// returns. Once the consumer context yields again for any reason the data is +/// potentially invalid. The data can only be peeked once by the consumer +/// because the second call to wait() will yield until the next data is +/// made available by the producer, not the same data. +/// +/// Producers will share an object during the call to notify(). Once the call +/// to notify() returns all consumers have peeked the data and the producer is +/// free to destroy it. +/// +template +class ircd::ctx::peek +{ + T *t {nullptr}; + dock a, b; + + bool ready() const; + + public: + size_t waiting() const; + + // Consumer interface; + template T &wait_until(time_point&&); + template T &wait_for(const duration &); + T &wait(); + + // Producer interface; + void notify(T &); + + peek() = default; + ~peek() noexcept; +}; + +template +ircd::ctx::peek::~peek() +noexcept +{ + assert(!waiting()); +} + +template +void +ircd::ctx::peek::notify(T &t) +{ + const unwind afterward{[this] + { + assert(a.empty()); + this->t = nullptr; + if(!b.empty()) + { + b.notify_all(); + yield(); + } + }}; + + assert(b.empty()); + this->t = &t; + a.notify_all(); + yield(); +} + +template +T & +ircd::ctx::peek::wait() +{ + b.wait([this] + { + return !ready(); + }); + + a.wait([this] + { + return ready(); + }); + + assert(t != nullptr); + return *t; +} + +template +template +T & +ircd::ctx::peek::wait_for(const duration &dur) +{ + return wait_until(now() + dur); +} + +template +template +T & +ircd::ctx::peek::wait_until(time_point&& tp) +{ + if(!b.wait_until(tp, [this] + { + return !ready(); + })) + throw timeout(); + + if(!a.wait_until(tp, [this] + { + return ready(); + })) + throw timeout(); + + assert(t != nullptr); + return *t; +} + +template +size_t +ircd::ctx::peek::waiting() +const +{ + return a.size() + b.size(); +} + +template +bool +ircd::ctx::peek::ready() +const +{ + return t != nullptr; +} diff --git a/include/ircd/ctx/shared_view.h b/include/ircd/ctx/shared_view.h new file mode 100644 index 000000000..f14a19b40 --- /dev/null +++ b/include/ircd/ctx/shared_view.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2016 Charybdis Development Team + * Copyright (C) 2016 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once +#define HAVE_IRCD_CTX_SHARED_VIEW_H + +namespace ircd::ctx +{ + template class shared_view; +} + +/// Device for a context to share data on its stack with others while yielding +/// +/// The shared_view yields a context while other contexts examine the object pointed +/// to in the shared_view. This allows a producing context to construct something +/// on its stack and then wait for the consuming contexts to do something with +/// that data before the producer resumes and potentially destroys the data. +/// This creates a very simple and lightweight single-producer/multi-consumer +/// queue mechanism using only context switching. +/// +/// The producer is blocked until all consumers are finished with their shared_view. +/// The consumers acquire the shared_lock before passing it to the call to wait(). +/// wait() returns with a shared_view of the object under shared_lock. Once the +/// consumer releases the shared_lock the viewed object is not safe for them. +/// +template +class ircd::ctx::shared_view +:public shared_mutex +{ + T *t {nullptr}; + dock q; + size_t waiting {0}; + + bool ready() const; + + public: + // Consumer interface; + template T &wait_until(std::shared_lock &, time_point&&); + template T &wait_for(std::shared_lock &, const duration &); + T &wait(std::shared_lock &); + + // Producer interface; + void notify(T &); + + shared_view() = default; + ~shared_view() noexcept; +}; + +template +ircd::ctx::shared_view::~shared_view() +noexcept +{ + assert(!waiting); +} + +template +void +ircd::ctx::shared_view::notify(T &t) +{ + if(!waiting) + return; + + this->t = &t; + q.notify_all(); + q.wait([this] { return !waiting; }); + const std::lock_guard lock{*this}; + this->t = nullptr; + assert(!waiting); + q.notify_all(); +} + +template +T & +ircd::ctx::shared_view::wait(std::shared_lock &lock) +{ + for(assert(lock.owns_lock_shared()); ready(); lock.lock_shared()) + { + lock.unlock_shared(); + q.wait(); + } + + const unwind ul{[this] + { + --waiting; + q.notify_all(); + }}; + + for(++waiting; !ready(); lock.lock_shared()) + { + lock.unlock_shared(); + q.wait(); + } + + assert(t != nullptr); + return *t; +} + +template +template +T & +ircd::ctx::shared_view::wait_for(std::shared_lock &lock, + const duration &dur) +{ + return wait_until(now() + dur); +} + +template +template +T & +ircd::ctx::shared_view::wait_until(std::shared_lock &lock, + time_point&& tp) +{ + for(assert(lock.owns_lock_shared()); ready(); lock.lock_shared()) + { + lock.unlock_shared(); + q.wait_until(tp); + } + + const unwind ul{[this] + { + --waiting; + q.notify_all(); + }}; + + for(++waiting; !ready(); lock.lock_shared()) + { + lock.unlock_shared(); + q.wait_until(tp); + } + + assert(t != nullptr); + return *t; +} + +template +bool +ircd::ctx::shared_view::ready() +const +{ + return t != nullptr; +} diff --git a/include/ircd/ctx/view.h b/include/ircd/ctx/view.h index db94039a2..0c5e0a3f5 100644 --- a/include/ircd/ctx/view.h +++ b/include/ircd/ctx/view.h @@ -36,31 +36,26 @@ namespace ircd::ctx /// This creates a very simple and lightweight single-producer/multi-consumer /// queue mechanism using only context switching. /// -/// Consumers get one chance to safely view the data when a call to wait() -/// returns. Once the consumer context yields again for any reason the data is -/// potentially invalid. The data can only be viewed once by the consumer -/// because the second call to wait() will yield until the next data is -/// made available by the producer, not the same data. -/// -/// Producers will share an object during the call to notify(). Once the call -/// to notify() returns all consumers have viewed the data and the producer is -/// free to destroy it. +/// The producer is blocked until all consumers are finished with their view. +/// The consumers acquire the unique_lock before passing it to the call to wait(). +/// wait() returns with a view of the object under unique_lock. Once the +/// consumer releases the unique_lock the viewed object is not safe for them. /// template class ircd::ctx::view +:public mutex { T *t {nullptr}; - dock a, b; + dock q; + size_t waiting {0}; bool ready() const; public: - size_t waiting() const; - // Consumer interface; - template T &wait_until(time_point&&); - template T &wait_for(const duration &); - T &wait(); + template T &wait_until(std::unique_lock &, time_point&&); + template T &wait_for(std::unique_lock &, const duration &); + T &wait(std::unique_lock &); // Producer interface; void notify(T &); @@ -73,43 +68,46 @@ template ircd::ctx::view::~view() noexcept { - assert(!waiting()); + assert(!waiting); } template void ircd::ctx::view::notify(T &t) { - const unwind afterward{[this] - { - assert(a.empty()); - this->t = nullptr; - if(!b.empty()) - { - b.notify_all(); - yield(); - } - }}; + if(!waiting) + return; - assert(b.empty()); this->t = &t; - a.notify_all(); - yield(); + q.notify_all(); + q.wait([this] { return !waiting; }); + const std::lock_guard lock{*this}; + this->t = nullptr; + assert(!waiting); + q.notify_all(); } template T & -ircd::ctx::view::wait() +ircd::ctx::view::wait(std::unique_lock &lock) { - b.wait([this] + for(assert(lock.owns_lock()); ready(); lock.lock()) { - return !ready(); - }); + lock.unlock(); + q.wait(); + } - a.wait([this] + const unwind ul{[this] { - return ready(); - }); + --waiting; + q.notify_all(); + }}; + + for(++waiting; !ready(); lock.lock()) + { + lock.unlock(); + q.wait(); + } assert(t != nullptr); return *t; @@ -118,7 +116,8 @@ ircd::ctx::view::wait() template template T & -ircd::ctx::view::wait_for(const duration &dur) +ircd::ctx::view::wait_for(std::unique_lock &lock, + const duration &dur) { return wait_until(now() + dur); } @@ -126,32 +125,31 @@ ircd::ctx::view::wait_for(const duration &dur) template template T & -ircd::ctx::view::wait_until(time_point&& tp) +ircd::ctx::view::wait_until(std::unique_lock &lock, + time_point&& tp) { - if(!b.wait_until(tp, [this] + for(assert(lock.owns_lock()); ready(); lock.lock()) { - return !ready(); - })) - throw timeout(); + lock.unlock(); + q.wait_until(tp); + } - if(!a.wait_until(tp, [this] + const unwind ul{[this] { - return ready(); - })) - throw timeout(); + --waiting; + q.notify_all(); + }}; + + for(++waiting; !ready(); lock.lock()) + { + lock.unlock(); + q.wait_until(tp); + } assert(t != nullptr); return *t; } -template -size_t -ircd::ctx::view::waiting() -const -{ - return a.size() + b.size(); -} - template bool ircd::ctx::view::ready() diff --git a/ircd/ctx.cc b/ircd/ctx.cc index 6571ba081..dee152d87 100644 --- a/ircd/ctx.cc +++ b/ircd/ctx.cc @@ -741,7 +741,7 @@ ircd::ctx::prof::check_slice() if(unlikely(settings.slice_warning > 0us && time_usage >= settings.slice_warning)) { - log::warning("CONTEXT TIMESLICE EXCEEDED (%p) '%s' last: %06ld$us total: %06ld$us", + log::warning("context timeslice exceeded (%p) '%s' last: %06ld$us total: %06ld$us", (const void *)&c, c.name, duration_cast(time_usage).count(), @@ -765,7 +765,7 @@ ircd::ctx::prof::check_stack() if(unlikely(stack_usage > stack_max * settings.stack_usage_warning)) { - log::warning("CONTEXT STACK USAGE ctx(%p) used %zu of %zu bytes", + log::warning("context stack usage ctx(%p) used %zu of %zu bytes", (const void *)&c, stack_usage, c.stack_max);