perf(receive): release drain encode arena on live transition

Merged
JL
25d ago
+80-4

Offline drain commits reuse an encoding arena, but live commits use separate buffers. Previously the arena kept its capacity until the client was dropped. Release it at the existing successful drain-to-live transition, preserving reuse between drain batches.

Cleanup uses try_lock: a forced exit racing an in-flight backend write leaves its borrowed rows untouched. Storage writes, Signal flushing, hooks, receipts/ACKs, and failed-tail retries keep their existing order. A busy forced-exit buffer may remain allocated. A subsequent connection with a backlog must allocate its drain arena again.

Measured result

Base 8f711a9bf3e1ff556d2c8374702462a918e7b433; production head 0137225f7f1b4f0cc15b90e8ae0c5e524811b811. CodSpeed simulation/memory comparison. This is the completed current-head PR run. An earlier measurement run independently produced the same allocation balances. Its 353530a6 commit adds only workflow wiring to this runtime source; those workflow changes are excluded from this PR.

The unchanged connected fixture performs real Noise/login/read-loop processing of 256 encrypted messages across 32 lanes, 256 inline-history messages, durable commits, and both storage backends. History enters the production parser without HTTP download. Startup work is included in activity measurements; warm-worker and maintenance cases are checked separately.

Connected activity metric (bytes)In-memory base → headFile SQLite base → head
Peak allocated4,138,813 → 4,138,8244,165,387 → 4,165,371
Total allocated12,173,795 → 12,174,01613,606,619 → 13,590,136
Total freed10,582,573 → 11,632,13811,955,393 → 12,988,270
Allocated minus freed within operation1,591,222 → 541,8781,651,226 → 601,866

The allocation balance improves by 1,049,344 / 1,049,360 bytes, approximately 1 MiB, before connected activity returns. Peak allocation is unchanged. This balance is not total retained heap or RSS: setup allocations lie outside the measured region, and allocators may retain freed memory. Connected lifecycle tests separately require zero arena capacity after the drain. Total allocation varies slightly; this is a lifetime improvement, not an allocation-count claim.

Matching ARM walltime runs, 1,000 samples × one iteration, show no regressions across 14 cases. Connected activity: 18.249 → 18.134 ms in-memory, 23.766 → 23.433 ms SQLite, both classified unchanged. Warm receive cases are unchanged. Two small maintenance cases improve; no broad CPU gain is claimed.

The full simulation/memory sets contain the same 848 rows, with no benchmarks removed. The comparison flags two Signal microbenchmarks (~8.2%) across AMD EPYC 9V45 → 7763 runners. Their crate, benchmarks, dependencies, and feature settings are unchanged. Both have exactly unchanged instruction cost; modeled memory cost changes from 1,111.111 to 1,222.222 ns, while cache cost falls 2.778 ns. Profiles show the same calls. The current-head rerun reproduces these flags. This standalone dependency benchmark does not depend on the modified client crate, and none of its build inputs changed. The evidence supports an environment/cache-layout explanation; the two flags remain disclosed rather than being presented as a completely clean simulation comparison. Other shards also carry environment warnings; simulation timing is not used to claim a CPU win.

Validation

  • 2,479 library tests passed; both focused arena tests passed again after strengthening forced-exit setup.
  • All eight connected lifecycle tests passed, including SQLite and post-activity idle checks. Virtual-time idle tests prove lifecycle behavior, not native RAM usage.
  • Clippy with warnings denied, formatting, and diff checks passed.
  • All four measurement CI shards passed, including Bartender integration with existing secure CI credentials.
  • No dependency or release API changes; capacity inspection exists only under the opt-in bench harness.

Current-head CI and review

All validation workflows completed successfully on 0137225f: Rust CI, native/MSRV API consumers, WASM release, Miri, E2E, supply chain, formatting/generated artifacts, CodSpeed, and binary size. The main test job passed 6,907 tests; Bartender E2E passed 168 with 11 existing skips. All-shareable-feature tests also passed.

The existing advisory SemVer job remains failed, identically on base and head: 23 / 3 / 7 findings against published 0.7.0 in unchanged wacore / wacore-binary / waproto. Its pre-existing continue-on-error setting was not changed. This is a qualified validation result, not a claim that every check is green.

Native size report: stripped binary +512 B, .text +448 B, dependency count unchanged. The size gate passed. WASM compilation passed; no quantified WASM/ESP32 binary-size or idle-RSS reduction is claimed.

Codex, CodeRabbit, and Greptile completed reviews with no actionable findings; no unresolved review threads. The PR is ready for maintainer review with the measurement and advisory-check caveats above. No thresholds or gates were weakened.