mirror of
https://github.com/boostorg/fiber.git
synced 2026-02-19 02:12:24 +00:00
Merge pull request #75 from nat-goodspeed/develop
Sync up with recent doc and Asio integration changes
This commit is contained in:
@@ -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]]
|
||||
]
|
||||
|
||||
|
||||
@@ -418,11 +418,11 @@ of typical STL containers.
|
||||
#include <boost/fiber/context.hpp>
|
||||
|
||||
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;
|
||||
|
||||
@@ -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.]]
|
||||
]
|
||||
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
#include <boost/fiber/all.hpp>
|
||||
|
||||
#include <mutex> // 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<spinlock>) 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<T> (capturing
|
||||
// a value to return from asio async function) and yield_handler<void> (no
|
||||
// such value). See yield_handler<T> and its <void> specialization below. Both
|
||||
// yield_handler<T> and yield_handler<void> 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<yield_handler<>> 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<completion token type, signature>::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<T>::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<void> is like yield_handler<T> 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<yield_handler<T>> and
|
||||
// async_result<yield_handler<void>>
|
||||
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<void>
|
||||
// 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<void>.
|
||||
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<parameter type> 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<void>. 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<parameter type> 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
|
||||
|
||||
@@ -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 <boost/config.hpp>
|
||||
|
||||
#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 <tt>yield_t</tt> and <tt>use_future_t</tt>. They
|
||||
* share a common handler class <tt>promise_handler</tt>.
|
||||
*
|
||||
* Each subclass (e.g. <tt>use_future_t</tt>) has a canonical instance
|
||||
* (<tt>use_future</tt>). These may be used in the following ways as a
|
||||
* Boost.Asio asynchronous operation completion token:
|
||||
*
|
||||
* <dl>
|
||||
* <dt><tt>boost::fibers::asio::use_future</tt></dt>
|
||||
* <dd>This is the canonical instance of <tt>use_future_t</tt>, provided
|
||||
* solely for convenience. It causes <tt>promise_handler</tt> to allocate its
|
||||
* internal <tt>boost::fibers::promise</tt> using a default-constructed
|
||||
* default allocator (<tt>std::allocator<void></tt>).</dd>
|
||||
* <dt><tt>boost::fibers::asio::use_future::with(alloc_instance)</tt></dt>
|
||||
* <dd>This usage specifies an alternate allocator instance
|
||||
* <tt>alloc_instance</tt>. It causes <tt>promise_handler</tt> to allocate its
|
||||
* internal <tt>boost::fibers::promise</tt> using the specified
|
||||
* allocator.</dd>
|
||||
* </dl>
|
||||
*/
|
||||
//[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
|
||||
@@ -13,8 +13,6 @@
|
||||
#ifndef BOOST_FIBERS_ASIO_YIELD_HPP
|
||||
#define BOOST_FIBERS_ASIO_YIELD_HPP
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <boost/config.hpp>
|
||||
|
||||
#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);
|
||||
|
||||
}}}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user