Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion advanced/signal-protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,9 @@ Downgrading to a version that predates the counter lease after running a leased

The scheduler is generation-scoped (embeds the connection generation in its atomic state), so a reconnect during an in-flight flush needs no explicit reset: a stale worker from the previous connection cannot mutate the new generation's state, and stands down when it observes a foreign generation.

The offline drain, retry-receipt recovery, identity-change recovery, and teardown all keep their own **synchronous** flushes — they gate acks, receipts, or follow-up reads on durability and are not routed through the receive coalescer. See [Inbound Durability Hook](/advanced/inbound-durability) for the drain-batch commit ordering, which this coalescing does not change.
The offline drain, identity-change recovery, and teardown all keep their own **unconditional synchronous** flushes — they gate acks, receipts, or follow-up reads on durability and are not routed through the receive coalescer. See [Inbound Durability Hook](/advanced/inbound-durability) for the drain-batch commit ordering, which this coalescing does not change.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't call teardown flushes unconditional

For teardown during an active or stalled offline drain, teardown_inbound_commits_bounded does not unconditionally flush: it only flushes after a durable drain commit with no restored entries, and on timeout or non-durable entries it clears or preserves state so the server can redeliver. The new unconditional synchronous wording can make operators think disconnect/teardown always gates Signal state on a flush, which is exactly the case the teardown path intentionally avoids to prevent rowless ratchet advances.

Useful? React with 👍 / 👎.


Retry-receipt recovery (`handle_retry_receipt` resending to a DM or group requester, `src/retry.rs`) instead shares the DM/group send durability rule above through one helper, `send_retry_stanza`: the session lock is released first, then `persist_signal_state_pre_wire()` flushes synchronously only if the retry's Signal advance crossed an unpersisted lease boundary, and the stanza is written to the wire only after that flush succeeds. A failed pre-wire flush aborts the retry instead of transmitting an advance it couldn't save. This replaced an earlier unconditional full flush that ran only *after* the retry stanza had already reached the wire — a crash or persistence failure in that window could reload the old chain state after the ciphertext was already sent.
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid overstating retry pre-wire durability

I checked the merged src/retry.rs/wacore/src/store/signal_cache.rs behavior behind whatsapp-rust#1041, and this guarantee is still too strong: after handle_retry_receipt drops the per-session lock, another send/retry for that device can check out the same dirty session before persist_signal_state_pre_wire() runs. The pre-wire flush skips checked-out sessions and can return Ok while the pending reservation remains, so send_retry_stanza can still write the stanza before the raised lease is actually durable. Until upstream fails/retries that case or prevents checkout across the gate, the docs should not state that the stanza is only written after a successful durable flush.

Useful? React with 👍 / 👎.


Call [`Client::flush_pending_signal_state()`](/api/client#flush_pending_signal_state) to force a deterministic settle — e.g. before reading persisted Signal state directly, or ahead of a non-graceful shutdown. Never call it from inside an `InboundDurabilityHook` or a synchronous, inline `EventHandler::handle_event` implementation, since settling re-enters the processing permit those run under and would deadlock during an offline-sync drain. Ordinary `Bot` closure handlers are unaffected — both default delivery modes run the callback in a detached task off the permit.

Expand Down