payloadZBytes property
The query's payload as a retained ZBytes handle, or null if the requester sent none.
Why this needs no opt-in, where a sample's does
Session.declareSubscriber takes a retainPayload: flag because a
sample's native payload dies when the delivery callback returns —
retaining it has to happen inside that callback or not at all, so it must
be decided at declaration time and costs something on every message.
A query's does not. The whole owned query is cloned across the seam and stays alive until dispose is called, so its payload is reachable whenever you ask. This accessor is therefore lazy — it costs nothing unless read — and needs no flag. The asymmetry is the reason the flag exists elsewhere, not an inconsistency.
Lifetime
The handle is an independent, shallow, refcounted clone. It outlives this query: calling dispose does not invalidate it, and reading it after disposal is fine once it has been materialised.
The accessor is memoized — reading it repeatedly returns the identical object rather than minting a handle per read, so a consumer cannot leak one by looping.
⛔ Release it, with ZBytes.dispose or by handing it to a send that
consumes it. dispose on this query deliberately does not release it:
it is yours, not the query's. A ZBytes carries a finalizer safety net,
but a finalizer runs at an unpredictable time or not at all, so the net is
not a substitute for releasing it.
Throws StateError if this query has been disposed and the payload was never materialised — there is no live handle left to clone from.
Implementation
ZBytes? get payloadZBytes {
// The memo is checked BEFORE the disposal guard, deliberately: once
// materialised the clone is independent of this query, so it stays
// readable afterwards. Only an unresolved read needs the native handle,
// and that is exactly what the guard protects.
if (_payloadZBytesResolved) return _payloadZBytes;
_ensureNotDisposed();
final size = bindings.zd_bytes_sizeof();
final slot = calloc.allocate<Void>(size);
final present = calloc<Int32>();
try {
bindings.zd_query_payload_clone(
Pointer<Uint8>.fromAddress(_handle),
slot.cast(),
present,
);
if (present.value == 0) {
// Absent, not empty. Nothing was written into the slot, so it is freed
// here rather than wrapped — wrapping it would hand out a ZBytes over
// uninitialised memory.
calloc.free(slot);
_payloadZBytes = null;
} else {
_payloadZBytes = ZBytes.fromNative(slot);
}
} finally {
// OUTER-FINALLY, enclosing every statement that can throw: the presence
// cell is Dart-allocated and is released on every control path.
calloc.free(present);
}
_payloadZBytesResolved = true;
return _payloadZBytes;
}