• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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