blob: e9b0ed26ea45ac9457cd464c43e8b5c207935a97 [file] [log] [blame] [view] [edit]
# libasync and friends
This set of libraries defines a C and C++ language interface for initiating
asynchronous operations and dispatching their results to callback functions.
The purpose of these libraries is to decouple clients which want to perform
asynchronous operations from the message loop implementations which dispatch
the results of those operations. This makes it an important building block
for other abstractions such as the FIDL bindings.
## Libraries
The async package consists of three libraries:
- `libasync.a` provides the C client API which includes all of the function
and structures declared in the following headers:
- [async/dispatcher.h](include/lib/async/dispatcher.h)
- [async/receiver.h](include/lib/async/receiver.h)
- [async/task.h](include/lib/async/task.h)
- [async/time.h](include/lib/async/time.h)
- [async/trap.h](include/lib/async/trap.h)
- [async/wait.h](include/lib/async/wait.h)
- `libasync-cpp.a` provides C++ wrappers:
- [async/cpp/receiver.h](include/lib/async/cpp/receiver.h)
- [async/cpp/task.h](include/lib/async/cpp/task.h)
- [async/cpp/time.h](include/lib/async/cpp/time.h)
- [async/cpp/trap.h](include/lib/async/cpp/trap.h)
- [async/cpp/wait.h](include/lib/async/cpp/wait.h)
- `libasync-default.so` provides functions for getting or setting a thread-local
default asynchronous dispatcher as declared in
[async/default.h](../async-default/include/lib/async/default.h).
See also [libasync-loop.a](../async-loop/README.md) which provides a
general-purpose implementation of `async_dispatcher_t`.
## Using the asynchronous dispatcher
### Waiting for signals
To asynchronously wait for signals, the client prepares an `async_wait_t`
structure then calls `async_begin_wait()` to register it with the dispatcher.
When the wait completes, the dispatcher invokes the handler.
The client can register handlers from any thread but dispatch will occur
on a thread of the dispatcher's choosing depending on its implementation.
The client is responsible for ensuring that the wait structure remains in
memory until the wait's handler runs or the wait is successfully canceled using
`async_cancel_wait()`.
See [async/wait.h](include/lib/async/wait.h) for details.
```c
#include <lib/async/wait.h> // for async_begin_wait()
#include <lib/async/default.h> // for async_get_default_dispatcher()
void handler(async_dispatcher_t* async, async_wait_t* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
printf("signal received: status=%d, observed=%d", status, signal ? signal->observed : 0);
free(wait);
}
zx_status_t await(zx_handle_t object, zx_signals_t trigger) {
async_dispatcher_t* async = async_get_default_dispatcher();
async_wait_t* wait = calloc(1, sizeof(async_wait_t));
wait->handler = handler;
wait->object = object;
wait->trigger = trigger;
return async_begin_wait(async, wait);
}
```
### Getting the current time
The dispatcher represents time in the form of a `zx_time_t`. In normal
operation, values of this type represent a moment in the `ZX_CLOCK_MONOTONIC`
time base. However for unit testing purposes, dispatchers may use a synthetic
time base instead.
To make unit testing easier, prefer using `async_now()` to get the current
time according the dispatcher's time base.
See [async/time.h](include/lib/async/time.h) for details.
### Posting tasks and getting the current time
To schedule asynchronous tasks, the client prepares an `async_task_t`
structure then calls `async_post_task()` to register it with the dispatcher.
When the task comes due, the dispatcher invokes the handler.
The client can post tasks from any thread but dispatch will occur
on a thread of the dispatcher's choosing depending on its implementation.
The client is responsible for ensuring that the task structure remains in
memory until the task's handler runs or the task is successfully canceled using
`async_cancel_task()`.
See [async/task.h](include/lib/async/task.h) for details.
```c
#include <lib/async/task.h> // for async_post_task()
#include <lib/async/time.h> // for async_now()
#include <lib/async/default.h> // for async_get_default_dispatcher()
typedef struct {
async_task_t task;
void* data;
} task_data_t;
void handler(async_dispatcher_t* async, async_task_t* task, zx_status_t status) {
task_data_t* task_data = (task_data_t*)task;
printf("task deadline elapsed: status=%d, data=%p", status, task_data->data);
free(task_data);
}
zx_status_t schedule_work(void* data) {
async_dispatcher_t* async = async_get_default_dispatcher();
task_data_t* task_data = calloc(1, sizeof(task_data_t));
task_data->task.handler = handler;
task_data->task.deadline = async_now(async) + ZX_SEC(2);
task_data->data = data;
return async_post_task(async, &task_data->task);
}
```
### Delivering packets to a receiver
Occasionally it may be useful to register a receiver which will be the
recipient of multiple data packets instead of allocating a separate task
structure for each one. The Zircon port takes care of storing the queued
packet data contents until it is delivered.
The client can queue packets from any thread but dispatch will occur
on a thread of the dispatcher's choosing depending on its implementation.
The client is responsible for ensuring that the receiver structure remains in
memory until all queued packets have been delivered.
See [async/receiver.h](include/lib/async/receiver.h) for details.
```c
#include <lib/async/receiver.h> // for async_queue_packet()
#include <lib/async/default.h> // for async_get_default_dispatcher()
void handler(async_dispatcher_t* async, async_receiver_t* receiver, zx_status_t status,
const zx_packet_user_t* data) {
printf("packet received: status=%d, data.u32[0]=%d", status, data ? data.u32[0] : 0);
}
const async_receiver_t receiver = {
.state = ASYNC_STATE_INIT,
.handler = handler;
}
zx_status_t send(const zx_packet_user_t* data) {
async_dispatcher_t* async = async_get_default_dispatcher();
return async_queue_packet(async, &receiver, data);
}
```
## The default async dispatcher
As a client of the async dispatcher, where should you get your
`async_dispatcher_t*` from?
The ideal answer is for the `async_dispatcher_t*` to be passed into your code
when it is initialized. However sometimes this becomes burdensome or isn't
practical.
For this reason, the `libasync-default.so` shared library provides functions
for getting or setting a thread-local default `async_dispatcher_t*` using
`async_get_default_dispatcher()` or `async_set_default_dispatcher()`.
This makes it easy to retrieve the `async_dispatcher_t*` from the ambient
environment by calling `async_get_default_dispatcher()`, which is used by many
libraries.
Message loop implementations should register themselves as the default
dispatcher any threads they service.
See [async/default.h](include/lib/async/default.h) for details.
## Using the C++ helpers
`libasync-cpp.a` includes helper classes such as `Wait`, `Task`, and `Receiver`
which wrap the C API with a more convenient type safe interface for use
in C++.
Note that the C API can of course be used directly from C++ for special
situations which may not be well addressed by the wrappers.
## Implementing a dispatcher
The `async_ops_t` interface is a low-level abstraction for asynchronous
dispatchers. You can make custom implementations of this interface to
integrate clients of this library with your own dispatcher.
It is possible to implement only some of the operations but this may cause
incompatibilities with certain clients.
See [async/dispatcher.h](include/lib/async/dispatcher.h) for details.