Sync and async handlers¶
A server generated with use_async = true expects every user handler to be an async fn, and with
use_async = false every handler to be a plain fn. Most handlers of a device never wait for anything (they read a
field, set a pin, answer Unimplemented), and an async fn costs flash even when it never awaits: each call
becomes a state of the dispatcher's future. On Donguru (Cortex-M0+), ~115 of ~130 handlers never await; making them
plain fns saved 20.2 KiB of flash at opt-level = "z" (282 -> 262 KiB) and 12.6 KiB at 3.
handler_model lists the exceptions, so an async server can have sync handlers (SRV-21):
mod server_impl {
wire_weaver::ww_codegen!(
donguru_api :: DonguruApi for super::ServerState,
server = true, no_alloc = true, use_async = true,
// these await (flash writes, ADC reads, a VBUS cycle), all others are plain `fn`s
handler_model = "device.settings.*|meter.reading|plug.vbus_cycle=async, _=sync",
);
}
- Each pattern is a regex matched against an item's whole dotted path in the API tree (
device.settings.vbus_modefor thevbus_modeproperty of thesettingstrait underdevice); the first one that matches decides..matches the dot between levels too, sodevice.settings.*takes everything underdevice.settings. _is the default for the items no pattern matches. Without_, they followuse_async; withouthandler_model, every handler does, as before.- A pattern can't contain
,,=or whitespace. A pattern that matches no method, property or stream is a compile error (a typo, or an item that was renamed), and so isasyncin a server withuse_async = false.
The model is per item: every handler of an item is the same kind. For a property that is its getter, setter and
changed_<name>, for a stream its open handler, close_<name>, sideband_<name> and the stream_<name>() state
accessor, for a sink write_<name> and sideband_<name>. valid_indices_<..> and observe_state() are always
plain fns.
What the generated server does with it¶
- The dispatcher calls a sync handler without
.await, andpoll_propertiesreads a sync getter without it. - A trait level whose items are all sync, down to its leaves, gets a plain
fndispatcher (process_<level>), called without.awaitfrom the level above. Some items await on their own whatever their handlers are: a reliable stream (its store is async), a credit sink (it sends credit events), and the root level when it serves introspection. process_request_bytesandpoll_propertiesstayasync fnin an async server.
A sync handler in an async server still gets cx: &mut Context<'_, impl EventOut, ..>, but can't await a send on
it: a handler that sends events (a deferred answer right away, a stream item) stays async.
A handler written the other way¶
Every call goes through wire_weaver::handler::sync(..) or wire_weaver::handler::r#async(..), which compile to
nothing but check the kind: a handler written as async fn where the model says sync is a compile error, not a
future that is dropped without running, and the other way round.
error[E0277]: this handler is `async fn`, but `handler_model` (or `use_async = false`) makes it a plain `fn`
= note: make the handler a plain `fn`, or list it as `async` in `ww_codegen!(.., handler_model = "..")`
The error points at the ww_codegen! call, and names the handler's output type; debug_to_file shows which call
it is.
Scaffolding¶
ww api scaffold <api_crate> --use-async --handler-model "<model>" writes async fn or fn per item to match, and
passes the model on to the ww_codegen! call it prints. To change an existing server, set handler_model, then let
the compiler list the handlers that are now the wrong kind.