callbacks.qbk 14 KB

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