Expand description
Unstable: post-commit notification of group-state changes.
Registered once at client construction (crate::builder::ClientBuilder),
not passed per call — the changes worth reacting to arrive from the stream
and sync paths, where no SDK method call is on the stack to carry a
parameter.
§Why a struct of callbacks
Only UnstableChangeCallbacks::app_data is implemented today. The
registry is a struct rather than a bare callback argument so callbacks for
the other mutable fields (name, description, image url, admin lists,
permissions, disappearing settings) land as additive fields on a type the
SDKs already construct — the same reason the bindings’ UpdateAppDataOptions
and the other FFI options records are structs.
§Delivery contract
Callbacks fire once the storage transaction has committed, the group commit
lock has been released, and the per-group sync mutex has been dropped —
so an implementation is free to publish the result of its merge with
update_app_data on the same group. That call re-enters sync_with_conn
and takes the same sync mutex, which is why dispatch cannot happen inline
during message processing; see
crate::groups::MlsGroup::dispatch_app_data_changes.
Concretely, that means the sync path delivers a batch after the whole sync completes rather than between messages, and the stream path delivers each change as its message is processed. Changes are dispatched one at a time, in the order they were observed — but see Timeouts: that ordering holds only for callbacks that return within their budget.
A callback observes the net change across one processed message, and fires for local commits as well as remote ones — an implementation that reacts by writing must make its merge idempotent, or it will chase its own echo.
§Timeouts
Each callback is given UnstableChangeCallbacks::app_data_timeout to
return. A host that overruns it is abandoned: the expiry is logged and the
rest of that batch is dropped, and neither the sync nor the stream fails,
because the change being reported is already durably committed and the
callback is advisory. Nothing is permanently lost — merges are idempotent,
so the next change re-triggers one from current state.
Abandoning is not cancelling. libxmtp drops the future and stops waiting; whether the host’s own work stops is up to the binding. uniffi notifies the foreign side that the future was dropped, which its Kotlin and Swift bindings can wire to cancelling the task, whereas a JS promise behind napi or wasm-bindgen keeps running to completion — or never resolves — with nothing left listening.
That has a consequence worth designing around: on a binding that cannot
cancel, an abandoned callback may still publish after a later one already
did, landing a merge derived from state that has since moved on. The budget
bounds how long sync waits; it cannot unwind work the host has already
started. Pass the compare-and-swap guard — update_app_data(merged, Some(value_you_were_handed)) — so a late write is superseded at publish
time instead of clobbering the newer one. Hosts that publish unguarded get
last-writer-wins, and after a timeout “last” is not necessarily “latest”.
The budget also only bounds callbacks that yield. A handler that blocks
its thread — synchronous FFI work, a blocking lock, Thread.sleep — stalls
the task the timer lives on, so the timeout cannot fire and sync waits as
long as the host does. This is inherent to async: a future that never
returns from poll cannot be timed out from inside the same runtime.
Callbacks must not block; do blocking work on the host’s own executor and
await its completion.
§Stability
Pre-release. The shape of the payloads and of the registry may change without a major version bump until this graduates onto the stable client surface.
Structs§
- AppData
Change - A change to a group’s opaque
app_dataslot, observed after it was applied to local state. - Unstable
Change Callbacks - The set of change callbacks registered on a client.
Constants§
- DEFAULT_
APP_ DATA_ CALLBACK_ TIMEOUT - How long a single
app_datacallback may run before it is abandoned.
Traits§
- AppData
Change Callback - Notified whenever a processed message changed a group’s
app_data.