[/ Copyright Oliver Kowalke, Nat Goodspeed 2015. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt ] [/ import path is relative to this .qbk file] [import ../examples/adapt_callbacks.cpp] [import ../examples/adapt_method_calls.cpp] [#callbacks] [section:callbacks Integrating Fibers with Asynchronous Callbacks] [section Overview] One of the primary benefits of __boost_fiber__ is the ability to use asynchronous operations for efficiency, while at the same time structuring the calling code ['as if] the operations were synchronous. Asynchronous operations provide completion notification in a variety of ways, but most involve a callback function of some kind. This section discusses tactics for interfacing __boost_fiber__ with an arbitrary async operation. For purposes of illustration, consider the following hypothetical API: [AsyncAPI] The significant points about each of `init_write()` and `init_read()` are: * The `AsyncAPI` method only initiates the operation. It returns immediately, while the requested operation is still pending. * The method accepts a callback. When the operation completes, the callback is called with relevant parameters (error code, data if applicable). We would like to wrap these asynchronous methods in functions that appear synchronous by blocking the calling fiber until the operation completes. This lets us use the wrapper function[s] return value to deliver relevant data. [tip [template_link promise] and [template_link future] are your friends here.] [endsect] [section Return Errorcode] The `AsyncAPI::init_write()` callback passes only an `errorcode`. If we simply want the blocking wrapper to return that `errorcode`, this is an extremely straightforward use of [template_link promise] and [template_link future]: [callbacks_write_ec] All we have to do is: # Instantiate a `promise<>` of correct type. # Obtain its `future<>`. # Arrange for the callback to call [member_link promise..set_value]. # Block on [member_link future..get]. [note This tactic for resuming a pending fiber works even if the callback is called on a different thread than the one on which the initiating fiber is running. In fact, [@../../examples/adapt_callbacks.cpp the example program[s]] dummy `AsyncAPI` implementation illustrates that: it simulates async I/O by launching a new thread that sleeps briefly and then calls the relevant callback.] [endsect] [section Success or Exception] A wrapper more aligned with modern C++ practice would use an exception, rather than an `errorcode`, to communicate failure to its caller. This is straightforward to code in terms of `write_ec()`: [callbacks_write] The point is that since each fiber has its own stack, you need not repeat messy boilerplate: normal encapsulation works. [endsect] [section Return Errorcode or Data] Things get a bit more interesting when the async operation[s] callback passes multiple data items of interest. One approach would be to use `std::pair<>` to capture both: [callbacks_read_ec] Once you bundle the interesting data in `std::pair<>`, the code is effectively identical to `write_ec()`. You can call it like this: [callbacks_read_ec_call] [endsect] [#Data_or_Exception] [section Data or Exception] But a more natural API for a function that obtains data is to return only the data on success, throwing an exception on error. As with `write()` above, it[s] certainly possible to code a `read()` wrapper in terms of `read_ec()`. But since a given application is unlikely to need both, let[s] code `read()` from scratch, leveraging [member_link promise..set_exception]: [callbacks_read] [member_link future..get] will do the right thing, either returning `std::string` or throwing an exception. [endsect] [section Success/Error Virtual Methods] One classic approach to completion notification is to define an abstract base class with `success()` and `error()` methods. Code wishing to perform async I/O must derive a subclass, override each of these methods and pass the async operation a pointer to a subclass instance. The abstract base class might look like this: [Response] Now the `AsyncAPI` operation might look more like this: [method_init_read] We can address this by writing a one-size-fits-all `PromiseResponse`: [PromiseResponse] Now we can simply obtain the `future<>` from that `PromiseResponse` and wait on its `get()`: [method_read] [/ @path link is relative to (eventual) doc/html/index.html, hence ../..] The source code above is found in [@../../examples/adapt_callbacks.cpp adapt_callbacks.cpp] and [@../../examples/adapt_method_calls.cpp adapt_method_calls.cpp]. [endsect] [#callbacks_asio] [section Then There[s] __boost_asio__] [import ../examples/asio/yield.hpp] [import ../examples/asio/detail/yield.hpp] Since the simplest form of Boost.Asio asynchronous operation completion token is a callback function, we could apply the same tactics for Asio as for our hypothetical `AsyncAPI` asynchronous operations. Fortunately we need not. Boost.Asio incorporates a mechanism[footnote This mechanism has been proposed as a conventional way to allow the caller of an arbitrary async function to specify completion handling: [@http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4045.pdf N4045].] by which the caller can customize the notification behavior of any async operation. Therefore we can construct a ['completion token] which, when passed to a __boost_asio__ async operation, requests blocking for the calling fiber. 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]] template < ..., class CompletionToken > ``['deduced_return_type]`` async_something( ... , CompletionToken&& token) { // construct handler_type instance from CompletionToken handler_type::type ``[*[`handler(token)]]``; // construct async_result instance from handler_type async_result ``[*[`result(handler)]]``; // ... arrange to call handler on completion ... // ... initiate actual I/O operation ... return ``[*[`result.get()]]``; } We will engage that mechanism, which is based on specializing Asio[s] `handler_type<>` template for the `CompletionToken` type and the signature of the specific callback. The remainder of this discussion will refer back to `async_something()` as the Asio async function under consideration. The implementation described below uses lower-level facilities than `promise` and `future` because the `promise` mechanism interacts badly with [@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/io_service/stop.html `io_service::stop()`]. It produces `broken_promise` exceptions. `boost::fibers::asio::yield` is a completion token of this kind. `yield` is an instance of `yield_t`: [fibers_asio_yield_t] `yield_t` is in fact only a placeholder, a way to trigger Boost.Asio customization. It can bind a [@http://www.boost.org/doc/libs/release/libs/system/doc/reference.html#Class-error_code `boost::system::error_code`] for use by the actual handler. `yield` is declared as: [fibers_asio_yield] Asio customization is engaged by specializing [@http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/handler_type.html `boost::asio::handler_type<>`] for `yield_t`: [asio_handler_type] (There are actually four different specializations in [@../../examples/asio/detail/yield.hpp detail/yield.hpp], one for each of the four Asio async callback signatures we expect.) The above directs Asio to use `yield_handler` as the actual handler for an async operation to which `yield` is passed. There[s] a generic `yield_handler` implementation and a `yield_handler` specialization. Let[s] start with the `` specialization: [fibers_asio_yield_handler_void] `async_something()`, having consulted the `handler_type<>` traits specialization, instantiates a `yield_handler` to be passed as the actual callback for the async operation. `yield_handler`[s] constructor accepts the `yield_t` instance (the `yield` object passed to the async function) and passes it along to `yield_handler_base`: [fibers_asio_yield_handler_base] `yield_handler_base` stores a copy of the `yield_t` instance [mdash] which, as shown above, contains only an `error_code*`. It also captures the [class_link context]* for the currently-running fiber by calling [member_link context..active]. You will notice that `yield_handler_base` has one more data member (`ycomp_`) that is initialized to `nullptr` by its constructor [mdash] though its `operator()()` method relies on `ycomp_` being non-null. More on this in a moment. Having constructed the `yield_handler` instance, `async_something()` goes on to construct an `async_result` specialized for the `handler_type<>::type`: in this case, `async_result>`. It passes the `yield_handler` instance to the new `async_result` instance. [fibers_asio_async_result_void] Naturally that leads us straight to `async_result_base`: [fibers_asio_async_result_base] This is how `yield_handler_base::ycomp_` becomes non-null: `async_result_base`[s] constructor injects a pointer back to its own `yield_completion` member. Recall that the canonical `yield_t` instance `yield` initializes its `error_code*` member `ec_` to `nullptr`. If this instance is passed to `async_something()` (`ec_` is still `nullptr`), the copy stored in `yield_handler_base` will likewise have null `ec_`. `async_result_base`[s] constructor sets `yield_handler_base`[s] `yield_t`[s] `ec_` member to point to its own `error_code` member. The stage is now set. `async_something()` initiates the actual async operation, arranging to call its `yield_handler` instance on completion. Let[s] say, for the sake of argument, that the actual async operation[s] callback has signature `void(error_code)`. But since it[s] an async operation, control returns at once to `async_something()`. `async_something()` calls `async_result>::get()`, and will return its return value. `async_result>::get()` inherits `async_result_base::get()`. `async_result_base::get()` immediately calls `yield_completion::wait()`. [fibers_asio_yield_completion] Supposing that the pending async operation has not yet completed, `yield_completion::completed_` will still be `false`, and `wait()` will call [member_link context..suspend] on the currently-running fiber. Other fibers will now have a chance to run. Some time later, the async operation completes. It calls `yield_handler::operator()(error_code const&)` with an `error_code` indicating either success or failure. We[,]ll consider both cases. `yield_handler` explicitly inherits `operator()(error_code const&)` from `yield_handler_base`. `yield_handler_base::operator()(error_code const&)` first sets `yield_completion::completed_` `true`. This way, if `async_something()`[s] async operation completes immediately [mdash] if `yield_handler_base::operator()` is called even before `async_result_base::get()` [mdash] the calling fiber will ['not] suspend. The actual `error_code` produced by the async operation is then stored through the stored `yield_t::ec_` pointer. If `async_something()`[s] caller used (e.g.) `yield[my_ec]` to bind a local `error_code` instance, the actual `error_code` value is stored into the caller[s] variable. Otherwise, it is stored into `async_result_base::ec_`. If the stored fiber context `yield_handler_base::ctx_` is not already running, it is marked as ready to run by passing it to [member_link context..schedule]. Control then returns from `yield_handler_base::operator()`: the callback is done. In due course, that fiber is resumed. Control returns from [member_link context..suspend] to `yield_completion::wait()`, which returns to `async_result_base::get()`. * If the original caller passed `yield[my_ec]` to `async_something()` to bind a local `error_code` instance, then `yield_handler_base::operator()` stored its `error_code` to the caller[s] `my_ec` instance, leaving `async_result_base::ec_` initialized to success. * If the original caller passed `yield` to `async_something()` without binding a local `error_code` variable, then `yield_handler_base::operator()` stored its `error_code` into `async_result_base::ec_`. If in fact that `error_code` is success, then all is well. * Otherwise [mdash] the original caller did not bind a local `error_code` and `yield_handler_base::operator()` was called with an `error_code` indicating error [mdash] `async_result_base::get()` throws `system_error` with that `error_code`. The case in which `async_something()`[s] completion callback has signature `void()` is similar. `yield_handler::operator()()` invokes the machinery above with a ["success] `error_code`. A completion callback with signature `void(error_code, T)` (that is: in addition to `error_code`, callback receives some data item) is handled somewhat differently. For this kind of signature, `handler_type<>::type` specifies `yield_handler` (for `T` other than `void`). A `yield_handler` reserves a `value_` pointer to a value of type `T`: [fibers_asio_yield_handler_T] This pointer is initialized to `nullptr`. When `async_something()` instantiates `async_result>`: [fibers_asio_async_result_T] this `async_result<>` specialization reserves a member of type `T` to receive the passed data item, and sets `yield_handler::value_` to point to its own data member. `async_result>` overrides `get()`. The override calls `async_result_base::get()`, so the calling fiber suspends as described above. `yield_handler::operator()(error_code, T)` stores its passed `T` value into `async_result>::value_`. Then it passes control to `yield_handler_base::operator()(error_code)` to deal with waking the original fiber as described above. When `async_result>::get()` resumes, it returns the stored `value_` to `async_something()` and ultimately to `async_something()`[s] caller. The case of a callback signature `void(T)` is handled by having `yield_handler::operator()(T)` engage the `void(error_code, T)` machinery, passing a ["success] `error_code`. [/ @path link is relative to (eventual) doc/html/index.html, hence ../..] The source code above is found in [@../../examples/asio/yield.hpp yield.hpp] and [@../../examples/asio/detail/yield.hpp detail/yield.hpp]. [endsect] [endsect]