1[/ 2 Copyright Oliver Kowalke, Nat Goodspeed 2015. 3 Distributed under the Boost Software License, Version 1.0. 4 (See accompanying file LICENSE_1_0.txt or copy at 5 http://www.boost.org/LICENSE_1_0.txt 6] 7 8[/ import path is relative to this .qbk file] 9[import ../examples/adapt_callbacks.cpp] 10[import ../examples/adapt_method_calls.cpp] 11 12[#callbacks] 13[section:callbacks Integrating Fibers with Asynchronous Callbacks] 14 15[section Overview] 16 17One of the primary benefits of __boost_fiber__ is the ability to use 18asynchronous operations for efficiency, while at the same time structuring the 19calling code ['as if] the operations were synchronous. Asynchronous operations 20provide completion notification in a variety of ways, but most involve a 21callback function of some kind. This section discusses tactics for interfacing 22__boost_fiber__ with an arbitrary async operation. 23 24For purposes of illustration, consider the following hypothetical API: 25 26[AsyncAPI] 27 28The significant points about each of `init_write()` and `init_read()` are: 29 30* The `AsyncAPI` method only initiates the operation. It returns immediately, 31 while the requested operation is still pending. 32* The method accepts a callback. When the operation completes, the callback is 33 called with relevant parameters (error code, data if applicable). 34 35We would like to wrap these asynchronous methods in functions that appear 36synchronous by blocking the calling fiber until the operation completes. This 37lets us use the wrapper function[s] return value to deliver relevant data. 38 39[tip [template_link promise] and [template_link future] are your friends here.] 40 41[endsect] 42[section Return Errorcode] 43 44The `AsyncAPI::init_write()` callback passes only an `errorcode`. If we simply 45want the blocking wrapper to return that `errorcode`, this is an extremely 46straightforward use of [template_link promise] and [template_link future]: 47 48[callbacks_write_ec] 49 50All we have to do is: 51 52# Instantiate a `promise<>` of correct type. 53# Obtain its `future<>`. 54# Arrange for the callback to call [member_link promise..set_value]. 55# Block on [member_link future..get]. 56 57[note This tactic for resuming a pending fiber works even if the callback is 58called on a different thread than the one on which the initiating fiber is 59running. In fact, [@../../examples/adapt_callbacks.cpp the example program[s]] 60dummy `AsyncAPI` implementation illustrates that: it simulates async I/O by 61launching a new thread that sleeps briefly and then calls the relevant 62callback.] 63 64[endsect] 65[section Success or Exception] 66 67A wrapper more aligned with modern C++ practice would use an exception, rather 68than an `errorcode`, to communicate failure to its caller. This is 69straightforward to code in terms of `write_ec()`: 70 71[callbacks_write] 72 73The point is that since each fiber has its own stack, you need not repeat 74messy boilerplate: normal encapsulation works. 75 76[endsect] 77[section Return Errorcode or Data] 78 79Things get a bit more interesting when the async operation[s] callback passes 80multiple data items of interest. One approach would be to use `std::pair<>` to 81capture both: 82 83[callbacks_read_ec] 84 85Once you bundle the interesting data in `std::pair<>`, the code is effectively 86identical to `write_ec()`. You can call it like this: 87 88[callbacks_read_ec_call] 89 90[endsect] 91[#Data_or_Exception] 92[section Data or Exception] 93 94But a more natural API for a function that obtains data is to return only the 95data on success, throwing an exception on error. 96 97As with `write()` above, it[s] certainly possible to code a `read()` wrapper in 98terms of `read_ec()`. But since a given application is unlikely to need both, 99let[s] code `read()` from scratch, leveraging [member_link 100promise..set_exception]: 101 102[callbacks_read] 103 104[member_link future..get] will do the right thing, either returning 105`std::string` or throwing an exception. 106 107[endsect] 108[section Success/Error Virtual Methods] 109 110One classic approach to completion notification is to define an abstract base 111class with `success()` and `error()` methods. Code wishing to perform async 112I/O must derive a subclass, override each of these methods and pass the async 113operation a pointer to a subclass instance. The abstract base class might look 114like this: 115 116[Response] 117 118Now the `AsyncAPI` operation might look more like this: 119 120[method_init_read] 121 122We can address this by writing a one-size-fits-all `PromiseResponse`: 123 124[PromiseResponse] 125 126Now we can simply obtain the `future<>` from that `PromiseResponse` and wait 127on its `get()`: 128 129[method_read] 130 131[/ @path link is relative to (eventual) doc/html/index.html, hence ../..] 132The source code above is found in 133[@../../examples/adapt_callbacks.cpp adapt_callbacks.cpp] 134and 135[@../../examples/adapt_method_calls.cpp adapt_method_calls.cpp]. 136 137[endsect] 138[#callbacks_asio] 139[section Then There[s] __boost_asio__] 140 141[import ../examples/asio/yield.hpp] 142[import ../examples/asio/detail/yield.hpp] 143 144Since the simplest form of Boost.Asio asynchronous operation completion token 145is a callback function, we could apply the same tactics for Asio as for our 146hypothetical `AsyncAPI` asynchronous operations. 147 148Fortunately we need not. Boost.Asio incorporates a mechanism[footnote This 149mechanism has been proposed as a conventional way to allow the caller of an 150arbitrary async function to specify completion handling: 151[@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4045.pdf N4045].] 152by which the caller can customize the notification behavior of any async 153operation. Therefore we can construct a ['completion token] which, when passed 154to a __boost_asio__ async operation, requests blocking for the calling fiber. 155 156A typical Asio async function might look something like this:[footnote per [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4045.pdf N4045]] 157 158 template < ..., class CompletionToken > 159 ``['deduced_return_type]`` 160 async_something( ... , CompletionToken&& token) 161 { 162 // construct handler_type instance from CompletionToken 163 handler_type<CompletionToken, ...>::type ``[*[`handler(token)]]``; 164 // construct async_result instance from handler_type 165 async_result<decltype(handler)> ``[*[`result(handler)]]``; 166 167 // ... arrange to call handler on completion ... 168 // ... initiate actual I/O operation ... 169 170 return ``[*[`result.get()]]``; 171 } 172 173We will engage that mechanism, which is based on specializing Asio[s] 174`handler_type<>` template for the `CompletionToken` type and the signature of 175the specific callback. The remainder of this discussion will refer back to 176`async_something()` as the Asio async function under consideration. 177 178The implementation described below uses lower-level facilities than `promise` 179and `future` because the `promise` mechanism interacts badly with 180[@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/io_service/stop.html 181`io_service::stop()`]. It produces `broken_promise` exceptions. 182 183`boost::fibers::asio::yield` is a completion token of this kind. `yield` is an 184instance of `yield_t`: 185 186[fibers_asio_yield_t] 187 188`yield_t` is in fact only a placeholder, a way to trigger Boost.Asio 189customization. It can bind a 190[@http://www.boost.org/doc/libs/release/libs/system/doc/reference.html#Class-error_code 191`boost::system::error_code`] 192for use by the actual handler. 193 194`yield` is declared as: 195 196[fibers_asio_yield] 197 198Asio customization is engaged by specializing 199[@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/handler_type.html 200`boost::asio::handler_type<>`] 201for `yield_t`: 202 203[asio_handler_type] 204 205(There are actually four different specializations in 206[@../../examples/asio/detail/yield.hpp detail/yield.hpp], 207one for each of the four Asio async callback signatures we expect.) 208 209The above directs Asio to use `yield_handler` as the actual handler for an 210async operation to which `yield` is passed. There[s] a generic 211`yield_handler<T>` implementation and a `yield_handler<void>` specialization. 212Let[s] start with the `<void>` specialization: 213 214[fibers_asio_yield_handler_void] 215 216`async_something()`, having consulted the `handler_type<>` traits 217specialization, instantiates a `yield_handler<void>` to be passed as the 218actual callback for the async operation. `yield_handler`[s] constructor accepts 219the `yield_t` instance (the `yield` object passed to the async function) and 220passes it along to `yield_handler_base`: 221 222[fibers_asio_yield_handler_base] 223 224`yield_handler_base` stores a copy of the `yield_t` instance [mdash] which, as 225shown above, contains only an `error_code*`. It also captures the 226[class_link context]* for the currently-running fiber by calling [member_link 227context..active]. 228 229You will notice that `yield_handler_base` has one more data member (`ycomp_`) 230that is initialized to `nullptr` by its constructor [mdash] though its 231`operator()()` method relies on `ycomp_` being non-null. More on this in a 232moment. 233 234Having constructed the `yield_handler<void>` instance, `async_something()` 235goes on to construct an `async_result` specialized for the 236`handler_type<>::type`: in this case, `async_result<yield_handler<void>>`. It 237passes the `yield_handler<void>` instance to the new `async_result` instance. 238 239[fibers_asio_async_result_void] 240 241Naturally that leads us straight to `async_result_base`: 242 243[fibers_asio_async_result_base] 244 245This is how `yield_handler_base::ycomp_` becomes non-null: 246`async_result_base`[s] constructor injects a pointer back to its own 247`yield_completion` member. 248 249Recall that the canonical `yield_t` instance `yield` initializes its 250`error_code*` member `ec_` to `nullptr`. If this instance is passed to 251`async_something()` (`ec_` is still `nullptr`), the copy stored in 252`yield_handler_base` will likewise have null `ec_`. `async_result_base`[s] 253constructor sets `yield_handler_base`[s] `yield_t`[s] `ec_` member to point to 254its own `error_code` member. 255 256The stage is now set. `async_something()` initiates the actual async 257operation, arranging to call its `yield_handler<void>` instance on completion. 258Let[s] say, for the sake of argument, that the actual async operation[s] 259callback has signature `void(error_code)`. 260 261But since it[s] an async operation, control returns at once to 262`async_something()`. `async_something()` calls 263`async_result<yield_handler<void>>::get()`, and will return its return value. 264 265`async_result<yield_handler<void>>::get()` inherits 266`async_result_base::get()`. 267 268`async_result_base::get()` immediately calls `yield_completion::wait()`. 269 270[fibers_asio_yield_completion] 271 272Supposing that the pending async operation has not yet completed, 273`yield_completion::completed_` will still be `false`, and `wait()` will call 274[member_link context..suspend] on the currently-running fiber. 275 276Other fibers will now have a chance to run. 277 278Some time later, the async operation completes. It calls 279`yield_handler<void>::operator()(error_code const&)` with an `error_code` indicating 280either success or failure. We[,]ll consider both cases. 281 282`yield_handler<void>` explicitly inherits `operator()(error_code const&)` from 283`yield_handler_base`. 284 285`yield_handler_base::operator()(error_code const&)` first sets 286`yield_completion::completed_` `true`. This way, if `async_something()`[s] 287async operation completes immediately [mdash] if 288`yield_handler_base::operator()` is called even before 289`async_result_base::get()` [mdash] the calling fiber will ['not] suspend. 290 291The actual `error_code` produced by the async operation is then stored through 292the stored `yield_t::ec_` pointer. If `async_something()`[s] caller used (e.g.) 293`yield[my_ec]` to bind a local `error_code` instance, the actual `error_code` 294value is stored into the caller[s] variable. Otherwise, it is stored into 295`async_result_base::ec_`. 296 297If the stored fiber context `yield_handler_base::ctx_` is not already running, 298it is marked as ready to run by passing it to [member_link 299context..schedule]. Control then returns from 300`yield_handler_base::operator()`: the callback is done. 301 302In due course, that fiber is resumed. Control returns from [member_link 303context..suspend] to `yield_completion::wait()`, which returns to 304`async_result_base::get()`. 305 306* If the original caller passed `yield[my_ec]` to `async_something()` to bind 307 a local `error_code` instance, then `yield_handler_base::operator()` stored 308 its `error_code` to the caller[s] `my_ec` instance, leaving 309 `async_result_base::ec_` initialized to success. 310* If the original caller passed `yield` to `async_something()` without binding 311 a local `error_code` variable, then `yield_handler_base::operator()` stored 312 its `error_code` into `async_result_base::ec_`. If in fact that `error_code` 313 is success, then all is well. 314* Otherwise [mdash] the original caller did not bind a local `error_code` and 315 `yield_handler_base::operator()` was called with an `error_code` indicating 316 error [mdash] `async_result_base::get()` throws `system_error` with that 317 `error_code`. 318 319The case in which `async_something()`[s] completion callback has signature 320`void()` is similar. `yield_handler<void>::operator()()` invokes the machinery 321above with a ["success] `error_code`. 322 323A completion callback with signature `void(error_code, T)` (that is: in 324addition to `error_code`, callback receives some data item) is handled 325somewhat differently. For this kind of signature, `handler_type<>::type` 326specifies `yield_handler<T>` (for `T` other than `void`). 327 328A `yield_handler<T>` reserves a `value_` pointer to a value of type `T`: 329 330[fibers_asio_yield_handler_T] 331 332This pointer is initialized to `nullptr`. 333 334When `async_something()` instantiates `async_result<yield_handler<T>>`: 335 336[fibers_asio_async_result_T] 337 338this `async_result<>` specialization reserves a member of type `T` to receive 339the passed data item, and sets `yield_handler<T>::value_` to point to its own 340data member. 341 342`async_result<yield_handler<T>>` overrides `get()`. The override calls 343`async_result_base::get()`, so the calling fiber suspends as described above. 344 345`yield_handler<T>::operator()(error_code, T)` stores its passed `T` value into 346`async_result<yield_handler<T>>::value_`. 347 348Then it passes control to `yield_handler_base::operator()(error_code)` to deal 349with waking the original fiber as described above. 350 351When `async_result<yield_handler<T>>::get()` resumes, it returns the stored 352`value_` to `async_something()` and ultimately to `async_something()`[s] 353caller. 354 355The case of a callback signature `void(T)` is handled by having 356`yield_handler<T>::operator()(T)` engage the `void(error_code, T)` machinery, 357passing a ["success] `error_code`. 358 359[/ @path link is relative to (eventual) doc/html/index.html, hence ../..] 360The source code above is found in 361[@../../examples/asio/yield.hpp yield.hpp] and 362[@../../examples/asio/detail/yield.hpp detail/yield.hpp]. 363 364[endsect] 365[endsect] 366