1# libasync and friends 2 3This set of libraries defines a C and C++ language interface for initiating 4asynchronous operations and dispatching their results to callback functions. 5 6The purpose of these libraries is to decouple clients which want to perform 7asynchronous operations from the message loop implementations which dispatch 8the results of those operations. This makes it an important building block 9for other abstractions such as the FIDL bindings. 10 11## Libraries 12 13The async package consists of three libraries: 14 15- `libasync.a` provides the C client API which includes all of the function 16and structures declared in the following headers: 17 - [async/dispatcher.h](include/lib/async/dispatcher.h) 18 - [async/receiver.h](include/lib/async/receiver.h) 19 - [async/task.h](include/lib/async/task.h) 20 - [async/time.h](include/lib/async/time.h) 21 - [async/trap.h](include/lib/async/trap.h) 22 - [async/wait.h](include/lib/async/wait.h) 23 24- `libasync-cpp.a` provides C++ wrappers: 25 - [async/cpp/receiver.h](include/lib/async/cpp/receiver.h) 26 - [async/cpp/task.h](include/lib/async/cpp/task.h) 27 - [async/cpp/time.h](include/lib/async/cpp/time.h) 28 - [async/cpp/trap.h](include/lib/async/cpp/trap.h) 29 - [async/cpp/wait.h](include/lib/async/cpp/wait.h) 30 31- `libasync-default.so` provides functions for getting or setting a thread-local 32default asynchronous dispatcher as declared in [async/default.h](include/lib/async/default.h). 33 34See also [libasync-loop.a](../async-loop/README.md) which provides a general-purpose 35implementation of `async_dispatcher_t`. 36 37## Using the asynchronous dispatcher 38 39### Waiting for signals 40 41To asynchronously wait for signals, the client prepares an `async_wait_t` 42structure then calls `async_begin_wait()` to register it with the dispatcher. 43When the wait completes, the dispatcher invokes the handler. 44 45The client can register handlers from any thread but dispatch will occur 46on a thread of the dispatcher's choosing depending on its implementation. 47 48The client is responsible for ensuring that the wait structure remains in 49memory until the wait's handler runs or the wait is successfully canceled using 50`async_cancel_wait()`. 51 52See [async/wait.h](include/lib/async/wait.h) for details. 53 54```c 55#include <lib/async/wait.h> // for async_begin_wait() 56#include <lib/async/default.h> // for async_get_default_dispatcher() 57 58void handler(async_dispatcher_t* async, async_wait_t* wait, 59 zx_status_t status, const zx_packet_signal_t* signal) { 60 printf("signal received: status=%d, observed=%d", status, signal ? signal->observed : 0); 61 free(wait); 62} 63 64zx_status_t await(zx_handle_t object, zx_signals_t trigger, void* data) { 65 async_dispatcher_t* async = async_get_default_dispatcher(); 66 async_wait_t* wait = calloc(1, sizeof(async_wait_t)); 67 wait->handler = handler; 68 wait->object = object; 69 wait->trigger = trigger; 70 return async_begin_wait(async, wait); 71} 72``` 73 74### Waiting for exceptions 75 76To asynchronously wait for exceptions, the client prepares an 77`async_exception_t` structure then calls `async_bind_exception_port()` 78to register it with the dispatcher. When an exception happens, the dispatcher 79invokes the handler. 80 81The client can register handlers from any thread but dispatch will occur 82on a thread of the dispatcher's choosing depending on its implementation. 83 84The client is responsible for ensuring that the exception structure remains in 85memory until the port is successfully unbound using 86`async_unbind_exception_port()`. 87 88See [async/exception.h](include/lib/async/exception.h) for details. 89 90```c 91#include <lib/async/wait.h> // for async_begin_wait() 92#include <lib/async/default.h> // for async_get_default_dispatcher() 93 94void handler(async_dispatcher_t* async, async_exception_t* exception, 95 zx_status_t status, const zx_port_packet_t* exception) { 96 printf("signal received: status=%d, kind=0x%x", 97 status, exception ? exception->type : 0); 98 if (status == ZX_OK) { 99 // ... process exception ... 100 } 101} 102 103zx_status_t ebind(async_exception_handler_t* handler, 104 zx_handle_t task, uint32_t options, 105 zx_async_exception_t* out_exception) { 106 async_dispatcher_t* async = async_get_default_dispatcher(); 107 async_exception_t* exception = calloc(1, sizeof(async_exception_t)); 108 exception->handler = handler; 109 exception->task = task; 110 exception->options = options; 111 zx_status_t status = async_bind_exception_port(async, exception); 112 if (status == ZX_OK) { 113 *out_exception = exception; 114 } else } 115 free(exception); 116 } 117 return status; 118} 119 120zx_status_t eunbind(async_exception_t* exception) { 121 async_dispatcher_t* async = async_get_default_dispatcher(); 122 zx_status_t status = async_unbind_exception_port(dispatcher, exception); 123 free(exception); 124 return status; 125} 126``` 127 128### Getting the current time 129 130The dispatcher represents time in the form of a `zx_time_t`. In normal 131operation, values of this type represent a moment in the `ZX_CLOCK_MONOTONIC` 132time base. However for unit testing purposes, dispatchers may use a synthetic 133time base instead. 134 135To make unit testing easier, prefer using `async_now()` to get the current 136time according the dispatcher's time base. 137 138See [async/time.h](include/lib/async/time.h) for details. 139 140### Posting tasks and getting the current time 141 142To schedule asynchronous tasks, the client prepares an `async_task_t` 143structure then calls `async_post_task()` to register it with the dispatcher. 144When the task comes due, the dispatcher invokes the handler. 145 146The client can post tasks from any thread but dispatch will occur 147on a thread of the dispatcher's choosing depending on its implementation. 148 149The client is responsible for ensuring that the task structure remains in 150memory until the task's handler runs or the task is successfully canceled using 151`async_cancel_task()`. 152 153See [async/task.h](include/lib/async/task.h) for details. 154 155```c 156#include <lib/async/task.h> // for async_post_task() 157#include <lib/async/time.h> // for async_now() 158#include <lib/async/default.h> // for async_get_default_dispatcher() 159 160typedef struct { 161 async_task_t task; 162 void* data; 163} task_data_t; 164 165void handler(async_dispatcher_t* async, async_task_t* task, zx_status_t status) { 166 task_data_t* task_data = (task_data_t*)task; 167 printf("task deadline elapsed: status=%d, data=%p", status, task_data->data); 168 free(task_data); 169} 170 171zx_status_t schedule_work(void* data) { 172 async_dispatcher_t* async = async_get_default_dispatcher(); 173 task_data_t* task_data = calloc(1, sizeof(task_data_t)); 174 task_data->task.handler = handler; 175 task_data->task.deadline = async_now(async) + ZX_SEC(2); 176 task_data->data = data; 177 return async_post_task(async, &task_data->task); 178} 179``` 180 181### Delivering packets to a receiver 182 183Occasionally it may be useful to register a receiver which will be the 184recipient of multiple data packets instead of allocating a separate task 185structure for each one. The Zircon port takes care of storing the queued 186packet data contents until it is delivered. 187 188The client can queue packets from any thread but dispatch will occur 189on a thread of the dispatcher's choosing depending on its implementation. 190 191The client is responsible for ensuring that the receiver structure remains in 192memory until all queued packets have been delivered. 193 194See [async/receiver.h](include/lib/async/receiver.h) for details. 195 196```c 197#include <lib/async/receiver.h> // for async_queue_packet() 198#include <lib/async/default.h> // for async_get_default_dispatcher() 199 200void handler(async_dispatcher_t* async, async_receiver_t* receiver, zx_status_t status, 201 const zx_packet_user_t* data) { 202 printf("packet received: status=%d, data.u32[0]=%d", status, data ? data.u32[0] : 0); 203} 204 205const async_receiver_t receiver = { 206 .state = ASYNC_STATE_INIT, 207 .handler = handler; 208} 209 210zx_status_t send(const zx_packet_user_t* data) { 211 async_dispatcher_t* async = async_get_default_dispatcher(); 212 return async_queue_packet(async, &receiver, data); 213} 214``` 215 216## The default async dispatcher 217 218As a client of the async dispatcher, where should you get your `async_dispatcher_t*` from? 219 220The ideal answer is for the `async_dispatcher_t*` to be passed into your code when it is 221initialized. However sometimes this becomes burdensome or isn't practical. 222 223For this reason, the `libasync-default.so` shared library provides functions 224for getting or setting a thread-local default `async_dispatcher_t*` using 225`async_get_default_dispatcher()` or `async_set_default_dispatcher()`. 226 227This makes it easy to retrieve the `async_dispatcher_t*` from the ambient environment 228by calling `async_get_default_dispatcher()`, which is used by many libraries. 229 230Message loop implementations should register themselves as the default 231dispatcher any threads they service. 232 233See [async/default.h](include/lib/async/default.h) for details. 234 235## Using the C++ helpers 236 237`libasync-cpp.a` includes helper classes such as `Wait`, `Task`, and `Receiver` 238which wrap the C API with a more convenient type safe interface for use 239in C++. 240 241Note that the C API can of course be used directly from C++ for special 242situations which may not be well addressed by the wrappers. 243 244## Implementing a dispatcher 245 246The `async_ops_t` interface is a low-level abstraction for asynchronous 247dispatchers. You can make custom implementations of this interface to 248integrate clients of this library with your own dispatcher. 249 250It is possible to implement only some of the operations but this may cause 251incompatibilities with certain clients. 252 253See [async/dispatcher.h](include/lib/async/dispatcher.h) for details. 254