123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- // Copyright Oliver Kowalke, Nat Goodspeed 2015.
- // 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)
- #ifndef BOOST_FIBERS_ASIO_DETAIL_YIELD_HPP
- #define BOOST_FIBERS_ASIO_DETAIL_YIELD_HPP
- #include <boost/asio/async_result.hpp>
- #include <boost/asio/detail/config.hpp>
- #include <boost/asio/handler_type.hpp>
- #include <boost/assert.hpp>
- #include <boost/atomic.hpp>
- #include <boost/intrusive_ptr.hpp>
- #include <boost/system/error_code.hpp>
- #include <boost/system/system_error.hpp>
- #include <boost/throw_exception.hpp>
- #include <boost/fiber/all.hpp>
- #include <mutex> // std::unique_lock
- #ifdef BOOST_HAS_ABI_HEADERS
- # include BOOST_ABI_PREFIX
- #endif
- namespace boost {
- namespace fibers {
- namespace asio {
- namespace detail {
- //[fibers_asio_yield_completion
- // Bundle a completion bool flag with a spinlock to protect it.
- struct yield_completion {
- enum state_t {
- init,
- waiting,
- complete
- };
- typedef fibers::detail::spinlock mutex_t;
- typedef std::unique_lock< mutex_t > lock_t;
- typedef boost::intrusive_ptr< yield_completion > ptr_t;
- std::atomic< std::size_t > use_count_{ 0 };
- mutex_t mtx_{};
- state_t state_{ init };
- void wait() {
- // yield_handler_base::operator()() will set state_ `complete` and
- // attempt to wake a suspended fiber. It would be Bad if that call
- // happened between our detecting (complete != state_) and suspending.
- lock_t lk{ mtx_ };
- // If state_ is already set, we're done here: don't suspend.
- if ( complete != state_) {
- state_ = waiting;
- // suspend(unique_lock<spinlock>) unlocks the lock in the act of
- // resuming another fiber
- fibers::context::active()->suspend( lk);
- }
- }
- friend void intrusive_ptr_add_ref( yield_completion * yc) noexcept {
- BOOST_ASSERT( nullptr != yc);
- yc->use_count_.fetch_add( 1, std::memory_order_relaxed);
- }
- friend void intrusive_ptr_release( yield_completion * yc) noexcept {
- BOOST_ASSERT( nullptr != yc);
- if ( 1 == yc->use_count_.fetch_sub( 1, std::memory_order_release) ) {
- std::atomic_thread_fence( std::memory_order_acquire);
- delete yc;
- }
- }
- };
- //]
- //[fibers_asio_yield_handler_base
- // 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_base( yield_t const& y) :
- // capture the context* associated with the running fiber
- ctx_{ boost::fibers::context::active() },
- // 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 state_ flag, wait until it
- // has observed (completed != state_).
- yield_completion::lock_t lk{ ycomp_->mtx_ };
- yield_completion::state_t state = ycomp_->state_;
- // Notify a subsequent yield_completion::wait() call that it need not
- // suspend.
- ycomp_->state_ = yield_completion::complete;
- // set the error_code bound by yield_t
- * yt_.ec_ = ec;
- // unlock the lock that protects state_
- lk.unlock();
- // If ctx_ is still active, e.g. because the async operation
- // immediately called its callback (this method!) before the asio
- // async function called async_result_base::get(), we must not set it
- // ready.
- if ( yield_completion::waiting == state) {
- // wake the fiber
- fibers::context::active()->schedule( ctx_);
- }
- }
- //private:
- boost::fibers::context * ctx_;
- yield_t yt_;
- // We depend on this pointer to yield_completion, which will be injected
- // by async_result.
- yield_completion::ptr_t ycomp_{};
- };
- //]
- //[fibers_asio_yield_handler_T
- // 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 };
- };
- //]
- //[fibers_asio_yield_handler_void
- // 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
- // from the handler are propagated back to the caller
- template< typename Fn, typename T >
- void asio_handler_invoke( Fn&& fn, yield_handler< T > *) {
- fn();
- }
- //[fibers_asio_async_result_base
- // 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) :
- ycomp_{ new yield_completion{} } {
- // 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_ } );
- }
- }
- private:
- // If yield_t does not bind an error_code instance, store into here.
- boost::system::error_code ec_{};
- yield_completion::ptr_t ycomp_;
- };
- //]
- }}}}
- namespace boost {
- namespace asio {
- //[fibers_asio_async_result_T
- // 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 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) :
- 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_;
- }
- // asio async method returns result of calling get()
- type get() {
- boost::fibers::asio::detail::async_result_base::get();
- return std::move( value_);
- }
- private:
- type value_{};
- };
- //]
- //[fibers_asio_async_result_void
- // 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 > > :
- public boost::fibers::asio::detail::async_result_base {
- public:
- typedef void type;
- explicit async_result( boost::fibers::asio::detail::yield_handler< void > & h):
- boost::fibers::asio::detail::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<void>.
- template< typename ReturnType >
- struct handler_type< fibers::asio::yield_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<parameter type> to return that parameter to
- // the caller.
- template< typename ReturnType, typename Arg1 >
- struct handler_type< fibers::asio::yield_t, ReturnType( Arg1) >
- { typedef fibers::asio::detail::yield_handler< Arg1 > type; };
- //[asio_handler_type
- // 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< fibers::asio::yield_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
- // 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< fibers::asio::yield_t, ReturnType( boost::system::error_code, Arg2) >
- { typedef fibers::asio::detail::yield_handler< Arg2 > type; };
- }}
- #ifdef BOOST_HAS_ABI_HEADERS
- # include BOOST_ABI_SUFFIX
- #endif
- #endif // BOOST_FIBERS_ASIO_DETAIL_YIELD_HPP
|