From fb17dc13d49f9a3446efdcad441017d9c9dd7f4f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 7 Mar 2016 20:51:58 -0500 Subject: [PATCH 1/9] Haskell has two L's -- see https://www.haskell.org/ --- doc/performance.qbk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/performance.qbk b/doc/performance.qbk index c76161ac..41f8d88c 100644 --- a/doc/performance.qbk +++ b/doc/performance.qbk @@ -64,7 +64,7 @@ Numbers of the [@https://github.com/atemerev/skynet microbenchmark ['syknet]] fr [footnote Intel Core2 Q6700, x86_64, 3GHz]: [table performance of N=100000 actors/goroutines/fibers - [[Haskel | stack-1.0.4] [fiber (single threaded/raw) | gcc-5.2.1] [fiber (single threaded/atomics) | gcc-5.2.1] [Erlang | erts-7.0] [Go | go1.4.2]] + [[Haskell | stack-1.0.4] [fiber (single threaded/raw) | gcc-5.2.1] [fiber (single threaded/atomics) | gcc-5.2.1] [Erlang | erts-7.0] [Go | go1.4.2]] [[58ms - 108ms] [205ms - 263ms] [221ms - 278ms] [237ms- 470ms] [614ms - 883ms]] ] From 908bd3ca6819e36966b62d914518956ae32ca51d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 7 Mar 2016 21:14:37 -0500 Subject: [PATCH 2/9] Fix up references to traits_type in stack allocation section. Provide a link to Boost.Context's stack_traits documentation to look up is_unbounded(), minimum_size() and maximum_size(). Fix spellings of minimum_size() and maximum_size(). When stack_traits::is_unbounded(), we shouldn't have to care about stack_traits::maximum_size(). Consistently change: ! traits_type::is_unbounded() && size <= traits_type::maximum_size() to: traits_type::is_unbounded() || size <= traits_type::maximum_size() --- doc/stack.qbk | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/doc/stack.qbk b/doc/stack.qbk index 56c17801..343970eb 100644 --- a/doc/stack.qbk +++ b/doc/stack.qbk @@ -51,6 +51,9 @@ undefined behaviour.] place inside __econtext__.] See also [@http://www.boost.org/doc/libs/release/libs/context/doc/html/context/stack.html Boost.Context stack allocation]. +In particular, `traits_type` methods are as described for +[@http://www.boost.org/doc/libs/release/libs/context/doc/html/context/stack/stack_traits.html +`boost::context::stack_traits`]. [class_heading protected_fixedsize_stack] @@ -79,9 +82,9 @@ virtual addresses are used.] [heading `stack_context allocate()`] [variablelist -[[Preconditions:] [`traits_type::minimum:size() <= size` and -`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= size)`.]] -[[Effects:] [Allocates memory of at least `size` Bytes and stores a pointer +[[Preconditions:] [`traits_type::minimum_size() <= size` and +`traits_type::is_unbounded() || ( size <= traits_type::maximum_size() )`.]] +[[Effects:] [Allocates memory of at least `size` bytes and stores a pointer to the stack and its actual size in `sctx`. Depending on the architecture (the stack grows downwards/upwards) the stored address is the highest/lowest address of the stack.]] @@ -89,8 +92,8 @@ the highest/lowest address of the stack.]] [heading `void deallocate( stack_context & sctx)`] [variablelist -[[Preconditions:] [`sctx.sp` is valid, `traits_type::minimum:size() <= sctx.size` and -`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= sctx.size)`.]] +[[Preconditions:] [`sctx.sp` is valid, `traits_type::minimum_size() <= sctx.size` and +`traits_type::is_unbounded() || ( sctx.size <= traits_type::maximum_size() )`.]] [[Effects:] [Deallocates the stack space.]] ] @@ -115,21 +118,21 @@ end of each stack. The memory is managed internally by [heading `pooled_fixedsize_stack(std::size_t stack_size, std::size_t next_size, std::size_t max_size)`] [variablelist -[[Preconditions:] [`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= stack_size)` -and `0 < nest_size`.]] -[[Effects:] [Allocates memory of at least `stack_size` Bytes and stores a pointer to +[[Preconditions:] [`traits_type::is_unbounded() || ( traits_type::maximum_size() >= stack_size)` +and `0 < next_size`.]] +[[Effects:] [Allocates memory of at least `stack_size` bytes and stores a pointer to the stack and its actual size in `sctx`. Depending on the architecture (the stack grows downwards/upwards) the stored address is the highest/lowest address of the stack. Argument `next_size` determines the number of stacks to request from the system the first time that `*this` needs to allocate system -memory. The third argument `max_size` controls how many memory might be -allocated for stacks - a value of zero means no uper limit.]] +memory. The third argument `max_size` controls how much memory might be +allocated for stacks [mdash] a value of zero means no upper limit.]] ] [heading `stack_context allocate()`] -[variablelis_stackt -[[Preconditions:] [`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= stack_size)`.]] -[[Effects:] [Allocates memory of at least `stack_size` Bytes and stores a pointer to +[variablelist +[[Preconditions:] [`traits_type::is_unbounded() || ( traits_type::maximum_size() >= stack_size)`.]] +[[Effects:] [Allocates memory of at least `stack_size` bytes and stores a pointer to the stack and its actual size in `sctx`. Depending on the architecture (the stack grows downwards/upwards) the stored address is the highest/lowest address of the stack.]] @@ -138,7 +141,7 @@ address of the stack.]] [heading `void deallocate( stack_context & sctx)`] [variablelist [[Preconditions:] [`sctx.sp` is valid, -`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= sctx.size)`.]] +`traits_type::is_unbounded() || ( traits_type::maximum_size() >= sctx.size)`.]] [[Effects:] [Deallocates the stack space.]] ] @@ -163,9 +166,9 @@ end of each stack. The memory is simply managed by `std::malloc()` and [heading `stack_context allocate()`] [variablelist -[[Preconditions:] [`traits_type::minimum:size() <= size` and -`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= size)`.]] -[[Effects:] [Allocates memory of at least `size` Bytes and stores a pointer to +[[Preconditions:] [`traits_type::minimum_size() <= size` and +`traits_type::is_unbounded() || ( traits_type::maximum_size() >= size)`.]] +[[Effects:] [Allocates memory of at least `size` bytes and stores a pointer to the stack and its actual size in `sctx`. Depending on the architecture (the stack grows downwards/upwards) the stored address is the highest/lowest address of the stack.]] @@ -173,8 +176,8 @@ address of the stack.]] [heading `void deallocate( stack_context & sctx)`] [variablelist -[[Preconditions:] [`sctx.sp` is valid, `traits_type::minimum:size() <= sctx.size` and -`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= sctx.size)`.]] +[[Preconditions:] [`sctx.sp` is valid, `traits_type::minimum_size() <= sctx.size` and +`traits_type::is_unbounded() || ( traits_type::maximum_size() >= sctx.size)`.]] [[Effects:] [Deallocates the stack space.]] ] @@ -208,9 +211,9 @@ command line.] [heading `stack_context allocate()`] [variablelist -[[Preconditions:] [`traits_type::minimum:size() <= size` and -`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= size)`.]] -[[Effects:] [Allocates memory of at least `size` Bytes and stores a pointer to +[[Preconditions:] [`traits_type::minimum_size() <= size` and +`traits_type::is_unbounded() || ( traits_type::maximum_size() >= size)`.]] +[[Effects:] [Allocates memory of at least `size` bytes and stores a pointer to the stack and its actual size in `sctx`. Depending on the architecture (the stack grows downwards/upwards) the stored address is the highest/lowest address of the stack.]] @@ -218,8 +221,8 @@ address of the stack.]] [heading `void deallocate( stack_context & sctx)`] [variablelist -[[Preconditions:] [`sctx.sp` is valid, `traits_type::minimum:size() <= sctx.size` and -`! traits_type::is_unbounded() && ( traits_type::maximum:size() >= sctx.size)`.]] +[[Preconditions:] [`sctx.sp` is valid, `traits_type::minimum_size() <= sctx.size` and +`traits_type::is_unbounded() || ( traits_type::maximum_size() >= sctx.size)`.]] [[Effects:] [Deallocates the stack space.]] ] From d9e5bde625faf2a2ce48e0d2782ef90880740673 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 7 Mar 2016 21:17:56 -0500 Subject: [PATCH 3/9] Make a pass through enum class type and new context::is_context(). Do not document the specific values of enum class type values. Consuming code should be based solely on enum names; their values are an implementation detail. Ensure that all enum class type values are mentioned in note for is_context() method. --- doc/scheduling.qbk | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/doc/scheduling.qbk b/doc/scheduling.qbk index 08260968..125e1831 100644 --- a/doc/scheduling.qbk +++ b/doc/scheduling.qbk @@ -418,11 +418,11 @@ of typical STL containers. #include enum class type { - none = 0, - main_context = 1 << 1, // fiber, associated with thread's stack - dispatcher_context = 1 << 2, // special fiber for maintanance operations - worker_context = 1 << 3, // fiber not special to the library - pinned_context = main_context | dispatcher_context // context of fiber must not be migrated to another thread + none, + main_context, // fiber associated with thread's stack + dispatcher_context, // special fiber for maintenance operations + worker_context, // fiber not special to the library + pinned_context // fiber must not be migrated to another thread }; class context { @@ -495,20 +495,18 @@ default-constructed __fiber_id__.]] bool is_context( type t) const noexcept; [variablelist -[[Returns:] [`true` if `*this` is of the specific type.]] +[[Returns:] [`true` if `*this` is of the specified type.]] [[Throws:] [Nothing]] -[[Note:] [For `type::main_context` the `context` is associated with the ["main] fiber of the thread: -the one implicitly created by the thread itself, rather than one explicitly -created by __boost_fiber__. -For `type::disaptcher_context` the `context` is associated with a ["dispatching] fiber, responsible -for dispatching awakened fibers to a scheduler's ready-queue. The -["dispatching] fiber is an implementation detail of the fiber manager. -The context of ["main] and ["dispatching] fiber must never be passed to -[member_link context..migrate] for any other thread. -true` if `*this` is a worker context, that is, a context -associated with a fiber explicitly launched by the application rather than -implicitly.]]] -[[Note:] [The term ["worker] here means any fiber not special to the library.]] +[[Note:] [`type::worker_context` here means any fiber not special to the +library. For `type::main_context` the `context` is associated with the ["main] +fiber of the thread: the one implicitly created by the thread itself, rather +than one explicitly created by __boost_fiber__. For `type::dispatcher_context` +the `context` is associated with a ["dispatching] fiber, responsible for +dispatching awakened fibers to a scheduler's ready-queue. The ["dispatching] +fiber is an implementation detail of the fiber manager. The context of the +["main] or ["dispatching] fiber [mdash] any fiber for which +`is_context(pinned_context)` is `true` [mdash] must never be passed to +[member_link context..migrate] for any other thread.]] ] [member_heading context..is_terminated] From f905cb4c3b2256701683d93d4d5c777cc3354627 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 12 Mar 2016 10:45:54 -0500 Subject: [PATCH 4/9] Document context::suspend() and set_ready(). --- doc/scheduling.qbk | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/doc/scheduling.qbk b/doc/scheduling.qbk index 125e1831..9ba57051 100644 --- a/doc/scheduling.qbk +++ b/doc/scheduling.qbk @@ -456,6 +456,9 @@ of typical STL containers. void ready_unlink() noexcept; void remote_ready_unlink() noexcept; void wait_unlink() noexcept; + + void suspend() noexcept; + void set_ready( context *) noexcept; }; bool operator<( context const& l, context const& r) noexcept; @@ -628,6 +631,44 @@ __boost_intrusive__.]] [[Throws:] [Nothing]] ] +[member_heading context..suspend] + + void suspend() noexcept; + +[variablelist +[[Effects:] [Suspends the running fiber (the fiber associated with `*this`) +until some other fiber passes `this` to [member_link context..set_ready]. +`*this` is marked as not-ready, and control passes to the scheduler to select +another fiber to run.]] +[[Throws:] [Nothing]] +[[Note:] [This is a low-level API potentially useful for integration with +other frameworks. It is not intended to be directly invoked by a typical +application program.]] +[[Note:] [The burden is on the caller to arrange for a call to `set_ready()` +with a pointer to `this` at some future time.]] +] + +[member_heading context..set_ready] + + void set_ready( context * ctx ) noexcept; + +[variablelist +[[Effects:] [Mark the fiber associated with context `*ctx` as being ready to +run. This does not immediately resume that fiber; rather it passes the fiber +to the scheduler for subsequent resumption. If the scheduler is idle (has not +returned from a call to [member_link sched_algorithm..suspend_until]), +[member_link sched_algorithm..notify] is called to wake it up.]] +[[Throws:] [Nothing]] +[[Note:] [This is a low-level API potentially useful for integration with +other frameworks. It is not intended to be directly invoked by a typical +application program.]] +[[Note:] [It is explicitly supported to call `set_ready(ctx)` from a thread +other than the one on which `*ctx` is currently suspended. The corresponding +fiber will be resumed on its original thread in due course.]] +[[Note:] [See [member_link context..migrate] for a way to migrate the +suspended thread to the thread calling `set_ready()`.]] +] + [hding context_less..Non-member function [`operator<()]] bool operator<( context const& l, context const& r) noexcept; From 5758781500cab3873a4fecbad171025d6b903094 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 13 Mar 2016 14:20:46 -0400 Subject: [PATCH 5/9] Allow caller of asio async function to permit or deny migration. Introduce yield_base with subclasses yield_t and yield_hop_t, each with a canonical instance yield and yield_hop. yield_base adds allow_hop_ bool to communicate the distinction to yield_handler: yield_t sets false, yield_hop_t sets true. Extract common base class yield_handler_base from yield_handler and yield_handler. In fact yield_handler_base is almost identical to yield_handler; yield_handler adds value processing. Instead of capturing just the error_code* from the passed yield_base instance, capture the whole yield_base: both its error_code* and the new allow_hop_ bool. yield_handler_base provides operator()(error_code) method. This operator() sets a new completed_ bool so calling fiber need NOT suspend if the async operation completes immediately. That bool must be defended with a mutex. This operator() also avoids migrating a pinned_context, or when a caller passes plain yield instead of yield_hop. New wait() method suspends the calling fiber only if (! completed_). Extract common base class async_result_base from async_result and async_result. In fact async_result_base is almost identical to async_result; async_result adds value processing. Add handler_type<> specializations for new yield_base and yield_hop_t completion token types. --- examples/asio/detail/yield.hpp | 302 ++++++++++++++++++++++----------- examples/asio/yield.hpp | 35 +++- 2 files changed, 237 insertions(+), 100 deletions(-) diff --git a/examples/asio/detail/yield.hpp b/examples/asio/detail/yield.hpp index 68cef5d1..36082019 100644 --- a/examples/asio/detail/yield.hpp +++ b/examples/asio/detail/yield.hpp @@ -10,6 +10,8 @@ #include +#include // std::unique_lock + #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX #endif @@ -19,65 +21,120 @@ namespace fibers { namespace asio { namespace detail { +// This class encapsulates common elements between yield_handler (capturing +// a value to return from asio async function) and yield_handler (no +// such value). See yield_handler and its specialization below. +class yield_handler_base { +public: + yield_handler_base( yield_base const& y) : + // capture the context* associated with the running fiber + ctx_( boost::fibers::context::active() ), + // capture the passed yield_base + yb_( y ) + {} + + // completion callback passing only (error_code) + void operator()( boost::system::error_code const& ec) + { + // We can't afford a wait() call during this method. + std::unique_lock< fibers::mutex > lk( mtx_ ); + // Notify a subsequent wait() call that it need not suspend. + completed_ = true; + // set the error_code bound by yield_t + * yb_.ec_ = ec; + // Are we permitted to wake up the suspended fiber on this thread, the + // thread that called the completion handler? + if ( ctx_->is_context( pinned_context ) || ( ! yb_.allow_hop_ )) { + // We must not migrate a pinned_context to another thread. + // If the application passed yield_t rather than yield_hop_t, we + // are forbidden to migrate this fiber. + // Just wake this fiber on its original scheduler. + ctx_->set_ready( ctx_); + } else { + // seems okay to migrate this context + // migrate the waiting fiber to the running thread + boost::fibers::context::active()->migrate( ctx_); + // and wake the fiber + boost::fibers::context::active()->set_ready( ctx_); + } + } + + void wait() + { + // do not interleave operator() with wait() + std::unique_lock< fibers::mutex > lk( mtx_ ); + // if operator() has not yet been called + if ( ! completed_ ) { + // then permit it to be called + lk.unlock(); + // and wait for that call + fibers::context::active()->suspend(); + } + } + +//private: + boost::fibers::context * ctx_; + yield_base yb_; + +private: + // completed_ tracks whether complete() is called before get() + fibers::mutex mtx_; + bool completed_{ false }; +}; + +// handler_type< yield_t, ... >::type is yield_handler<>. When you pass an +// instance of yield_t as an asio completion token, asio uses +// handler_type<>::type to select yield_handler<> as the actual handler class. template< typename T > -class yield_handler +class yield_handler: public yield_handler_base { public: - yield_handler( yield_t const& y) : - ctx_( boost::fibers::context::active() ), - ec_( y.ec_), + // asio passes the completion token to the handler constructor + explicit yield_handler( yield_base const& y) : + yield_handler_base( y), + // pointer to destination for eventual value value_( nullptr) {} + // completion callback passing only value (T) void operator()( T t) { - * ec_ = boost::system::error_code(); - * value_ = std::move( t); - boost::fibers::context::active()->migrate( ctx_); - boost::fibers::context::active()->set_ready( ctx_); + // just like a callback passing success error_code + (*this)( boost::system::error_code(), std::move(t) ); } + // completion callback passing (error_code, T) void operator()( boost::system::error_code const& ec, T t) { - * ec_ = ec; + // move the value to async_result<> instance * value_ = std::move( t); - boost::fibers::context::active()->migrate( ctx_); - boost::fibers::context::active()->set_ready( ctx_); + // forward the call to base-class completion handler + yield_handler_base::operator()( ec); } //private: - boost::fibers::context * ctx_; - boost::system::error_code * ec_; T * value_; }; -// Completion handler to adapt a void promise as a completion handler. +// yield_handler is like yield_handler without value_. In fact it's +// just like yield_handler_base. template<> -class yield_handler< void > +class yield_handler< void >: public yield_handler_base { public: - yield_handler( yield_t const& y) : - ctx_( boost::fibers::context::active() ), - ec_( y.ec_) + explicit yield_handler( yield_base const& y) : + yield_handler_base( y) {} - + + // nullary completion callback void operator()() { - * ec_ = boost::system::error_code(); - boost::fibers::context::active()->migrate( ctx_); - boost::fibers::context::active()->set_ready( ctx_); - } - - void operator()( boost::system::error_code const& ec) - { - * ec_ = ec; - boost::fibers::context::active()->migrate( ctx_); - boost::fibers::context::active()->set_ready( ctx_); + (*this)( boost::system::error_code() ); } -//private: - boost::fibers::context * ctx_; - boost::system::error_code * ec_; + // inherit operator()(error_code) overload from base class + using yield_handler_base::operator(); + }; // Specialize asio_handler_invoke hook to ensure that any exceptions thrown @@ -95,90 +152,141 @@ void asio_handler_invoke( Fn fn, yield_handler< T > * h) { namespace boost { namespace asio { -template< typename T > -class async_result< boost::fibers::asio::detail::yield_handler< T > > { +class async_result_base +{ public: - typedef T type; - - explicit async_result( boost::fibers::asio::detail::yield_handler< T > & h) { - out_ec_ = h.ec_; - if ( ! out_ec_) { - h.ec_ = & ec_; - } - h.value_ = & value_; - } - - type get() { - boost::fibers::context::active()->suspend(); - if ( ! out_ec_ && ec_) { - throw_exception( boost::system::system_error( ec_) ); - } - boost::this_fiber::interruption_point(); - return std::move( value_); - } - -private: - boost::system::error_code * out_ec_; - boost::system::error_code ec_; - type value_; -}; - -template<> -class async_result< boost::fibers::asio::detail::yield_handler< void > > { -public: - typedef void type; - - explicit async_result( boost::fibers::asio::detail::yield_handler< void > & h) { - out_ec_ = h.ec_; - if ( ! out_ec_) { - h.ec_ = & ec_; + template< typename T > + explicit async_result_base( boost::fibers::asio::detail::yield_handler_base & h): + // bind this yield_handler_base for later use + yh_( h) + { + // if yield_t didn't bind an error_code, make yield_handler_base's + // error_code* point to an error_code local to this object so + // yield_handler_base::operator() can unconditionally store through + // its error_code* + if ( ! h.yb_.ec_) { + h.yb_.ec_ = & ec_; } } void get() { - boost::fibers::context::active()->suspend(); - if ( ! out_ec_ && ec_) { + // Unless yield_handler_base::operator() has already been called, + // suspend the calling fiber until that call. + yh_.wait(); + // The only way our own ec_ member could have a non-default value is + // if our yield_handler did not have a bound error_code AND the + // completion callback passed a non-default error_code. + if ( ec_) { throw_exception( boost::system::system_error( ec_) ); } boost::this_fiber::interruption_point(); } +protected: + boost::system::error_code ec_; + private: - boost::system::error_code * out_ec_; - boost::system::error_code ec_; + fibers::asio::detail::yield_handler_base& yh_; }; -// Handler type specialisation for use_future. -template< typename ReturnType > -struct handler_type< - boost::fibers::asio::yield_t, - ReturnType() -> -{ typedef boost::fibers::asio::detail::yield_handler< void > type; }; +// asio constructs an async_result<> instance from the yield_handler specified +// by handler_type<>::type. A particular asio async method constructs the +// yield_handler, constructs this async_result specialization from it, then +// returns the result of calling its get() method. +template< typename T > +class async_result< boost::fibers::asio::detail::yield_handler< T > >: + public async_result_base { +public: + // type returned by get() + typedef T type; -// Handler type specialisation for use_future. + // async_result is constructed with a non-const yield_handler instance + explicit async_result( boost::fibers::asio::detail::yield_handler< T > & h): + async_result_base(h) + { + // set yield_handler's T* to this object's value_ member so + // yield_handler::operator() will store the callback value here + h.value_ = & value_; + } + + // asio async method returns result of calling get() + type get() { + async_result_base::get(); + return std::move( value_); + } + +private: + type value_; +}; + +// Without the need to handle a passed value, our yield_handler +// specialization is just like async_result_base. +template<> +class async_result< boost::fibers::asio::detail::yield_handler< void > >: + public async_result_base { +public: + typedef void type; + + explicit async_result( boost::fibers::asio::detail::yield_handler< void > & h): + async_result_base(h) + {} +}; + +// Handler type specialisation for fibers::asio::yield. +// When 'yield' is passed as a completion handler which accepts no parameters, +// use yield_handler. +template< typename ReturnType > +struct handler_type< fibers::asio::yield_base, ReturnType() > +{ typedef fibers::asio::detail::yield_handler< void > type; }; +template< typename ReturnType > +struct handler_type< fibers::asio::yield_t, ReturnType() > +{ typedef fibers::asio::detail::yield_handler< void > type; }; +template< typename ReturnType > +struct handler_type< fibers::asio::yield_hop_t, ReturnType() > +{ typedef fibers::asio::detail::yield_handler< void > type; }; + +// Handler type specialisation for fibers::asio::yield. +// When 'yield' is passed as a completion handler which accepts a data +// parameter, use yield_handler to return that parameter to +// the caller. template< typename ReturnType, typename Arg1 > -struct handler_type< - boost::fibers::asio::yield_t, - ReturnType( Arg1) -> -{ typedef boost::fibers::asio::detail::yield_handler< Arg1 > type; }; +struct handler_type< fibers::asio::yield_base, ReturnType( Arg1) > +{ typedef fibers::asio::detail::yield_handler< Arg1 > type; }; +template< typename ReturnType, typename Arg1 > +struct handler_type< fibers::asio::yield_t, ReturnType( Arg1) > +{ typedef fibers::asio::detail::yield_handler< Arg1 > type; }; +template< typename ReturnType, typename Arg1 > +struct handler_type< fibers::asio::yield_hop_t, ReturnType( Arg1) > +{ typedef fibers::asio::detail::yield_handler< Arg1 > type; }; -// Handler type specialisation for use_future. +// Handler type specialisation for fibers::asio::yield. +// When 'yield' is passed as a completion handler which accepts only +// error_code, use yield_handler. yield_handler will take care of the +// error_code one way or another. template< typename ReturnType > -struct handler_type< - boost::fibers::asio::yield_t, - ReturnType( boost::system::error_code) -> -{ typedef boost::fibers::asio::detail::yield_handler< void > type; }; +struct handler_type< fibers::asio::yield_base, ReturnType( boost::system::error_code) > +{ typedef fibers::asio::detail::yield_handler< void > type; }; +template< typename ReturnType > +struct handler_type< fibers::asio::yield_t, ReturnType( boost::system::error_code) > +{ typedef fibers::asio::detail::yield_handler< void > type; }; +template< typename ReturnType > +struct handler_type< fibers::asio::yield_hop_t, ReturnType( boost::system::error_code) > +{ typedef fibers::asio::detail::yield_handler< void > type; }; -// Handler type specialisation for use_future. +// Handler type specialisation for fibers::asio::yield. +// When 'yield' is passed as a completion handler which accepts a data +// parameter and an error_code, use yield_handler to return +// just the parameter to the caller. yield_handler will take care of the +// error_code one way or another. template< typename ReturnType, typename Arg2 > -struct handler_type< - boost::fibers::asio::yield_t, - ReturnType( boost::system::error_code, Arg2) -> -{ typedef boost::fibers::asio::detail::yield_handler< Arg2 > type; }; +struct handler_type< fibers::asio::yield_base, ReturnType( boost::system::error_code, Arg2) > +{ typedef fibers::asio::detail::yield_handler< Arg2 > type; }; +template< typename ReturnType, typename Arg2 > +struct handler_type< fibers::asio::yield_t, ReturnType( boost::system::error_code, Arg2) > +{ typedef fibers::asio::detail::yield_handler< Arg2 > type; }; +template< typename ReturnType, typename Arg2 > +struct handler_type< fibers::asio::yield_hop_t, ReturnType( boost::system::error_code, Arg2) > +{ typedef fibers::asio::detail::yield_handler< Arg2 > type; }; } // namespace asio } // namespace boost diff --git a/examples/asio/yield.hpp b/examples/asio/yield.hpp index 66737b1a..5f8430a7 100644 --- a/examples/asio/yield.hpp +++ b/examples/asio/yield.hpp @@ -25,21 +25,50 @@ namespace boost { namespace fibers { namespace asio { -class yield_t { +class yield_base { public: constexpr yield_t() = default; - yield_t operator[]( boost::system::error_code & ec) const { - yield_t tmp; + /** + * @code + * static yield_base yield; + * boost::system::error_code myec; + * func(yield[myec]); + * @endcode + * @c yield[myec] returns an instance of @c yield_base whose @c ec_ points + * to @c myec. The expression @c yield[myec] "binds" @c myec to that + * (anonymous) @c yield_base instance, instructing @c func() to store any + * @c error_code it might produce into @c myec rather than throwing @c + * boost::system::system_error. + */ + yield_base operator[]( boost::system::error_code & ec) const { + yield_base tmp{ *this }; tmp.ec_ = & ec; return tmp; } //private: + // ptr to bound error_code instance if any boost::system::error_code * ec_{ nullptr }; + // allow calling fiber to "hop" to another thread if it could resume more + // quickly that way + bool allow_hop_{ false }; }; +class yield_t : public yield_base { +}; + +class yield_hop_t : public yield_base { +public: + yield_hop_t() { + allow_hop_ = true; + } +}; + +// canonical instance with allow_hop_ == false thread_local yield_t yield{}; +// canonical instance with allow_hop_ == true +thread_local yield_hop_t yield_hop{}; }}} From 3e7b94c922b368cf3b4bba7cea79687fb26b177f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 14 Mar 2016 21:20:30 -0400 Subject: [PATCH 6/9] Fix silly compile errors in yield.hpp and detail/yield.hpp. Still to fix: can't use ctx_->set_ready(ctx_) to wake suspended context on its own scheduler. --- examples/asio/detail/yield.hpp | 13 +++++++------ examples/asio/yield.hpp | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/asio/detail/yield.hpp b/examples/asio/detail/yield.hpp index 36082019..18552252 100644 --- a/examples/asio/detail/yield.hpp +++ b/examples/asio/detail/yield.hpp @@ -11,6 +11,7 @@ #include #include // std::unique_lock +#include // std::shared_ptr #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX @@ -37,14 +38,14 @@ public: void operator()( boost::system::error_code const& ec) { // We can't afford a wait() call during this method. - std::unique_lock< fibers::mutex > lk( mtx_ ); + std::unique_lock< fibers::mutex > lk( *mtx_ ); // Notify a subsequent wait() call that it need not suspend. completed_ = true; // set the error_code bound by yield_t * yb_.ec_ = ec; // Are we permitted to wake up the suspended fiber on this thread, the // thread that called the completion handler? - if ( ctx_->is_context( pinned_context ) || ( ! yb_.allow_hop_ )) { + if ( ctx_->is_context( fibers::type::pinned_context ) || ( ! yb_.allow_hop_ )) { // We must not migrate a pinned_context to another thread. // If the application passed yield_t rather than yield_hop_t, we // are forbidden to migrate this fiber. @@ -62,7 +63,7 @@ public: void wait() { // do not interleave operator() with wait() - std::unique_lock< fibers::mutex > lk( mtx_ ); + std::unique_lock< fibers::mutex > lk( *mtx_ ); // if operator() has not yet been called if ( ! completed_ ) { // then permit it to be called @@ -77,8 +78,9 @@ public: yield_base yb_; private: - // completed_ tracks whether complete() is called before get() - fibers::mutex mtx_; + // completed_ tracks whether complete() is called before get(). + // We use a shared_ptr because yield_handler_base is copied. + std::shared_ptr mtx_{std::make_shared()}; bool completed_{ false }; }; @@ -155,7 +157,6 @@ namespace asio { class async_result_base { public: - template< typename T > explicit async_result_base( boost::fibers::asio::detail::yield_handler_base & h): // bind this yield_handler_base for later use yh_( h) diff --git a/examples/asio/yield.hpp b/examples/asio/yield.hpp index 5f8430a7..6705cfcd 100644 --- a/examples/asio/yield.hpp +++ b/examples/asio/yield.hpp @@ -27,7 +27,7 @@ namespace asio { class yield_base { public: - constexpr yield_t() = default; + constexpr yield_base() = default; /** * @code From 85c0d26d118908c429237fb5c2dc0de0de7a8745 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 23 Mar 2016 21:28:40 -0400 Subject: [PATCH 7/9] Fix minor errors in doc generation. --- doc/stack.qbk | 2 +- examples/priority.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/stack.qbk b/doc/stack.qbk index 343970eb..0913a343 100644 --- a/doc/stack.qbk +++ b/doc/stack.qbk @@ -145,7 +145,7 @@ address of the stack.]] [[Effects:] [Deallocates the stack space.]] ] - +[#class_fixedsize_stack] [section:fixedsize Class ['fixedsize_stack]] __boost_fiber__ provides the class __fixedsize__ which models diff --git a/examples/priority.cpp b/examples/priority.cpp index ac47c04a..9f95714a 100644 --- a/examples/priority.cpp +++ b/examples/priority.cpp @@ -46,8 +46,8 @@ public: // Call this method to alter priority, because we must notify // priority_scheduler of any change. - void set_priority( int p) { - /*< It's important to call notify() on any + void set_priority( int p) { /*< + It's important to call `notify()` on any change in a property that can affect the scheduler's behavior. Therefore, such modifications should only be performed @@ -155,7 +155,7 @@ public: // 'ctx' might not be in our queue at all, if caller is changing the // priority of (say) the running fiber. If it's not there, no need to // move it: we'll handle it next time it hits awakened(). - if ( ! ctx->ready_is_linked()) {/*< + if ( ! ctx->ready_is_linked()) { /*< Your `property_change()` override must be able to handle the case in which the passed `ctx` is not in your ready queue. It might be running, or it might be From cba1a74b70e511b09ca5fab102aec7ffec6e08bb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 23 Mar 2016 21:43:01 -0400 Subject: [PATCH 8/9] Eliminate yield_hop_t, therefore yield_base type. The whole yield / yield_hop dichotomy becomes much easier to read and explain if we stick to a single yield_t class. Since the intention is for a consumer to pass canonical instances rather than manipulating that class in any other way, we can instantiate it however we want. This gets rid of lots of ugly redundant boost::asio::handler_type<> specializations. --- examples/asio/detail/yield.hpp | 50 +++++++++------------------------- examples/asio/yield.hpp | 34 ++++++++--------------- 2 files changed, 25 insertions(+), 59 deletions(-) diff --git a/examples/asio/detail/yield.hpp b/examples/asio/detail/yield.hpp index 1c9a6f81..7e949f78 100644 --- a/examples/asio/detail/yield.hpp +++ b/examples/asio/detail/yield.hpp @@ -54,11 +54,11 @@ struct yield_completion { // should be instantiated only once. class yield_handler_base { public: - yield_handler_base( yield_base const& y) : + yield_handler_base( yield_t const& y) : // capture the context* associated with the running fiber ctx_( boost::fibers::context::active() ), - // capture the passed yield_base - yb_( y ) + // capture the passed yield_t + yt_( y ) {} // completion callback passing only (error_code) @@ -67,7 +67,7 @@ public: BOOST_ASSERT_MSG( ycomp_, "Must inject yield_completion* " "before calling yield_handler_base::operator()()"); - BOOST_ASSERT_MSG( yb_.ec_, + BOOST_ASSERT_MSG( yt_.ec_, "Must inject boost::system::error_code* " "before calling yield_handler_base::operator()()"); // If originating fiber is busy testing completed_ flag, wait until it @@ -77,13 +77,13 @@ public: // suspend. ycomp_->completed_ = true; // set the error_code bound by yield_t - * yb_.ec_ = ec; + * yt_.ec_ = ec; // Are we permitted to wake up the suspended fiber on this thread, the // thread that called the completion handler? - if ( (! ctx_->is_context( fibers::type::pinned_context )) && yb_.allow_hop_ ) { + if ( (! ctx_->is_context( fibers::type::pinned_context )) && yt_.allow_hop_ ) { // We must not migrate a pinned_context to another thread. If this - // isn't a pinned_context, and the application passed yield_hop_t - // rather than yield_t, migrate this fiber to the running thread. + // isn't a pinned_context, and the application passed yield_hop + // rather than yield, migrate this fiber to the running thread. fibers::context::active()->migrate( ctx_); } // either way, wake the fiber @@ -92,7 +92,7 @@ public: //private: boost::fibers::context * ctx_; - yield_base yb_; + yield_t yt_; // We depend on this pointer to yield_completion, which will be injected // by async_result. yield_completion * ycomp_{ nullptr }; @@ -107,7 +107,7 @@ template< typename T > class yield_handler: public yield_handler_base { public: // asio passes the completion token to the handler constructor - explicit yield_handler( yield_base const& y) : + explicit yield_handler( yield_t const& y) : yield_handler_base( y) {} @@ -142,7 +142,7 @@ public: template<> class yield_handler< void >: public yield_handler_base { public: - explicit yield_handler( yield_base const& y) : + explicit yield_handler( yield_t const& y) : yield_handler_base( y) {} @@ -175,8 +175,8 @@ public: // error_code* point to an error_code local to this object so // yield_handler_base::operator() can unconditionally store through // its error_code* - if ( ! h.yb_.ec_) { - h.yb_.ec_ = & ec_; + if ( ! h.yt_.ec_) { + h.yt_.ec_ = & ec_; } } @@ -255,42 +255,24 @@ public: // When 'yield' is passed as a completion handler which accepts no parameters, // use yield_handler. template< typename ReturnType > -struct handler_type< fibers::asio::yield_base, ReturnType() > -{ typedef fibers::asio::detail::yield_handler< void > type; }; -template< typename ReturnType > struct handler_type< fibers::asio::yield_t, ReturnType() > { typedef fibers::asio::detail::yield_handler< void > type; }; -template< typename ReturnType > -struct handler_type< fibers::asio::yield_hop_t, ReturnType() > -{ typedef fibers::asio::detail::yield_handler< void > type; }; // Handler type specialisation for fibers::asio::yield. // When 'yield' is passed as a completion handler which accepts a data // parameter, use yield_handler to return that parameter to // the caller. template< typename ReturnType, typename Arg1 > -struct handler_type< fibers::asio::yield_base, ReturnType( Arg1) > -{ typedef fibers::asio::detail::yield_handler< Arg1 > type; }; -template< typename ReturnType, typename Arg1 > struct handler_type< fibers::asio::yield_t, ReturnType( Arg1) > { typedef fibers::asio::detail::yield_handler< Arg1 > type; }; -template< typename ReturnType, typename Arg1 > -struct handler_type< fibers::asio::yield_hop_t, ReturnType( Arg1) > -{ typedef fibers::asio::detail::yield_handler< Arg1 > type; }; // Handler type specialisation for fibers::asio::yield. // When 'yield' is passed as a completion handler which accepts only // error_code, use yield_handler. yield_handler will take care of the // error_code one way or another. template< typename ReturnType > -struct handler_type< fibers::asio::yield_base, ReturnType( boost::system::error_code) > -{ typedef fibers::asio::detail::yield_handler< void > type; }; -template< typename ReturnType > struct handler_type< fibers::asio::yield_t, ReturnType( boost::system::error_code) > { typedef fibers::asio::detail::yield_handler< void > type; }; -template< typename ReturnType > -struct handler_type< fibers::asio::yield_hop_t, ReturnType( boost::system::error_code) > -{ typedef fibers::asio::detail::yield_handler< void > type; }; // Handler type specialisation for fibers::asio::yield. // When 'yield' is passed as a completion handler which accepts a data @@ -298,14 +280,8 @@ struct handler_type< fibers::asio::yield_hop_t, ReturnType( boost::system::error // just the parameter to the caller. yield_handler will take care of the // error_code one way or another. template< typename ReturnType, typename Arg2 > -struct handler_type< fibers::asio::yield_base, ReturnType( boost::system::error_code, Arg2) > -{ typedef fibers::asio::detail::yield_handler< Arg2 > type; }; -template< typename ReturnType, typename Arg2 > struct handler_type< fibers::asio::yield_t, ReturnType( boost::system::error_code, Arg2) > { typedef fibers::asio::detail::yield_handler< Arg2 > type; }; -template< typename ReturnType, typename Arg2 > -struct handler_type< fibers::asio::yield_hop_t, ReturnType( boost::system::error_code, Arg2) > -{ typedef fibers::asio::detail::yield_handler< Arg2 > type; }; } // namespace asio } // namespace boost diff --git a/examples/asio/yield.hpp b/examples/asio/yield.hpp index 6705cfcd..5728e2a7 100644 --- a/examples/asio/yield.hpp +++ b/examples/asio/yield.hpp @@ -13,8 +13,6 @@ #ifndef BOOST_FIBERS_ASIO_YIELD_HPP #define BOOST_FIBERS_ASIO_YIELD_HPP -#include - #include #ifdef BOOST_HAS_ABI_HEADERS @@ -25,24 +23,26 @@ namespace boost { namespace fibers { namespace asio { -class yield_base { +class yield_t { public: - constexpr yield_base() = default; + yield_t(bool hop): + allow_hop_( hop) + {} /** * @code - * static yield_base yield; + * static yield_t yield; * boost::system::error_code myec; * func(yield[myec]); * @endcode - * @c yield[myec] returns an instance of @c yield_base whose @c ec_ points + * @c yield[myec] returns an instance of @c yield_t whose @c ec_ points * to @c myec. The expression @c yield[myec] "binds" @c myec to that - * (anonymous) @c yield_base instance, instructing @c func() to store any + * (anonymous) @c yield_t instance, instructing @c func() to store any * @c error_code it might produce into @c myec rather than throwing @c * boost::system::system_error. */ - yield_base operator[]( boost::system::error_code & ec) const { - yield_base tmp{ *this }; + yield_t operator[]( boost::system::error_code & ec) const { + yield_t tmp{ *this }; tmp.ec_ = & ec; return tmp; } @@ -52,23 +52,13 @@ public: boost::system::error_code * ec_{ nullptr }; // allow calling fiber to "hop" to another thread if it could resume more // quickly that way - bool allow_hop_{ false }; -}; - -class yield_t : public yield_base { -}; - -class yield_hop_t : public yield_base { -public: - yield_hop_t() { - allow_hop_ = true; - } + bool allow_hop_; }; // canonical instance with allow_hop_ == false -thread_local yield_t yield{}; +thread_local yield_t yield(false); // canonical instance with allow_hop_ == true -thread_local yield_hop_t yield_hop{}; +thread_local yield_t yield_hop(true); }}} From 46c696f5f6bfc3c844c68d7f30693a206433e93f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 23 Mar 2016 21:51:34 -0400 Subject: [PATCH 9/9] Eliminate obsolete promise_completion_token.hpp header. This was used in an earlier implementation of use_future (no longer present) and fibers::asio::yield (completely reimplemented). --- examples/asio/promise_completion_token.hpp | 86 ---------------------- 1 file changed, 86 deletions(-) delete mode 100644 examples/asio/promise_completion_token.hpp diff --git a/examples/asio/promise_completion_token.hpp b/examples/asio/promise_completion_token.hpp deleted file mode 100644 index 9ea5349a..00000000 --- a/examples/asio/promise_completion_token.hpp +++ /dev/null @@ -1,86 +0,0 @@ -// -// promise_completion_token.hpp -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// modified by Oliver Kowalke and Nat Goodspeed -// - -#ifndef BOOST_FIBERS_ASIO_PROMISE_COMPLETION_TOKEN_HPP -#define BOOST_FIBERS_ASIO_PROMISE_COMPLETION_TOKEN_HPP - -#include - -#ifdef BOOST_HAS_ABI_HEADERS -# include BOOST_ABI_PREFIX -#endif - -namespace boost { -namespace fibers { -namespace asio { - -/// Common base class for yield_t and use_future_t. See also yield.hpp and -/// use_future.hpp. -/** - * The awkward name of this class is because it's not intended to be used - * directly in user code: it's the common base class for a couple of user- - * facing placeholder classes yield_t and use_future_t. They - * share a common handler class promise_handler. - * - * Each subclass (e.g. use_future_t) has a canonical instance - * (use_future). These may be used in the following ways as a - * Boost.Asio asynchronous operation completion token: - * - *
- *
boost::fibers::asio::use_future
- *
This is the canonical instance of use_future_t, provided - * solely for convenience. It causes promise_handler to allocate its - * internal boost::fibers::promise using a default-constructed - * default allocator (std::allocator).
- *
boost::fibers::asio::use_future::with(alloc_instance)
- *
This usage specifies an alternate allocator instance - * alloc_instance. It causes promise_handler to allocate its - * internal boost::fibers::promise using the specified - * allocator.
- *
- */ -//[fibers_asio_promise_completion_token -template< typename Allocator > -class promise_completion_token { -public: - typedef Allocator allocator_type; - - /// Construct using default-constructed allocator. - constexpr promise_completion_token() = default; - - /// Construct using specified allocator. - explicit promise_completion_token( Allocator const& allocator) noexcept : - ec_{ nullptr }, - allocator_{ allocator } { - } - - /// Obtain allocator. - allocator_type get_allocator() const { - return allocator_; - } - -//private: - // used by some subclasses to bind an error_code to suppress exceptions - boost::system::error_code * ec_{ nullptr }; - -private: - Allocator allocator_{}; -}; -//] - -}}} - -#ifdef BOOST_HAS_ABI_HEADERS -# include BOOST_ABI_SUFFIX -#endif - -#endif // BOOST_FIBERS_ASIO_PROMISE_COMPLETION_TOKEN_HPP