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