• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //          Copyright Oliver Kowalke, Nat Goodspeed 2015.
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          http://www.boost.org/LICENSE_1_0.txt)
5 
6 // modified for boost.asio >= 1.70
7 
8 #ifndef BOOST_FIBERS_ASIO_DETAIL_YIELD_HPP
9 #define BOOST_FIBERS_ASIO_DETAIL_YIELD_HPP
10 
11 #include <boost/asio/async_result.hpp>
12 #include <boost/asio/detail/config.hpp>
13 #include <boost/assert.hpp>
14 #include <boost/atomic.hpp>
15 #include <boost/intrusive_ptr.hpp>
16 #include <boost/system/error_code.hpp>
17 #include <boost/system/system_error.hpp>
18 #include <boost/throw_exception.hpp>
19 
20 #include <boost/fiber/all.hpp>
21 
22 #include <mutex>                    // std::unique_lock
23 
24 #ifdef BOOST_HAS_ABI_HEADERS
25 #  include BOOST_ABI_PREFIX
26 #endif
27 
28 namespace boost {
29 namespace fibers {
30 namespace asio {
31 namespace detail {
32 
33 //[fibers_asio_yield_completion
34 // Bundle a completion bool flag with a spinlock to protect it.
35 struct yield_completion {
36     enum state_t {
37         init,
38         waiting,
39         complete
40     };
41 
42     typedef fibers::detail::spinlock                    mutex_t;
43     typedef std::unique_lock< mutex_t >                 lock_t;
44     typedef boost::intrusive_ptr< yield_completion >    ptr_t;
45 
46     std::atomic< std::size_t >  use_count_{ 0 };
47     mutex_t                     mtx_{};
48     state_t                     state_{ init };
49 
waitboost::fibers::asio::detail::yield_completion50     void wait() {
51         // yield_handler_base::operator()() will set state_ `complete` and
52         // attempt to wake a suspended fiber. It would be Bad if that call
53         // happened between our detecting (complete != state_) and suspending.
54         lock_t lk{ mtx_ };
55         // If state_ is already set, we're done here: don't suspend.
56         if ( complete != state_) {
57             state_ = waiting;
58             // suspend(unique_lock<spinlock>) unlocks the lock in the act of
59             // resuming another fiber
60             fibers::context::active()->suspend( lk);
61         }
62     }
63 
intrusive_ptr_add_ref(yield_completion * yc)64     friend void intrusive_ptr_add_ref( yield_completion * yc) noexcept {
65         BOOST_ASSERT( nullptr != yc);
66         yc->use_count_.fetch_add( 1, std::memory_order_relaxed);
67     }
68 
intrusive_ptr_release(yield_completion * yc)69     friend void intrusive_ptr_release( yield_completion * yc) noexcept {
70         BOOST_ASSERT( nullptr != yc);
71         if ( 1 == yc->use_count_.fetch_sub( 1, std::memory_order_release) ) {
72             std::atomic_thread_fence( std::memory_order_acquire);
73             delete yc;
74         }
75     }
76 };
77 //]
78 
79 //[fibers_asio_yield_handler_base
80 // This class encapsulates common elements between yield_handler<T> (capturing
81 // a value to return from asio async function) and yield_handler<void> (no
82 // such value). See yield_handler<T> and its <void> specialization below. Both
83 // yield_handler<T> and yield_handler<void> are passed by value through
84 // various layers of asio functions. In other words, they're potentially
85 // copied multiple times. So key data such as the yield_completion instance
86 // must be stored in our async_result<yield_handler<>> specialization, which
87 // should be instantiated only once.
88 class yield_handler_base {
89 public:
yield_handler_base(yield_t const & y)90     yield_handler_base( yield_t const& y) :
91         // capture the context* associated with the running fiber
92         ctx_{ boost::fibers::context::active() },
93         // capture the passed yield_t
94         yt_( y ) {
95     }
96 
97     // completion callback passing only (error_code)
operator ()(boost::system::error_code const & ec)98     void operator()( boost::system::error_code const& ec) {
99         BOOST_ASSERT_MSG( ycomp_,
100                           "Must inject yield_completion* "
101                           "before calling yield_handler_base::operator()()");
102         BOOST_ASSERT_MSG( yt_.ec_,
103                           "Must inject boost::system::error_code* "
104                           "before calling yield_handler_base::operator()()");
105         // If originating fiber is busy testing state_ flag, wait until it
106         // has observed (completed != state_).
107         yield_completion::lock_t lk{ ycomp_->mtx_ };
108         yield_completion::state_t state = ycomp_->state_;
109         // Notify a subsequent yield_completion::wait() call that it need not
110         // suspend.
111         ycomp_->state_ = yield_completion::complete;
112         // set the error_code bound by yield_t
113         * yt_.ec_ = ec;
114         // unlock the lock that protects state_
115         lk.unlock();
116         // If ctx_ is still active, e.g. because the async operation
117         // immediately called its callback (this method!) before the asio
118         // async function called async_result_base::get(), we must not set it
119         // ready.
120         if ( yield_completion::waiting == state) {
121             // wake the fiber
122             fibers::context::active()->schedule( ctx_);
123         }
124     }
125 
126 //private:
127     boost::fibers::context      *   ctx_;
128     yield_t                         yt_;
129     // We depend on this pointer to yield_completion, which will be injected
130     // by async_result.
131     yield_completion::ptr_t         ycomp_{};
132 };
133 //]
134 
135 //[fibers_asio_yield_handler_T
136 // asio uses handler_type<completion token type, signature>::type to decide
137 // what to instantiate as the actual handler. Below, we specialize
138 // handler_type< yield_t, ... > to indicate yield_handler<>. So when you pass
139 // an instance of yield_t as an asio completion token, asio selects
140 // yield_handler<> as the actual handler class.
141 template< typename T >
142 class yield_handler: public yield_handler_base {
143 public:
144     // asio passes the completion token to the handler constructor
yield_handler(yield_t const & y)145     explicit yield_handler( yield_t const& y) :
146         yield_handler_base{ y } {
147     }
148 
149     // completion callback passing only value (T)
operator ()(T t)150     void operator()( T t) {
151         // just like callback passing success error_code
152         (*this)( boost::system::error_code(), std::move(t) );
153     }
154 
155     // completion callback passing (error_code, T)
operator ()(boost::system::error_code const & ec,T t)156     void operator()( boost::system::error_code const& ec, T t) {
157         BOOST_ASSERT_MSG( value_,
158                           "Must inject value ptr "
159                           "before caling yield_handler<T>::operator()()");
160         // move the value to async_result<> instance BEFORE waking up a
161         // suspended fiber
162         * value_ = std::move( t);
163         // forward the call to base-class completion handler
164         yield_handler_base::operator()( ec);
165     }
166 
167 //private:
168     // pointer to destination for eventual value
169     // this must be injected by async_result before operator()() is called
170     T   *   value_{ nullptr };
171 };
172 //]
173 
174 //[fibers_asio_yield_handler_void
175 // yield_handler<void> is like yield_handler<T> without value_. In fact it's
176 // just like yield_handler_base.
177 template<>
178 class yield_handler< void >: public yield_handler_base {
179 public:
yield_handler(yield_t const & y)180     explicit yield_handler( yield_t const& y) :
181         yield_handler_base{ y } {
182     }
183 
184     // nullary completion callback
operator ()()185     void operator()() {
186         ( * this)( boost::system::error_code() );
187     }
188 
189     // inherit operator()(error_code) overload from base class
190     using yield_handler_base::operator();
191 };
192 //]
193 
194 // Specialize asio_handler_invoke hook to ensure that any exceptions thrown
195 // from the handler are propagated back to the caller
196 template< typename Fn, typename T >
asio_handler_invoke(Fn && fn,yield_handler<T> *)197 void asio_handler_invoke( Fn&& fn, yield_handler< T > *) {
198         fn();
199 }
200 
201 //[fibers_asio_async_result_base
202 // Factor out commonality between async_result<yield_handler<T>> and
203 // async_result<yield_handler<void>>
204 class async_result_base {
205 public:
async_result_base(yield_handler_base & h)206     explicit async_result_base( yield_handler_base & h) :
207             ycomp_{ new yield_completion{} } {
208         // Inject ptr to our yield_completion instance into this
209         // yield_handler<>.
210         h.ycomp_ = this->ycomp_;
211         // if yield_t didn't bind an error_code, make yield_handler_base's
212         // error_code* point to an error_code local to this object so
213         // yield_handler_base::operator() can unconditionally store through
214         // its error_code*
215         if ( ! h.yt_.ec_) {
216             h.yt_.ec_ = & ec_;
217         }
218     }
219 
get()220     void get() {
221         // Unless yield_handler_base::operator() has already been called,
222         // suspend the calling fiber until that call.
223         ycomp_->wait();
224         // The only way our own ec_ member could have a non-default value is
225         // if our yield_handler did not have a bound error_code AND the
226         // completion callback passed a non-default error_code.
227         if ( ec_) {
228             throw_exception( boost::system::system_error{ ec_ } );
229         }
230     }
231 
232 private:
233     // If yield_t does not bind an error_code instance, store into here.
234     boost::system::error_code       ec_{};
235     yield_completion::ptr_t         ycomp_;
236 };
237 //]
238 
239 }}}}
240 
241 namespace boost {
242 namespace asio {
243 
244 //[fibers_asio_async_result_T
245 // asio constructs an async_result<> instance from the yield_handler specified
246 // by handler_type<>::type. A particular asio async method constructs the
247 // yield_handler, constructs this async_result specialization from it, then
248 // returns the result of calling its get() method.
249 template< typename ReturnType, typename T >
250 class async_result< boost::fibers::asio::yield_t, ReturnType(boost::system::error_code, T) > :
251     public boost::fibers::asio::detail::async_result_base {
252 public:
253     // type returned by get()
254     using return_type = T;
255     using completion_handler_type = fibers::asio::detail::yield_handler<T>;
256 
async_result(boost::fibers::asio::detail::yield_handler<T> & h)257     explicit async_result( boost::fibers::asio::detail::yield_handler< T > & h) :
258         boost::fibers::asio::detail::async_result_base{ h } {
259         // Inject ptr to our value_ member into yield_handler<>: result will
260         // be stored here.
261         h.value_ = & value_;
262     }
263 
264     // asio async method returns result of calling get()
get()265     return_type get() {
266         boost::fibers::asio::detail::async_result_base::get();
267         return std::move( value_);
268     }
269 
270 private:
271     return_type value_{};
272 };
273 //]
274 
275 //[fibers_asio_async_result_void
276 // Without the need to handle a passed value, our yield_handler<void>
277 // specialization is just like async_result_base.
278 template<>
279 class async_result< boost::fibers::asio::yield_t, void(boost::system::error_code) > :
280     public boost::fibers::asio::detail::async_result_base {
281 public:
282     using return_type = void;
283     using completion_handler_type = fibers::asio::detail::yield_handler<void>;
284 
async_result(boost::fibers::asio::detail::yield_handler<void> & h)285     explicit async_result( boost::fibers::asio::detail::yield_handler< void > & h):
286         boost::fibers::asio::detail::async_result_base{ h } {
287     }
288 };
289 //]
290 
291 }}
292 
293 #ifdef BOOST_HAS_ABI_HEADERS
294 #  include BOOST_ABI_SUFFIX
295 #endif
296 
297 #endif // BOOST_FIBERS_ASIO_DETAIL_YIELD_HPP
298