Responding to requests asynchronously in LLCPP


This tutorial builds on the LLCPP getting started tutorials.


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

// An implementation of the Echo protocol. Protocols are implemented in LLCPP by
// creating a subclass of the fidl::WireServer class for the protocol.
class EchoImpl final : public fidl::WireServer<fuchsia_examples::Echo> {
  // Bind this implementation to a channel.
  EchoImpl(async_dispatcher_t* dispatcher, fidl::ServerEnd<fuchsia_examples::Echo> server_end)
      : binding_(fidl::BindServer(dispatcher, std::move(server_end), this,
                                  // This is a fidl::OnUnboundFn<EchoImpl>.
                                  [this](EchoImpl* impl, fidl::UnbindInfo info,
                                         fidl::ServerEnd<fuchsia_examples::Echo> server_end) {
                                    if (info.is_peer_closed()) {
                                      FX_LOGS(INFO) << "Client disconnected";
                                    } else if (!info.is_user_initiated()) {
                                      FX_LOGS(ERROR) << "Server error: " << info;
                                    delete this;
                                  })) {}

  // Handle a SendString request by sending on OnString "event" (an unsolicited server-to-client
  // message) back on the same channel.
  // For fire-and-forget methods like this one, the completer is normally not used but its
  // Close(zx_status_t) method can be used to close the channel (either if the connection is "done"
  // or it encountered an unrecoverable error).
  void SendString(SendStringRequestView request, SendStringCompleter::Sync& completer) override {
    fidl::Status status = fidl::WireSendEvent(binding_)->OnString(request->value);

  // Handle an EchoString request by responding with the request value. For two-way methods (those
  // with a response) like this one, the completer is used to send the response.
  void EchoString(EchoStringRequestView request, EchoStringCompleter::Sync& completer) override {

  // A reference back to the Binding that this class is bound to, which is used
  // to send events to the client.
  fidl::ServerBindingRef<fuchsia_examples::Echo> binding_;

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:

protocol Echo {
    EchoString(struct {
        value string:MAX_STRING_LENGTH;
    }) -> (struct {
        response string:MAX_STRING_LENGTH;
    SendString(struct {
        value string:MAX_STRING_LENGTH;
    -> OnString(struct {
        response string:MAX_STRING_LENGTH;

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:

int main(int argc, const char** argv) {
  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
  async_dispatcher_t* dispatcher = loop.dispatcher();
  int num_responses = 0;

  auto client_end = service::Connect<fuchsia_examples::Echo>();
  fidl::WireClient client(std::move(*client_end), dispatcher);

  auto start = time(nullptr);

  // Make |kNumEchoes| EchoString requests to the server, and print the result when
  // it is received.
  for (int i = 0; i < kNumEchoes; i++) {
        [&](fidl::WireUnownedResult<fuchsia_examples::Echo::EchoString>& result) {
          if (!result.ok())
          auto* response = result.Unwrap();
          std::string reply(response->, response->response.size());
          std::cout << "Got response after " << time(nullptr) - start << " seconds" << std::endl;
          if (++num_responses == kNumEchoes) {

  return num_responses == kNumEchoes ? 0 : 1;

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:

class EchoImpl final : public fidl::WireServer<fuchsia_examples::Echo> {
  explicit EchoImpl(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
  // SendString is not used in this example, so requests are just ignored.
  void SendString(SendStringRequestView request, SendStringCompleter::Sync& completer) override {}
  void EchoString(EchoStringRequestView request, EchoStringCompleter::Sync& completer) override {
    // Respond to the request asynchronously by using ToAsync() and moving it into
    // a lambda capture. This allows multiple requests to EchoString to wait concurrently
    // rather than in sequence.
            // The buffer referenced by the request view only lives until the end
            // of the |EchoString| call. Convert it to an owned value to use asynchronously.
            value_owned = std::string(request->value.get()),
            // The lambda capturing `completer` must be marked mutable, because making a
            // reply using the completer mutates it such that duplicate replies will panic.
            completer = completer.ToAsync()]() mutable {

  // Contains a pointer to the dispatcher in order to wait asynchronously using
  // PostDelayedTask.
  async_dispatcher_t* dispatcher_;

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.

Another noteworthy aspect is that the request views provided to method handlers do not own the request message. In order to use the request parameters after the EchoString method returns, we need to copy relevant fields to an owned type, here value_owned in the lambda captures. For further information on the memory ownership, refer to the LLCPP Memory Management.

Run the example

In order for the client and server to communicate using the Echo protocol, component framework must route the fuchsia.examples.Echo capability from the server to the client. For this tutorial, a realm component is provided to declare the appropriate capabilities and routes.

  1. Configure your build to include the provided package that includes the echo realm, server, and client:

    fx set core.qemu-x64 --with //examples/fidl/llcpp:echo-llcpp-async
  2. Build the Fuchsia image:

    fx build
  3. Run the echo_realm component. This creates the client and server component instances and routes the capabilities:

    ffx component run fuchsia-pkg://
  4. Start the echo_client instance:

    ffx component start /core/ffx-laboratory:echo_realm/echo_client

The server component starts when the client attempts to connect to the Echo protocol. You should see output similar to the following in the device logs (ffx log):

[echo_server][][I] Running echo server
[echo_server][][I] echo_server_llcpp: Incoming connection for fuchsia.examples.Echo
[echo_client][][I] Got response after 5 seconds
[echo_client][][I] Got response after 5 seconds
[echo_client][][I] Got response after 5 seconds

By using the async completer, the client receives all 3 responses after 5 seconds, rather than individually at 5 second intervals.

Terminate the realm component to stop execution and clean up the component instances:

ffx component destroy /core/ffx-laboratory:echo_realm