Responding to requests asynchronously in LLCPP

Prerequisites

This tutorial builds on the LLCPP getting started tutorials.

Overview

In the Echo implementation from the server tutorial, the server code responded to EchoString requests using the completer.

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/server/main.cc" region_tag="impl" highlight="14,15,16" %}

Notice that the type for the completer has ::Sync. This indicates the default mode of operation: the server must synchronously make a reply before returning from the handler function. Enforcing this allows optimizations since the bookkeeping metadata for making a reply can be stack-allocated.

This tutorial provides an example of how to respond to requests asynchronously, by converting the sync completer into an async completer.

The full example code for this tutorial is located at //examples/fidl/llcpp/async_completer.

The Echo protocol

This example uses the Echo protocol from the examples library:

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples/echo.test.fidl" region_tag="echo" %}

As part of this tutorial, you will implement a client that makes multiple EchoString requests in succession. The server will respond to these requests asynchronously, emulating a scenario where the server must execute a long running task before sending a response. By using an async completer, these long running tasks can be completed asynchronously.

Implement the client

The client code is mostly similar to the code from the client tutorial. The differences are highlighted in this section.

After connecting to the server, the client will make multiple EchoString requests inside of a for loop:

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/async_completer/client/main.cc" region_tag="main" highlight="14,16,17,18,19,20,21,22,23,24,25,26" %}

The loop is run kNumEchoes times (which is by default 3), and will print the time elapsed since the first request every time it receives a response. After it receives kNumEchoes responses, the code quits from the loop.

Implement the server

The main() function from the server code is the same as in the server tutorial. The difference lies in the implementation of Echo:

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/async_completer/server/main.cc" region_tag="impl" %}

When an EchoString request is received, the server calls async::PostDelayedTask. This function takes a dispatcher, a callback, and a duration, and will execute the callback at the end of the duration. This call emulates a long running task that returns its result through a callback when it is finished. The handler uses PostDelayedTask to wait 5 seconds before echoing the request value back to the client.

A key point is that the completer being moved into the lambda capture is the async completer. A ::Sync completer can be converted to the ::Async counterpart by using the ToAsync() method.

For further information on the completer API, refer to the LLCPP bindings reference.

Run the example

First, build the code:

fx set core.x64 --with //examples/fidl/llcpp/async_completer/client --with //examples/fidl/llcpp/async_completer/server --with //examples/fidl/test:echo-launcher

fx build

Then run the example:

fx shell run fuchsia-pkg://fuchsia.com/echo-launcher#meta/launcher.cmx fuchsia-pkg://fuchsia.com/echo-llcpp-client-async#meta/echo-client.cmx fuchsia-pkg://fuchsia.com/echo-llcpp-server-async#meta/echo-server.cmx fuchsia.examples.Echo

You should see the following print output in the QEMU console (or using fx log):

[193539.863] 884542:884544> Running echo server
[193539.871] 884542:884544> echo_server_llcpp: Incoming connection for fuchsia.examples.Echo
[193544.899] 884632:884636> Got response after 5 seconds
[193544.899] 884632:884636> Got response after 5 seconds
[193544.899] 884632:884636> Got response after 5 seconds

By using the async completer, the client receives all 3 responses after 5 seconds, rather than in 5/10/15 seconds.