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]] ] diff --git a/doc/scheduling.qbk b/doc/scheduling.qbk index 08260968..9ba57051 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 { @@ -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; @@ -495,20 +498,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] @@ -630,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; diff --git a/doc/stack.qbk b/doc/stack.qbk index 56c17801..0913a343 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,11 +141,11 @@ 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.]] ] - +[#class_fixedsize_stack] [section:fixedsize Class ['fixedsize_stack]] __boost_fiber__ provides the class __fixedsize__ which models @@ -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.]] ] diff --git a/examples/asio/detail/yield.hpp b/examples/asio/detail/yield.hpp index 935574cd..7e949f78 100644 --- a/examples/asio/detail/yield.hpp +++ b/examples/asio/detail/yield.hpp @@ -7,9 +7,12 @@ #include #include #include +#include #include +#include // std::unique_lock + #ifdef BOOST_HAS_ABI_HEADERS # include BOOST_ABI_PREFIX #endif @@ -19,81 +22,138 @@ namespace fibers { namespace asio { namespace detail { -template< typename T > -class yield_handler { -public: - yield_handler( yield_t const& y) : - ctx_( boost::fibers::context::active() ), - ec_( y.ec_) { - } +// Bundle a completion bool flag with a spinlock to protect it. +struct yield_completion { + typedef mutex_t fibers::detail::spinlock; + typedef lock_t std::unique_lock< mutex_t >; - void operator()( T t) { - std::unique_lock< fibers::detail::spinlock > lk( * mtx_); - * completed_ = true; - * ec_ = boost::system::error_code(); - * value_ = std::move( t); - if ( ! ctx_->is_context( fibers::type::pinned_context) ) { - boost::fibers::context::active()->migrate( ctx_); + mutex_t mtx_{}; + bool completed_{ false }; + + void wait() { + // yield_handler_base::operator()() will set completed_ true and + // attempt to wake a suspended fiber. It would be Bad if that call + // happened between our detecting (! completed_) and suspending. + lock_t lk( mtx_); + // If completed_ is already set, we're done here: don't suspend. + if ( ! completed_) { + // suspend(unique_lock) unlocks the lock in the act of + // resuming another fiber + fibers::context::active()->suspend( lk); } - boost::fibers::context::active()->set_ready( ctx_); } - - void operator()( boost::system::error_code const& ec, T t) { - std::unique_lock< fibers::detail::spinlock > lk( * mtx_); - * completed_ = true; - * ec_ = ec; - * value_ = std::move( t); - if ( ! ctx_->is_context( fibers::type::pinned_context) && - boost::fibers::context::active() != ctx_) { - boost::fibers::context::active()->migrate( ctx_); - } - boost::fibers::context::active()->set_ready( ctx_); - } - -//private: - boost::fibers::context * ctx_; - boost::system::error_code * ec_; - T * value_{ nullptr }; - fibers::detail::spinlock * mtx_{ nullptr }; - bool * completed_{ nullptr }; }; -// Completion handler to adapt a void promise as a completion handler. -template<> -class yield_handler< void > -{ +// 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. Both +// yield_handler and yield_handler are passed by value through +// various layers of asio functions. In other words, they're potentially +// copied multiple times. So key data such as the yield_completion instance +// must be stored in our async_result> specialization, which +// should be instantiated only once. +class yield_handler_base { public: - yield_handler( yield_t const& y) : + yield_handler_base( yield_t const& y) : + // capture the context* associated with the running fiber ctx_( boost::fibers::context::active() ), - ec_( y.ec_) { - } - - void operator()() { - std::unique_lock< fibers::detail::spinlock > lk( * mtx_); - * completed_ = true; - * ec_ = boost::system::error_code(); - if ( ! ctx_->is_context( fibers::type::pinned_context) ) { - boost::fibers::context::active()->migrate( ctx_); + // capture the passed yield_t + yt_( y ) + {} + + // completion callback passing only (error_code) + void operator()( boost::system::error_code const& ec) + { + BOOST_ASSERT_MSG( ycomp_, + "Must inject yield_completion* " + "before calling yield_handler_base::operator()()"); + 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 + // has observed (! completed_). + yield_completion::lock_t lk( ycomp_->mtx_); + // Notify a subsequent yield_completion::wait() call that it need not + // suspend. + ycomp_->completed_ = true; + // set the error_code bound by yield_t + * 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 )) && 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 + // rather than yield, migrate this fiber to the running thread. + fibers::context::active()->migrate( ctx_); } - boost::fibers::context::active()->set_ready( ctx_); - } - - void operator()( boost::system::error_code const& ec) { - std::unique_lock< fibers::detail::spinlock > lk( * mtx_); - * completed_ = true; - * ec_ = ec; - if ( ! ctx_->is_context( fibers::type::pinned_context) && - boost::fibers::context::active() != ctx_) { - boost::fibers::context::active()->migrate( ctx_); - } - boost::fibers::context::active()->set_ready( ctx_); + // either way, wake the fiber + fibers::context::active()->set_ready( ctx_); } //private: boost::fibers::context * ctx_; - boost::system::error_code * ec_; - fibers::detail::spinlock * mtx_{ nullptr }; - bool * completed_{ nullptr }; + yield_t yt_; + // We depend on this pointer to yield_completion, which will be injected + // by async_result. + yield_completion * ycomp_{ nullptr }; +}; + +// asio uses handler_type::type to decide +// what to instantiate as the actual handler. Below, we specialize +// handler_type< yield_t, ... > to indicate yield_handler<>. So when you pass +// an instance of yield_t as an asio completion token, asio selects +// yield_handler<> as the actual handler class. +template< typename T > +class yield_handler: public yield_handler_base { +public: + // asio passes the completion token to the handler constructor + explicit yield_handler( yield_t const& y) : + yield_handler_base( y) + {} + + // completion callback passing only value (T) + void operator()( T t) + { + // just like 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) + { + BOOST_ASSERT_MSG( value_, + "Must inject value ptr " + "before caling yield_handler::operator()()"); + // move the value to async_result<> instance BEFORE waking up a + // suspended fiber + * value_ = std::move( t); + // forward the call to base-class completion handler + yield_handler_base::operator()( ec); + } + +//private: + // pointer to destination for eventual value + // this must be injected by async_result before operator()() is called + T * value_{ nullptr }; +}; + +// yield_handler is like yield_handler without value_. In fact it's +// just like yield_handler_base. +template<> +class yield_handler< void >: public yield_handler_base { +public: + explicit yield_handler( yield_t const& y) : + yield_handler_base( y) + {} + + // nullary completion callback + void operator()() + { + (*this)( boost::system::error_code() ); + } + + // inherit operator()(error_code) overload from base class + using yield_handler_base::operator(); }; // Specialize asio_handler_invoke hook to ensure that any exceptions thrown @@ -103,6 +163,44 @@ void asio_handler_invoke( Fn fn, yield_handler< T > * h) { fn(); } +// Factor out commonality between async_result> and +// async_result> +class async_result_base { +public: + explicit async_result_base( yield_handler_base & h) { + // Inject ptr to our yield_completion instance into this + // yield_handler<>. + h->ycomp_ = &this->ycomp_; + // 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.yt_.ec_) { + h.yt_.ec_ = & ec_; + } + } + + void get() { + // Unless yield_handler_base::operator() has already been called, + // suspend the calling fiber until that call. + ycomp_.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(); + } + +private: + // If yield_t does not bind an error_code instance, store into here. + boost::system::error_code ec_{}; + // async_result_base owns the yield_completion because, unlike + // yield_handler<>, async_result<> is only instantiated once. + yield_completion ycomp_{}; +}; + } // namespace detail } // namespace asio } // namespace fibers @@ -111,106 +209,79 @@ void asio_handler_invoke( Fn fn, yield_handler< T > * h) { namespace boost { namespace asio { +// 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 > > { +class async_result< boost::fibers::asio::detail::yield_handler< T > >: + public boost::fibers::asio::detail::async_result_base { public: + // type returned by get() typedef T type; - - explicit async_result( boost::fibers::asio::detail::yield_handler< T > & h) { - out_ec_ = h.ec_; - if ( ! out_ec_) { - h.ec_ = & ec_; - } + + explicit async_result( boost::fibers::asio::detail::yield_handler< T > & h): + boost::fibers::asio::detail::async_result_base( h) + { + // Inject ptr to our value_ member into yield_handler<>: result will + // be stored here. h.value_ = & value_; - h.mtx_ = & mtx_; - h.completed_ = & completed_; } - + + // asio async method returns result of calling get() type get() { - std::unique_lock< fibers::detail::spinlock > lk( mtx_); - if ( ! completed_) { - boost::fibers::context::active()->suspend( lk); - } - //lk.unlock(); - if ( ! out_ec_ && ec_) { - throw_exception( boost::system::system_error( ec_) ); - } - boost::this_fiber::interruption_point(); + boost::fibers::asio::detail::async_result_base::get(); return std::move( value_); } private: - boost::system::error_code * out_ec_{ nullptr }; - boost::system::error_code ec_{}; type value_{}; - fibers::detail::spinlock mtx_{}; - bool completed_{ false }; }; +// 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 > > { +class async_result< boost::fibers::asio::detail::yield_handler< void > >: + public boost::fibers::asio::detail::async_result_base { public: - typedef void type; + typedef void type; - explicit async_result( boost::fibers::asio::detail::yield_handler< void > & h) { - out_ec_ = h.ec_; - if ( ! out_ec_) { - h.ec_ = & ec_; - } - h.mtx_ = & mtx_; - h.completed_ = & completed_; - } - - void get() { - std::unique_lock< fibers::detail::spinlock > lk( mtx_); - if ( ! completed_) { - boost::fibers::context::active()->suspend( lk); - } - //lk.unlock(); - if ( ! out_ec_ && ec_) { - throw_exception( boost::system::system_error( ec_) ); - } - boost::this_fiber::interruption_point(); - } - -private: - boost::system::error_code * out_ec_{ nullptr }; - boost::system::error_code ec_{}; - fibers::detail::spinlock mtx_{}; - bool completed_{ false }; + explicit async_result( boost::fibers::asio::detail::yield_handler< void > & h): + boost::fibers::asio::detail::async_result_base( h) + {} }; -// Handler type specialisation for use_future. +// 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< - boost::fibers::asio::yield_t, - ReturnType() -> -{ typedef boost::fibers::asio::detail::yield_handler< void > type; }; +struct handler_type< fibers::asio::yield_t, ReturnType() > +{ 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, 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_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_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_t, ReturnType( boost::system::error_code, Arg2) > +{ typedef fibers::asio::detail::yield_handler< Arg2 > type; }; } // namespace asio } // namespace boost 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 diff --git a/examples/asio/yield.hpp b/examples/asio/yield.hpp index 66737b1a..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 @@ -27,19 +25,40 @@ namespace asio { class yield_t { public: - constexpr yield_t() = default; + yield_t(bool hop): + allow_hop_( hop) + {} + /** + * @code + * static yield_t yield; + * boost::system::error_code myec; + * func(yield[myec]); + * @endcode + * @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_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_t operator[]( boost::system::error_code & ec) const { - yield_t tmp; + yield_t 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_; }; -thread_local yield_t yield{}; +// canonical instance with allow_hop_ == false +thread_local yield_t yield(false); +// canonical instance with allow_hop_ == true +thread_local yield_t yield_hop(true); }}} 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