> ## Documentation Index
> Fetch the complete documentation index at: https://whatsapp-rust.jlucaso.com/llms.txt
> Use this file to discover all available pages before exploring further.

# June 9, 2026 — Group send: session setup hoisted out of chain lock

> Concurrent sends to the same group no longer serialize behind prekey fetch I/O. The sender-key chain lock now covers only the CPU-bound SKDM creation and skmsg encrypt. A new per-group session-setup lock serializes cold prekey fetch and X3DH without blocking warm sends.

## Performance

**Group send: session setup hoisted out of the sender-key chain lock ([#807](https://github.com/oxidezap/whatsapp-rust/pull/807))**

Previously, `prepare_group_stanza` took the per-(group, sender) chain lock at the top and held it through the entire SKDM path — including cold-path device resolution and prekey fetch + X3DH session establishment. Any group send that needed to establish a new pairwise session held the chain lock across a server round-trip, so concurrent sends to the same group serialized behind that RTT.

**Two-phase split.** `encrypt_for_devices` is now composed of two separately callable halves:

* **`ensure_sessions_for_devices`** — network phase: LID-first session lookup, batch prekey fetch, parallel X3DH. Returns a `SessionPlan` (per-device LID overrides + 406 flag). Touches only session/identity state, never a sender-key chain. May span network I/O.
* **`encrypt_for_devices_with_sessions`** — CPU phase: the bounded pairwise encrypt fan-out, consuming the `SessionPlan`. Safe to run under a lock that must not span I/O.

`encrypt_for_devices` remains as the composition of both, so the DM path is unchanged.

The group path now runs `ensure_sessions_for_devices` before taking the chain lock; the chain lock covers only SKDM creation + pairwise fan-out + `skmsg` encrypt. This matches WA Web, where `ensureE2ESessions` is a separate step before `GroupSkmsgJob`'s encrypt.

**Session-setup lock.** Hoisting session setup out of the chain lock would have let two concurrent cold sends to the same group race prekey fetch + X3DH writes to the same per-device sessions. A new `SenderKeyStore::session_setup_lock` (per-group, default-uncontended; backed by the same `SignalStoreCache` lock map under a `::setup` key suffix) is held only during `ensure_sessions_for_devices`. Same-group cold sends serialize their setup exactly as before; warm sends (no SKDM needed) never take it, so the chain lock stays network-free.

**New public items:**

* `wacore::send::encrypt::ensure_sessions_for_devices`
* `wacore::send::encrypt::encrypt_for_devices_with_sessions`
* `wacore::send::encrypt::SessionPlan`
* `SenderKeyStore::session_setup_lock` — defaulted trait method (returns a fresh uncontended mutex by default); production stores override via `SignalStoreCache::session_setup_lock`

**Tracing.** Both the DM and group paths now emit `wa.send.ensure_sessions` + `wa.send.encrypt_fanout` instead of a single combined `wa.send.encrypt_fanout` span.

**No breaking changes** — `encrypt_for_devices` and `prepare_group_stanza` keep their existing signatures.
