Skip to content

perf: remove four per-message tasks and buffers from the send/receive round trip - #1116

Merged
jlucaso1 merged 7 commits into
mainfrom
perf/pingpong-round2
Jul 26, 2026
Merged

perf: remove four per-message tasks and buffers from the send/receive round trip#1116
jlucaso1 merged 7 commits into
mainfrom
perf/pingpong-round2

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Four independent cuts to the per-message cost of a DM send/receive round trip, found by profiling the harness pingpong scenario (120k messages at 12k/s) with the rss, dhat, cpu and tokio passes and then re-checking each conclusion against the code.

The theme is per-message task and channel plumbing. A steady-state round trip was spawning six tokio tasks; four of them existed for work that either does not need a task at all or can be served by a worker that already exists.

What changes

1. The message id is derived without a staging buffer or a fresh RNG. Naming a message built a RequestUtils only to reach the derivation, which cloned the unique id that the derivation never reads, staged the digest input in a Vec, and seeded a StdRng from the thread generator to get sixteen bytes, running a full ChaCha key schedule per message (visible in the CPU profile as chacha20::rng_inner). It is now an associated function fed incrementally from the thread-local generator. Tests pin the wire shape (3EB0 plus eighteen upper-case hex chars), uniqueness inside one second, and the JID-less form.

2. Message secrets drain from one worker instead of a task per capture. The drain task exited as soon as the pending map emptied, which under a steady stream is one spawned task per message: the next capture always arrives after the previous one has been written. A single worker woken through a capacity-one channel keeps the coalescing behaviour. The worker is not permanent by ownership: it holds a Weak, and the Sender lives in the buffer, so dropping the buffer closes the channel and ends the worker; seal() closes it explicitly because every queue writes inline from that point.

3. Deferred acks go through one worker instead of a task per ack. This reuses the pattern already in place for delivery receipts. Two behaviours improve as a side effect: acks now leave in arrival order, which spawning never guaranteed, and disconnect()'s flush waits for a queued ack because the FlushGuard rides the queue until the send returns.

4. The ack phash is compared inline. Validating it cost a oneshot channel, a spawned task and a ten second timer per outgoing message, to run a string comparison that almost always succeeds. The waiter map now carries what the check needs, so the read loop compares when the ack lands and spawns only on a mismatch, which is the path that re-reads caches and can force sender-key redistribution.

Two details there are load-bearing. The waiter is still registered before the stanza goes out, because a fast link can deliver the ack while send_node is still returning. And with no timer to clean up after an unanswered ack, the keepalive tick now sweeps expired phash entries before deciding whether IQs are pending: a leftover waiter would otherwise read as "pending" and silence pings for the life of the connection. The deadline reuses the wall second the send already sampled instead of reading the clock again, which the existing send clock-budget test enforces.

Measurements

Harness pingpong, 120k messages at 12k/s, against b08ba338. Runs are interleaved ABBA/BAAB rather than blocked, so host drift is not attributed to the branch. Every run in every table had lost=0 and ack=120000.

metric main branch delta
allocator calls per message 185.01 (sd 0.23, n=3) 174.34 (sd 0.08, n=3) -5.77%
bytes requested per message 31 208 (sd 10) 30 130 (sd 4) -3.46%
CPU total, MODE=normal 10.44 s (sd 0.33, n=5) 9.93 s (sd 0.32, n=5) -4.85%
RSS peak 70.8 MB 71.1 MB no change
pong latency 0.94 ms 0.95 ms no change

The CPU figure is the one worth qualifying. Welch t = 2.47, df = 8, p ~ 0.013 two-sided, so it survives the noise across five interleaved pairs, but the min/max ranges do touch (main 10.17-11.00, branch 9.54-10.35). Treat it as a real but modest effect, not a headline. The allocation figures have no such ambiguity: the standard deviations are two orders of magnitude below the difference.

Note that allocator calls counts alloc/alloc_zeroed/realloc calls, not live objects, and bytes requested counts new allocations plus the positive delta of a realloc. They measure pressure on the allocator, not heap residency.

Call-site proof

Aggregate numbers do not show what was removed, so the dhat pass (20k messages) is diffed per call site. Everything the change targets disappears, and the only new cost is the listener the persistent workers park on:

blocks/msg call site
-4.01 Vec<u8>::with_capacity_in (message-id staging buffer among others)
-2.97 Box<TokioRuntime::spawn::{closure}>
-2.97 Box<tokio::runtime::task::Cell> (the spawned tasks themselves)
-1.01 Box<bytes::Shared>
-1.00 Box<tokio::time::sleep::Sleep> (the phash timer)
-1.00 Box<maybe_deferred_ack::{closure}>
-1.00 Box<ArcInner<oneshot::Inner<OwnedNodeRef>>> (the phash oneshot)
-1.00 Box<spawn_phash_validation::{closure}>
-1.00 Box<ArcInner<OwnedNodeRef>>
-0.97 Box<MsgSecretWriteBuffer::schedule_drain::{closure}>
+1.60 Box<event_listener::InnerListener> (workers parked on their channel)
-15.48 total, 187.26 -> 171.78 blocks/msg in the dhat regime

Verification

cargo fmt --all, cargo clippy --all-targets clean, cargo test -p whatsapp-rust --lib green (1183 tests, including three new ones: the message-id shape/uniqueness pins and a sweep test asserting that expired phash waiters are dropped while IQ waiters are never touched).

What is deliberately not here

Two further candidates were identified and left out of this PR rather than rushed into it:

  • Reusing the WABinary buffer as the Noise buffer (removing two copies per frame, aimed at the ~6.6% of profile samples in memcpy). It changes the encrypt/framing path, and the sender should first be made to poison itself on a transport error: today the write counter advances only after transport.send returns Ok, so an ambiguous partial write can let the same counter, and therefore the same AES-GCM nonce, be reused. That fix belongs in front of the optimization, in its own PR.
  • Memoizing the DM device fanout. A stale fanout silently drops a recipient device, so it needs the same topology-generation invalidation the group memo already has, plus tests for PN/LID migration, device add/remove and explicit refresh. Not a change to land alongside unrelated work.

Review notes

Two findings on the ack worker were checked against the code and are recorded here rather than changed:

Acks skipped during an expected disconnect. send_ack_for returns Ok(()) early while expected_disconnect is set, so a queued ack is dropped rather than sent. That early return predates this PR and the previous spawn-per-ack path hit it identically, so no ack is lost that was being sent before. What is new is that the FlushGuard makes disconnect()'s flush wait for entries that will then be skipped, which costs a short wait rather than losing anything. Sending pending acks before marking the disconnect expected is a change to the shutdown ordering and belongs in its own PR.

Byte accounting for the queues. MemoryReport reports both worker queues by entry count, not by retained bytes, so a backlog contributes zero to total_estimated_bytes(). An async_channel cannot be iterated, so the only way to total the retained nodes is a counter maintained on every push and pop, which puts bookkeeping on the hot path for a secondary diagnostic. The entry count already surfaces the backlog, and it is what the pre-existing delivery-receipt queue reports too.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of server acknowledgements and response matching, including phash validation and expiration.
    • Preserved acknowledgement ordering and reliability during deferred delivery and disconnects.
    • Fixed long-lived message capture processing across scheduler cycles.
    • Improved keepalive cleanup of expired response waiters.
  • Performance

    • Reduced per-ack and buffer-drain task creation through persistent workers and queues.
    • Optimized message ID generation without changing its format.
  • Tests

    • Added coverage for waiter expiration, message ID uniqueness, and multi-cycle capture persistence.

Walkthrough

The PR unifies IQ and phash response waiters, adds epoch-based phash cleanup, queues deferred transport ACKs through persistent workers, refactors secret-buffer draining, streams message-ID hashing, expands tests and memory reporting, and ignores generated snapshot artifacts.

Changes

Runtime waiter and queue flows

Layer / File(s) Summary
Typed waiter contracts and registration
src/client.rs, src/client/messaging.rs, src/request.rs, src/client/node_io.rs
Response waiters now represent IQ or phash entries, with epoch-based phash expiration and updated insertion, removal, and registration APIs.
ACK dispatch and transport queue
src/client.rs, src/client/lifecycle.rs, src/client/node_io.rs
Deferred transport ACKs use a persistent FIFO worker and flush guards; ACK dispatch selects IQ or phash handling.
Send-path phash validation
src/send/mod.rs
Send paths register phash waiters directly, use the outgoing ACK identifier for cleanup, and remove the prior background validation helper.
Runtime wiring and waiter coverage
src/client/accessors.rs, src/client/tests.rs, src/keepalive.rs, src/test_utils.rs
Queue lengths are reported, keepalive sweeps phash waiters, and waiter resolution and sweep behavior are updated in tests and helpers.

Secret-buffer drain worker

Layer / File(s) Summary
Wake-driven buffer draining
src/msg_secret_buffer.rs
A bounded wake channel and persistent worker replace flag-based drain scheduling, with sealing terminating the worker and tests covering multiple scheduler turns.

Message-ID generation

Layer / File(s) Summary
Streamed message-ID generation
src/request.rs, wacore/src/request.rs
Message-ID generation calls the direct utility path, streams hash inputs, uses thread-local randomness, and tests format and uniqueness properties.

Test artifact hygiene

Layer / File(s) Summary
Snapshot artifact ignore rules
.gitignore
Diagnostic store snapshot filename and file-URI patterns are ignored.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: performance, api-design

Suggested reviewers: cubic-dev-ai, greptile-apps

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: reducing per-message tasks and buffers in the send/receive round trip.
Description check ✅ Passed The description is detailed and clearly describes the same performance-focused round-trip changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/pingpong-round2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown

Greptile Summary

This PR reduces per-message allocation and task overhead across message IDs, acknowledgements, phash validation, and message-secret persistence.

  • Derives message IDs incrementally using the thread-local RNG.
  • Replaces per-message secret-drain and deferred-ack tasks with shared workers.
  • Moves common-case phash comparison into the read loop and sweeps unanswered waiters from keepalive.
  • Adds queue observability and regression tests for the new worker and waiter behavior.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/client/node_io.rs Adds the shared deferred-ack worker and performs phash comparisons while routing incoming acknowledgements.
src/client.rs Introduces typed IQ/phash waiters, epoch-based phash cleanup, ack-worker state, and queue metrics.
src/send/mod.rs Registers phash expectations before sending and retains asynchronous work only for the mismatch path.
src/msg_secret_buffer.rs Replaces repeated drain tasks with a weakly owned, capacity-one-woken worker while preserving terminal inline flushing.
src/keepalive.rs Sweeps unanswered phash waiters on every keepalive tick before activity-based early returns.
wacore/src/request.rs Generates message IDs without a staging vector or newly seeded per-message RNG.

Sequence Diagram

sequenceDiagram
    participant Send as Send pipeline
    participant Waiters as Response waiter map
    participant Server as WhatsApp server
    participant Read as Read loop
    participant Cache as Device/group caches
    Send->>Waiters: Register expected phash
    Send->>Server: Send encrypted stanza
    Server-->>Read: Ack with phash
    Read->>Waiters: Remove waiter by message ID
    alt phash matches
        Read-->>Send: Complete inline
    else phash differs
        Read->>Cache: Spawn cache invalidation
    end
    loop Keepalive tick
        Read->>Waiters: Sweep expired phash waiters
    end
Loading

Reviews (6): Last reviewed commit: "fix(voip): gate the node-returning ack w..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown

📦 Binary size report

Metric main PR Δ
bin size (stripped) 9.95 MiB 9.96 MiB +5.28 KiB (+0.05%) 🔺
bin .text 8.00 MiB 8.00 MiB +4.75 KiB (+0.06%) 🔺
bin allocated (text+data+bss) 9.95 MiB 9.95 MiB +4.09 KiB (+0.04%) 🔺
llvm-lines wacore 492,142 492,078 -64 (-0.01%) 🔽
llvm-lines wacore copies 16,337 16,335 -2 (-0.01%) 🔽
llvm-lines whatsapp-rust lib 709,263 712,178 +2,915 (+0.41%) 🔺
llvm-lines whatsapp-rust lib copies 22,353 22,420 +67 (+0.30%) 🔺
deps crates (Cargo.lock) 471 471 0
.text per crate
Crate main PR Δ
.text whatsapp_rust 1.79 MiB 1.79 MiB +4.57 KiB (+0.25%) 🔺
.text wacore 648.84 KiB 648.85 KiB +2 B (+0.00%) 🔺
.text wacore_binary 89.42 KiB 89.42 KiB 0
.text wacore_libsignal 161.84 KiB 161.84 KiB 0
.text wacore_appstate 22.36 KiB 22.36 KiB 0
.text wacore_noise 21.60 KiB 21.60 KiB 0
.text waproto 1.74 MiB 1.74 MiB 0
.text whatsapp_rust_sqlite_storage 514.77 KiB 514.77 KiB 0
.text whatsapp_rust_tokio_transport 39.91 KiB 39.91 KiB 0
.text whatsapp_rust_ureq_http_client 10.40 KiB 10.40 KiB 0
.text std 1.07 MiB 1.07 MiB +327 B (+0.03%) 🔺
.text other deps 1.89 MiB 1.89 MiB -245 B (-0.01%) 🔽
Top movers (cargo-bloat attribution)
Crate main PR Δ
whatsapp_rust 1.79 MiB 1.79 MiB +4.57 KiB (+0.25%)

Baseline: 44614311a (latest main run) · Head: bc1d9022c · Graphs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/keepalive.rs (1)

81-93: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

The sweep never runs on a busy connection — phash waiters leak for the life of the socket.

This is the part that has to work right. The sweep lives inside send_keepalive, but keepalive_loop returns early at Line 179 whenever data arrived within KEEP_ALIVE_INTERVAL_MIN — it continues before Line 196 ever calls send_keepalive. So on any connection carrying real traffic, drop_expired_phash is never invoked.

Every DM/group/status send now registers a phash waiter (src/send/mod.rs Line 1766 and Line 1236). Each one whose ack is lost stays in response_waiters forever. On the exact 120k-message workload this PR benchmarks, the recent-activity early-return fires on every tick, so the map grows monotonically — unbounded memory plus a widening std::sync::Mutex critical section on the send hot path. That trades the allocation win straight back.

Hoist the sweep into the tick body, above the early-return, next to spawn_retention_cleanup which is already positioned there for this same reason.

🐛 Proposed fix: sweep on every tick, not only when a ping is sent

In send_keepalive, drop the sweep and keep the plain check:

         // WA Web: skip ping if there are pending IQs
         // (`activePing || ackHandlers.length || pendingIqs.size`)
-        //
-        // Sweep first: a phash waiter is resolved by an ack that may never come,
-        // and nothing else polls it. Leaving one behind would read as "IQ
-        // pending" and silence keepalives for the life of the connection.
-        let has_pending = {
-            let mut waiters = self.response_waiters_guard();
-            waiters.drop_expired_phash(wacore::time::now_secs());
-            !waiters.is_empty()
-        };
-        if has_pending {
+        if !self.response_waiters_guard().is_empty() {
             debug!(target: "Client/Keepalive", "Skipping ping: IQ responses pending");
             return KeepaliveResult::Ok;
         }

Then run it on every tick in keepalive_loop, before the recent-activity early-return:

                     cleanup_counter += 1;
                     if cleanup_counter >= 12 {
                         cleanup_counter = 0;
                         self.spawn_retention_cleanup(sent_msg_ttl);
                     }
 
+                    // A phash waiter is resolved by an ack that may never come, and
+                    // nothing else polls it. Sweep on the tick, not inside the ping:
+                    // a busy connection skips the ping entirely and would otherwise
+                    // accumulate a waiter per lost ack for the life of the socket.
+                    self.response_waiters_guard()
+                        .drop_expired_phash(wacore::time::now_secs());
+
                     let last_recv = self.stats.last_data_received_ms();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/keepalive.rs` around lines 81 - 93, Move the expired-phash waiter sweep
out of send_keepalive and into keepalive_loop’s per-tick path, immediately
before the recent-activity early return and alongside spawn_retention_cleanup.
Keep send_keepalive limited to checking whether response waiters remain,
preserving its existing pending-IQ behavior while ensuring drop_expired_phash
runs on every tick.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/client.rs`:
- Around line 735-737: Rename the ResponseWaiterEntry.sender field to waiter and
update every construction, access, pattern, and destructuring site, including
entry.sender in drop_expired_phash, so no stale sender references remain.

---

Outside diff comments:
In `@src/keepalive.rs`:
- Around line 81-93: Move the expired-phash waiter sweep out of send_keepalive
and into keepalive_loop’s per-tick path, immediately before the recent-activity
early return and alongside spawn_retention_cleanup. Keep send_keepalive limited
to checking whether response waiters remain, preserving its existing pending-IQ
behavior while ensuring drop_expired_phash runs on every tick.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7555878f-9cf0-4058-a08e-21384950b32e

📥 Commits

Reviewing files that changed from the base of the PR and between b08ba33 and da91a8b.

📒 Files selected for processing (11)
  • src/client.rs
  • src/client/lifecycle.rs
  • src/client/messaging.rs
  • src/client/node_io.rs
  • src/client/tests.rs
  • src/keepalive.rs
  • src/msg_secret_buffer.rs
  • src/request.rs
  • src/send/mod.rs
  • src/test_utils.rs
  • wacore/src/request.rs

Comment thread src/client.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: da91a8b6cf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/client/messaging.rs Outdated
expected,
jid,
invalidate_group_cache,
expires_at_secs: sent_at_secs + PHASH_ACK_TIMEOUT_SECS,

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 Measure the phash timeout from waiter registration

When stanza preparation or the pre-wire persistence flush takes close to ten seconds, this deadline is already stale when the waiter is registered because sent_at_secs was sampled at the beginning of send_message_impl; a concurrent keepalive tick can therefore remove the waiter immediately, causing a subsequent phash-mismatching ack to skip the required device/group cache invalidation. It is also a wall-clock deadline even though wacore/src/time.rs explicitly permits that clock to jump, so clock adjustments can expire live waiters or retain lost-ack waiters. Base the timeout on a monotonic instant captured when the waiter is inserted, matching the previous timer's ten seconds after sending.

Useful? React with 👍 / 👎.

Comment thread src/keepalive.rs Outdated
Comment on lines +85 to +87
let has_pending = {
let mut waiters = self.response_waiters_guard();
waiters.drop_expired_phash(wacore::time::now_secs());

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 Sweep expired phash waiters on every keepalive tick

When the connection continuously receives traffic at least once every 15 seconds, keepalive_loop takes its recent-activity continue before calling send_keepalive, so this is never reached. Any outgoing-message ack lost during that period therefore leaves its phash entry in response_waiters indefinitely, and repeated lost acks make the map grow for as long as inbound traffic remains active; move the sweep to the periodic loop before the recent-activity early return.

Useful? React with 👍 / 👎.

Comment thread src/client.rs
Comment on lines +1098 to +1102
pub(crate) transport_ack_queue: std::sync::OnceLock<
async_channel::Sender<(
Arc<wacore_binary::OwnedNodeRef>,
crate::flush_scope::FlushGuard,
)>,

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 Include the transport-ack queue in memory reports

When the transport is slow or stalled, this unbounded queue can retain many full inbound nodes and flush guards, but Client::memory_report() does not count either its entries or retained bytes. That makes the report understate precisely the growth introduced by the new persistent worker and prevents the documented per-session leak diagnostics from identifying an ack backlog; expose the queue length and estimated node retention in MemoryReport.

AGENTS.md reference: AGENTS.md:L54-L54

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST`:
- Around line 68-71: Remove the diagnostic SQLite artifacts
memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST (lines
68-71), memdb_capt_dup_receipt_16_1748392.snapshot-1785024799-pre_pkmsg_EST
(lines 68-71), and
memdb_capt_group_skmsg_lock_happy_31_1745544.snapshot-1785024780-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY
(lines 68-71) from version control, then add the memdb_*.snapshot-* pattern to
.gitignore. If persisted-state assertions are needed, update them to compare
queried, normalized schema and row projections rather than raw SQLite images;
PersistenceManager::create_snapshot requires no direct change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 47dfc13b-9d5b-46be-b335-fc1f52ee0197

📥 Commits

Reviewing files that changed from the base of the PR and between da91a8b and 99f14f0.

📒 Files selected for processing (105)
  • file:memdb_badmac_lid_shadow_262_1745544.snapshot-1785024781-pre_pkmsg_.json
  • file:memdb_badmac_lid_shadow_262_1748392.snapshot-1785024800-pre_pkmsg_.json
  • file:memdb_badmac_preserves_263_1745544.snapshot-1785024781-pre_pkmsg_.json
  • file:memdb_badmac_preserves_263_1748392.snapshot-1785024800-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_closed_flush_1_1745544.snapshot-1785024780-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_closed_flush_1_1748392.snapshot-1785024799-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_offline_3_1745544.snapshot-1785024780-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_offline_3_1748392.snapshot-1785024799-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_prepare_retry_0_1745544.snapshot-1785024780-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_prepare_retry_0_1748392.snapshot-1785024799-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_retry_2_1745544.snapshot-1785024780-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_retry_2_1748392.snapshot-1785024799-pre_pkmsg_.json
  • file:memdb_capt_bad_plaintext_no_skdm_ack_75_1745544.snapshot-1785024781-pre_pkmsg_BAD_SESSION_PLAINTEXT.json
  • file:memdb_capt_bad_plaintext_no_skdm_ack_75_1748392.snapshot-1785024800-pre_pkmsg_BAD_SESSION_PLAINTEXT.json
  • file:memdb_capt_bad_session_skips_skmsg_4_1745544.snapshot-1785024780-pre_pkmsg_BAD_SESSION_WITH_SKMSG.json
  • file:memdb_capt_bad_session_skips_skmsg_4_1748392.snapshot-1785024799-pre_pkmsg_BAD_SESSION_WITH_SKMSG.json
  • file:memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST.json
  • file:memdb_capt_dup_receipt_16_1748392.snapshot-1785024799-pre_pkmsg_EST.json
  • file:memdb_capt_group_skmsg_lock_happy_31_1745544.snapshot-1785024780-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY.json
  • file:memdb_capt_group_skmsg_lock_happy_31_1748392.snapshot-1785024799-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY.json
  • file:memdb_capt_migration_plaintext_nack_44_1745544.snapshot-1785024781-pre_pkmsg_.json
  • file:memdb_capt_migration_plaintext_nack_44_1748392.snapshot-1785024799-pre_pkmsg_.json
  • file:memdb_capt_mixed_skdm_bad_plaintext_47_1745544.snapshot-1785024781-pre_pkmsg_SKDM_WITH_BAD_SESSION.json
  • file:memdb_capt_mixed_skdm_bad_plaintext_47_1748392.snapshot-1785024799-pre_pkmsg_SKDM_WITH_BAD_SESSION.json
  • file:memdb_capt_own_group_skdm_ack_63_1745544.snapshot-1785024781-pre_pkmsg_OWN_GROUP_SKDM_ONLY.json
  • file:memdb_capt_own_group_skdm_ack_63_1748392.snapshot-1785024800-pre_pkmsg_OWN_GROUP_SKDM_ONLY.json
  • file:memdb_capt_pkmsg_parse_nack_65_1745544.snapshot-1785024781-pre_pkmsg_REGRESSION_PKMSG_PARSE.json
  • file:memdb_capt_pkmsg_parse_nack_65_1748392.snapshot-1785024800-pre_pkmsg_REGRESSION_PKMSG_PARSE.json
  • file:memdb_capt_session_content_group_ack_74_1745544.snapshot-1785024781-pre_pkmsg_SESSION_CONTENT_GROUP.json
  • file:memdb_capt_session_content_group_ack_74_1748392.snapshot-1785024800-pre_pkmsg_SESSION_CONTENT_GROUP.json
  • file:memdb_capt_skdm_msmsg_no_fallback_ack_78_1745544.snapshot-1785024781-pre_pkmsg_SKDM_WITH_MSMSG.json
  • file:memdb_capt_skdm_msmsg_no_fallback_ack_78_1748392.snapshot-1785024800-pre_pkmsg_SKDM_WITH_MSMSG.json
  • file:memdb_capt_skdm_only_group_ack_77_1745544.snapshot-1785024781-pre_pkmsg_SKDM_ONLY_SESSION.json
  • file:memdb_capt_skdm_only_group_ack_77_1748392.snapshot-1785024800-pre_pkmsg_SKDM_ONLY_SESSION.json
  • file:memdb_capt_skdm_plus_skmsg_ack_79_1745544.snapshot-1785024781-pre_pkmsg_SKDM_WITH_SKMSG.json
  • file:memdb_capt_skdm_plus_skmsg_ack_79_1748392.snapshot-1785024800-pre_pkmsg_SKDM_WITH_SKMSG.json
  • file:memdb_capt_status_skdm_only_ack_81_1745544.snapshot-1785024781-pre_pkmsg_STATUS_SKDM_ONLY.json
  • file:memdb_capt_status_skdm_only_ack_81_1748392.snapshot-1785024800-pre_pkmsg_STATUS_SKDM_ONLY.json
  • file:memdb_invalid_spk_id_264_1745544.snapshot-1785024781-pre_pkmsg_INVALID_SPK_ID_MSG.json
  • file:memdb_invalid_spk_id_264_1748392.snapshot-1785024800-pre_pkmsg_INVALID_SPK_ID_MSG.json
  • file:memdb_lid_cache_test.snapshot-1785024781-pre_pkmsg_TEST123456789.json
  • file:memdb_lid_cache_test.snapshot-1785024800-pre_pkmsg_TEST123456789.json
  • file:memdb_no_lid_cache_test.snapshot-1785024781-pre_pkmsg_TEST123456789.json
  • file:memdb_no_lid_cache_test.snapshot-1785024800-pre_pkmsg_TEST123456789.json
  • file:memdb_prod_archive_265_1745544.snapshot-1785024782-pre_pkmsg_.json
  • file:memdb_prod_archive_265_1748392.snapshot-1785024801-pre_pkmsg_.json
  • file:memdb_repeated_msg_test.snapshot-1785024781-pre_pkmsg_TEST0.json
  • file:memdb_repeated_msg_test.snapshot-1785024781-pre_pkmsg_TEST1.json
  • file:memdb_repeated_msg_test.snapshot-1785024781-pre_pkmsg_TEST2.json
  • file:memdb_repeated_msg_test.snapshot-1785024800-pre_pkmsg_TEST0.json
  • file:memdb_repeated_msg_test.snapshot-1785024800-pre_pkmsg_TEST1.json
  • file:memdb_repeated_msg_test.snapshot-1785024800-pre_pkmsg_TEST2.json
  • memdb_badmac_lid_shadow_262_1745544.snapshot-1785024781-pre_pkmsg_
  • memdb_badmac_lid_shadow_262_1748392.snapshot-1785024800-pre_pkmsg_
  • memdb_badmac_preserves_263_1745544.snapshot-1785024781-pre_pkmsg_
  • memdb_badmac_preserves_263_1748392.snapshot-1785024800-pre_pkmsg_
  • memdb_capt_appstate_key_share_closed_flush_1_1745544.snapshot-1785024780-pre_pkmsg_
  • memdb_capt_appstate_key_share_closed_flush_1_1748392.snapshot-1785024799-pre_pkmsg_
  • memdb_capt_appstate_key_share_offline_3_1745544.snapshot-1785024780-pre_pkmsg_
  • memdb_capt_appstate_key_share_offline_3_1748392.snapshot-1785024799-pre_pkmsg_
  • memdb_capt_appstate_key_share_prepare_retry_0_1745544.snapshot-1785024780-pre_pkmsg_
  • memdb_capt_appstate_key_share_prepare_retry_0_1748392.snapshot-1785024799-pre_pkmsg_
  • memdb_capt_appstate_key_share_retry_2_1745544.snapshot-1785024780-pre_pkmsg_
  • memdb_capt_appstate_key_share_retry_2_1748392.snapshot-1785024799-pre_pkmsg_
  • memdb_capt_bad_plaintext_no_skdm_ack_75_1745544.snapshot-1785024781-pre_pkmsg_BAD_SESSION_PLAINTEXT
  • memdb_capt_bad_plaintext_no_skdm_ack_75_1748392.snapshot-1785024800-pre_pkmsg_BAD_SESSION_PLAINTEXT
  • memdb_capt_bad_session_skips_skmsg_4_1745544.snapshot-1785024780-pre_pkmsg_BAD_SESSION_WITH_SKMSG
  • memdb_capt_bad_session_skips_skmsg_4_1748392.snapshot-1785024799-pre_pkmsg_BAD_SESSION_WITH_SKMSG
  • memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST
  • memdb_capt_dup_receipt_16_1748392.snapshot-1785024799-pre_pkmsg_EST
  • memdb_capt_group_skmsg_lock_happy_31_1745544.snapshot-1785024780-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY
  • memdb_capt_group_skmsg_lock_happy_31_1748392.snapshot-1785024799-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY
  • memdb_capt_migration_plaintext_nack_44_1745544.snapshot-1785024781-pre_pkmsg_
  • memdb_capt_migration_plaintext_nack_44_1748392.snapshot-1785024799-pre_pkmsg_
  • memdb_capt_mixed_skdm_bad_plaintext_47_1745544.snapshot-1785024781-pre_pkmsg_SKDM_WITH_BAD_SESSION
  • memdb_capt_mixed_skdm_bad_plaintext_47_1748392.snapshot-1785024799-pre_pkmsg_SKDM_WITH_BAD_SESSION
  • memdb_capt_own_group_skdm_ack_63_1745544.snapshot-1785024781-pre_pkmsg_OWN_GROUP_SKDM_ONLY
  • memdb_capt_own_group_skdm_ack_63_1748392.snapshot-1785024800-pre_pkmsg_OWN_GROUP_SKDM_ONLY
  • memdb_capt_pkmsg_parse_nack_65_1745544.snapshot-1785024781-pre_pkmsg_REGRESSION_PKMSG_PARSE
  • memdb_capt_pkmsg_parse_nack_65_1748392.snapshot-1785024800-pre_pkmsg_REGRESSION_PKMSG_PARSE
  • memdb_capt_session_content_group_ack_74_1745544.snapshot-1785024781-pre_pkmsg_SESSION_CONTENT_GROUP
  • memdb_capt_session_content_group_ack_74_1748392.snapshot-1785024800-pre_pkmsg_SESSION_CONTENT_GROUP
  • memdb_capt_skdm_msmsg_no_fallback_ack_78_1745544.snapshot-1785024781-pre_pkmsg_SKDM_WITH_MSMSG
  • memdb_capt_skdm_msmsg_no_fallback_ack_78_1748392.snapshot-1785024800-pre_pkmsg_SKDM_WITH_MSMSG
  • memdb_capt_skdm_only_group_ack_77_1745544.snapshot-1785024781-pre_pkmsg_SKDM_ONLY_SESSION
  • memdb_capt_skdm_only_group_ack_77_1748392.snapshot-1785024800-pre_pkmsg_SKDM_ONLY_SESSION
  • memdb_capt_skdm_plus_skmsg_ack_79_1745544.snapshot-1785024781-pre_pkmsg_SKDM_WITH_SKMSG
  • memdb_capt_skdm_plus_skmsg_ack_79_1748392.snapshot-1785024800-pre_pkmsg_SKDM_WITH_SKMSG
  • memdb_capt_status_skdm_only_ack_81_1745544.snapshot-1785024781-pre_pkmsg_STATUS_SKDM_ONLY
  • memdb_capt_status_skdm_only_ack_81_1748392.snapshot-1785024800-pre_pkmsg_STATUS_SKDM_ONLY
  • memdb_invalid_spk_id_264_1745544.snapshot-1785024781-pre_pkmsg_INVALID_SPK_ID_MSG
  • memdb_invalid_spk_id_264_1748392.snapshot-1785024800-pre_pkmsg_INVALID_SPK_ID_MSG
  • memdb_lid_cache_test.snapshot-1785024781-pre_pkmsg_TEST123456789
  • memdb_lid_cache_test.snapshot-1785024800-pre_pkmsg_TEST123456789
  • memdb_no_lid_cache_test.snapshot-1785024781-pre_pkmsg_TEST123456789
  • memdb_no_lid_cache_test.snapshot-1785024800-pre_pkmsg_TEST123456789
  • memdb_prod_archive_265_1745544.snapshot-1785024782-pre_pkmsg_
  • memdb_prod_archive_265_1748392.snapshot-1785024801-pre_pkmsg_
  • memdb_repeated_msg_test.snapshot-1785024781-pre_pkmsg_TEST0
  • memdb_repeated_msg_test.snapshot-1785024781-pre_pkmsg_TEST1
  • memdb_repeated_msg_test.snapshot-1785024781-pre_pkmsg_TEST2
  • memdb_repeated_msg_test.snapshot-1785024800-pre_pkmsg_TEST0
  • memdb_repeated_msg_test.snapshot-1785024800-pre_pkmsg_TEST1
  • memdb_repeated_msg_test.snapshot-1785024800-pre_pkmsg_TEST2
  • src/client/messaging.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST`:
- Around line 68-71: Remove the diagnostic SQLite artifacts
memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST (lines
68-71), memdb_capt_dup_receipt_16_1748392.snapshot-1785024799-pre_pkmsg_EST
(lines 68-71), and
memdb_capt_group_skmsg_lock_happy_31_1745544.snapshot-1785024780-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY
(lines 68-71) from version control, then add the memdb_*.snapshot-* pattern to
.gitignore. If persisted-state assertions are needed, update them to compare
queried, normalized schema and row projections rather than raw SQLite images;
PersistenceManager::create_snapshot requires no direct change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 47dfc13b-9d5b-46be-b335-fc1f52ee0197

📥 Commits

Reviewing files that changed from the base of the PR and between da91a8b and 99f14f0.

📒 Files selected for processing (105)
  • file:memdb_badmac_lid_shadow_262_1745544.snapshot-1785024781-pre_pkmsg_.json
  • file:memdb_badmac_lid_shadow_262_1748392.snapshot-1785024800-pre_pkmsg_.json
  • file:memdb_badmac_preserves_263_1745544.snapshot-1785024781-pre_pkmsg_.json
  • file:memdb_badmac_preserves_263_1748392.snapshot-1785024800-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_closed_flush_1_1745544.snapshot-1785024780-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_closed_flush_1_1748392.snapshot-1785024799-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_offline_3_1745544.snapshot-1785024780-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_offline_3_1748392.snapshot-1785024799-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_prepare_retry_0_1745544.snapshot-1785024780-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_prepare_retry_0_1748392.snapshot-1785024799-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_retry_2_1745544.snapshot-1785024780-pre_pkmsg_.json
  • file:memdb_capt_appstate_key_share_retry_2_1748392.snapshot-1785024799-pre_pkmsg_.json
  • file:memdb_capt_bad_plaintext_no_skdm_ack_75_1745544.snapshot-1785024781-pre_pkmsg_BAD_SESSION_PLAINTEXT.json
  • file:memdb_capt_bad_plaintext_no_skdm_ack_75_1748392.snapshot-1785024800-pre_pkmsg_BAD_SESSION_PLAINTEXT.json
  • file:memdb_capt_bad_session_skips_skmsg_4_1745544.snapshot-1785024780-pre_pkmsg_BAD_SESSION_WITH_SKMSG.json
  • file:memdb_capt_bad_session_skips_skmsg_4_1748392.snapshot-1785024799-pre_pkmsg_BAD_SESSION_WITH_SKMSG.json
  • file:memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST.json
  • file:memdb_capt_dup_receipt_16_1748392.snapshot-1785024799-pre_pkmsg_EST.json
  • file:memdb_capt_group_skmsg_lock_happy_31_1745544.snapshot-1785024780-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY.json
  • file:memdb_capt_group_skmsg_lock_happy_31_1748392.snapshot-1785024799-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY.json
  • file:memdb_capt_migration_plaintext_nack_44_1745544.snapshot-1785024781-pre_pkmsg_.json
  • file:memdb_capt_migration_plaintext_nack_44_1748392.snapshot-1785024799-pre_pkmsg_.json
  • file:memdb_capt_mixed_skdm_bad_plaintext_47_1745544.snapshot-1785024781-pre_pkmsg_SKDM_WITH_BAD_SESSION.json
  • file:memdb_capt_mixed_skdm_bad_plaintext_47_1748392.snapshot-1785024799-pre_pkmsg_SKDM_WITH_BAD_SESSION.json
  • file:memdb_capt_own_group_skdm_ack_63_1745544.snapshot-1785024781-pre_pkmsg_OWN_GROUP_SKDM_ONLY.json
  • file:memdb_capt_own_group_skdm_ack_63_1748392.snapshot-1785024800-pre_pkmsg_OWN_GROUP_SKDM_ONLY.json
  • file:memdb_capt_pkmsg_parse_nack_65_1745544.snapshot-1785024781-pre_pkmsg_REGRESSION_PKMSG_PARSE.json
  • file:memdb_capt_pkmsg_parse_nack_65_1748392.snapshot-1785024800-pre_pkmsg_REGRESSION_PKMSG_PARSE.json
  • file:memdb_capt_session_content_group_ack_74_1745544.snapshot-1785024781-pre_pkmsg_SESSION_CONTENT_GROUP.json
  • file:memdb_capt_session_content_group_ack_74_1748392.snapshot-1785024800-pre_pkmsg_SESSION_CONTENT_GROUP.json
  • file:memdb_capt_skdm_msmsg_no_fallback_ack_78_1745544.snapshot-1785024781-pre_pkmsg_SKDM_WITH_MSMSG.json
  • file:memdb_capt_skdm_msmsg_no_fallback_ack_78_1748392.snapshot-1785024800-pre_pkmsg_SKDM_WITH_MSMSG.json
  • file:memdb_capt_skdm_only_group_ack_77_1745544.snapshot-1785024781-pre_pkmsg_SKDM_ONLY_SESSION.json
  • file:memdb_capt_skdm_only_group_ack_77_1748392.snapshot-1785024800-pre_pkmsg_SKDM_ONLY_SESSION.json
  • file:memdb_capt_skdm_plus_skmsg_ack_79_1745544.snapshot-1785024781-pre_pkmsg_SKDM_WITH_SKMSG.json
  • file:memdb_capt_skdm_plus_skmsg_ack_79_1748392.snapshot-1785024800-pre_pkmsg_SKDM_WITH_SKMSG.json
  • file:memdb_capt_status_skdm_only_ack_81_1745544.snapshot-1785024781-pre_pkmsg_STATUS_SKDM_ONLY.json
  • file:memdb_capt_status_skdm_only_ack_81_1748392.snapshot-1785024800-pre_pkmsg_STATUS_SKDM_ONLY.json
  • file:memdb_invalid_spk_id_264_1745544.snapshot-1785024781-pre_pkmsg_INVALID_SPK_ID_MSG.json
  • file:memdb_invalid_spk_id_264_1748392.snapshot-1785024800-pre_pkmsg_INVALID_SPK_ID_MSG.json
  • file:memdb_lid_cache_test.snapshot-1785024781-pre_pkmsg_TEST123456789.json
  • file:memdb_lid_cache_test.snapshot-1785024800-pre_pkmsg_TEST123456789.json
  • file:memdb_no_lid_cache_test.snapshot-1785024781-pre_pkmsg_TEST123456789.json
  • file:memdb_no_lid_cache_test.snapshot-1785024800-pre_pkmsg_TEST123456789.json
  • file:memdb_prod_archive_265_1745544.snapshot-1785024782-pre_pkmsg_.json
  • file:memdb_prod_archive_265_1748392.snapshot-1785024801-pre_pkmsg_.json
  • file:memdb_repeated_msg_test.snapshot-1785024781-pre_pkmsg_TEST0.json
  • file:memdb_repeated_msg_test.snapshot-1785024781-pre_pkmsg_TEST1.json
  • file:memdb_repeated_msg_test.snapshot-1785024781-pre_pkmsg_TEST2.json
  • file:memdb_repeated_msg_test.snapshot-1785024800-pre_pkmsg_TEST0.json
  • file:memdb_repeated_msg_test.snapshot-1785024800-pre_pkmsg_TEST1.json
  • file:memdb_repeated_msg_test.snapshot-1785024800-pre_pkmsg_TEST2.json
  • memdb_badmac_lid_shadow_262_1745544.snapshot-1785024781-pre_pkmsg_
  • memdb_badmac_lid_shadow_262_1748392.snapshot-1785024800-pre_pkmsg_
  • memdb_badmac_preserves_263_1745544.snapshot-1785024781-pre_pkmsg_
  • memdb_badmac_preserves_263_1748392.snapshot-1785024800-pre_pkmsg_
  • memdb_capt_appstate_key_share_closed_flush_1_1745544.snapshot-1785024780-pre_pkmsg_
  • memdb_capt_appstate_key_share_closed_flush_1_1748392.snapshot-1785024799-pre_pkmsg_
  • memdb_capt_appstate_key_share_offline_3_1745544.snapshot-1785024780-pre_pkmsg_
  • memdb_capt_appstate_key_share_offline_3_1748392.snapshot-1785024799-pre_pkmsg_
  • memdb_capt_appstate_key_share_prepare_retry_0_1745544.snapshot-1785024780-pre_pkmsg_
  • memdb_capt_appstate_key_share_prepare_retry_0_1748392.snapshot-1785024799-pre_pkmsg_
  • memdb_capt_appstate_key_share_retry_2_1745544.snapshot-1785024780-pre_pkmsg_
  • memdb_capt_appstate_key_share_retry_2_1748392.snapshot-1785024799-pre_pkmsg_
  • memdb_capt_bad_plaintext_no_skdm_ack_75_1745544.snapshot-1785024781-pre_pkmsg_BAD_SESSION_PLAINTEXT
  • memdb_capt_bad_plaintext_no_skdm_ack_75_1748392.snapshot-1785024800-pre_pkmsg_BAD_SESSION_PLAINTEXT
  • memdb_capt_bad_session_skips_skmsg_4_1745544.snapshot-1785024780-pre_pkmsg_BAD_SESSION_WITH_SKMSG
  • memdb_capt_bad_session_skips_skmsg_4_1748392.snapshot-1785024799-pre_pkmsg_BAD_SESSION_WITH_SKMSG
  • memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST
  • memdb_capt_dup_receipt_16_1748392.snapshot-1785024799-pre_pkmsg_EST
  • memdb_capt_group_skmsg_lock_happy_31_1745544.snapshot-1785024780-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY
  • memdb_capt_group_skmsg_lock_happy_31_1748392.snapshot-1785024799-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY
  • memdb_capt_migration_plaintext_nack_44_1745544.snapshot-1785024781-pre_pkmsg_
  • memdb_capt_migration_plaintext_nack_44_1748392.snapshot-1785024799-pre_pkmsg_
  • memdb_capt_mixed_skdm_bad_plaintext_47_1745544.snapshot-1785024781-pre_pkmsg_SKDM_WITH_BAD_SESSION
  • memdb_capt_mixed_skdm_bad_plaintext_47_1748392.snapshot-1785024799-pre_pkmsg_SKDM_WITH_BAD_SESSION
  • memdb_capt_own_group_skdm_ack_63_1745544.snapshot-1785024781-pre_pkmsg_OWN_GROUP_SKDM_ONLY
  • memdb_capt_own_group_skdm_ack_63_1748392.snapshot-1785024800-pre_pkmsg_OWN_GROUP_SKDM_ONLY
  • memdb_capt_pkmsg_parse_nack_65_1745544.snapshot-1785024781-pre_pkmsg_REGRESSION_PKMSG_PARSE
  • memdb_capt_pkmsg_parse_nack_65_1748392.snapshot-1785024800-pre_pkmsg_REGRESSION_PKMSG_PARSE
  • memdb_capt_session_content_group_ack_74_1745544.snapshot-1785024781-pre_pkmsg_SESSION_CONTENT_GROUP
  • memdb_capt_session_content_group_ack_74_1748392.snapshot-1785024800-pre_pkmsg_SESSION_CONTENT_GROUP
  • memdb_capt_skdm_msmsg_no_fallback_ack_78_1745544.snapshot-1785024781-pre_pkmsg_SKDM_WITH_MSMSG
  • memdb_capt_skdm_msmsg_no_fallback_ack_78_1748392.snapshot-1785024800-pre_pkmsg_SKDM_WITH_MSMSG
  • memdb_capt_skdm_only_group_ack_77_1745544.snapshot-1785024781-pre_pkmsg_SKDM_ONLY_SESSION
  • memdb_capt_skdm_only_group_ack_77_1748392.snapshot-1785024800-pre_pkmsg_SKDM_ONLY_SESSION
  • memdb_capt_skdm_plus_skmsg_ack_79_1745544.snapshot-1785024781-pre_pkmsg_SKDM_WITH_SKMSG
  • memdb_capt_skdm_plus_skmsg_ack_79_1748392.snapshot-1785024800-pre_pkmsg_SKDM_WITH_SKMSG
  • memdb_capt_status_skdm_only_ack_81_1745544.snapshot-1785024781-pre_pkmsg_STATUS_SKDM_ONLY
  • memdb_capt_status_skdm_only_ack_81_1748392.snapshot-1785024800-pre_pkmsg_STATUS_SKDM_ONLY
  • memdb_invalid_spk_id_264_1745544.snapshot-1785024781-pre_pkmsg_INVALID_SPK_ID_MSG
  • memdb_invalid_spk_id_264_1748392.snapshot-1785024800-pre_pkmsg_INVALID_SPK_ID_MSG
  • memdb_lid_cache_test.snapshot-1785024781-pre_pkmsg_TEST123456789
  • memdb_lid_cache_test.snapshot-1785024800-pre_pkmsg_TEST123456789
  • memdb_no_lid_cache_test.snapshot-1785024781-pre_pkmsg_TEST123456789
  • memdb_no_lid_cache_test.snapshot-1785024800-pre_pkmsg_TEST123456789
  • memdb_prod_archive_265_1745544.snapshot-1785024782-pre_pkmsg_
  • memdb_prod_archive_265_1748392.snapshot-1785024801-pre_pkmsg_
  • memdb_repeated_msg_test.snapshot-1785024781-pre_pkmsg_TEST0
  • memdb_repeated_msg_test.snapshot-1785024781-pre_pkmsg_TEST1
  • memdb_repeated_msg_test.snapshot-1785024781-pre_pkmsg_TEST2
  • memdb_repeated_msg_test.snapshot-1785024800-pre_pkmsg_TEST0
  • memdb_repeated_msg_test.snapshot-1785024800-pre_pkmsg_TEST1
  • memdb_repeated_msg_test.snapshot-1785024800-pre_pkmsg_TEST2
  • src/client/messaging.rs
🛑 Comments failed to post (1)
memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST (1)

68-71: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Stop committing debug-snapshots SQLite dumps to the repo. All three of these extensionless binaries are diagnostic output from PersistenceManager::create_snapshot (src/store/persistence_manager.rs:149-169). They share one root cause and one fix. They embed wall-clock __diesel_schema_migrations.run_on values plus SQLite page padding, so they are non-reproducible — the two dup_receipt files differ only because they were generated 19 seconds apart — and each one carries raw noise_key, identity_key, and adv_secret_key blobs for the test device. A PR whose stated goal is trimming allocator calls per message should not be shipping these.

  • memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST#L68-L71: remove this artifact from version control and add the memdb_*.snapshot-* pattern to .gitignore.
  • memdb_capt_dup_receipt_16_1748392.snapshot-1785024799-pre_pkmsg_EST#L68-L71: remove this artifact; it is the 19-second-later twin of the file above and demonstrates the byte-level non-determinism.
  • memdb_capt_group_skmsg_lock_happy_31_1745544.snapshot-1785024780-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY#L68-L71: remove this artifact under the same ignore rule.

If some test genuinely needs to assert against persisted DB state, assert on a queried, normalized projection of the schema and rows rather than on a raw page-level SQLite image.

📍 Affects 3 files
  • memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST#L68-L71 (this comment)
  • memdb_capt_dup_receipt_16_1748392.snapshot-1785024799-pre_pkmsg_EST#L68-L71
  • memdb_capt_group_skmsg_lock_happy_31_1745544.snapshot-1785024780-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY#L68-L71
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST` around
lines 68 - 71, Remove the diagnostic SQLite artifacts
memdb_capt_dup_receipt_16_1745544.snapshot-1785024780-pre_pkmsg_EST (lines
68-71), memdb_capt_dup_receipt_16_1748392.snapshot-1785024799-pre_pkmsg_EST
(lines 68-71), and
memdb_capt_group_skmsg_lock_happy_31_1745544.snapshot-1785024780-pre_pkmsg_GROUP_SKMSG_LOCK_HAPPY
(lines 68-71) from version control, then add the memdb_*.snapshot-* pattern to
.gitignore. If persisted-state assertions are needed, update them to compare
queried, normalized schema and row projections rather than raw SQLite images;
PersistenceManager::create_snapshot requires no direct change.

Source: Coding guidelines

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66bf9ed4c8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -0,0 +1,7 @@
{

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 Remove generated SQLite snapshot artifacts

This commit accidentally checks in 104 generated test artifacts at the repository root: 52 timestamped 245,760-byte SQLite snapshots and 52 companion JSON files, totaling about 13 MiB. They are not consumed by the source or tests, and future test runs generate differently named copies, so retaining them permanently bloats every clone and source archive; remove these files and ignore the snapshot output pattern.

Useful? React with 👍 / 👎.

Comment thread src/client/node_io.rs
let Some(client) = client.upgrade() else {
break;
};
if let Err(e) = client.send_ack_for(node.get()).await

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 Flush queued ACKs before marking the disconnect expected

When reconnect_immediately() runs with deferred ACKs still queued—especially behind a slow transport—it sets expected_disconnect before waiting on outbound_flush. Each queued item then reaches this call, but send_ack_for returns Ok(()) immediately while that flag is set, so the guard is dropped and the flush reports completion without sending the ACK; after reconnect, the server can replay already-processed receipt, notification, call, newsletter, or status stanzas. Drain this queue through a shutdown-safe send path or defer setting the flag until the ACK flush completes.

Useful? React with 👍 / 👎.

@jlucaso1
jlucaso1 force-pushed the perf/pingpong-round2 branch from 66bf9ed to 09fe4b6 Compare July 26, 2026 00:34

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d32f66353

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/client/accessors.rs
group_distribution_lock_evictions: group_distribution_locks.evictions,
group_distribution_lock_eviction_blocks: group_distribution_locks.eviction_blocks,
resend_rate_limiter_chats: self.resend_rate_limiter.entry_count(),
transport_ack_queue: self.transport_ack_queue.get().map_or(0, |tx| tx.len()),

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 Include queued ACK bytes in memory totals

When the transport stalls, each unbounded slot retains an entire OwnedNodeRef, but this attempted accounting records only Sender::len() as a usize; MemoryReport::total_estimated_bytes() sums only CollectionStats byte fields, so the potentially dominant backlog still contributes zero bytes. The fresh evidence beyond the earlier comment is that the queue is now visible by count while its payload retention remains absent from the report. Report queue retention as CollectionStats or otherwise add estimated payload bytes to the total so leak diagnostics reflect the stalled backlog.

AGENTS.md reference: AGENTS.md:L54-L54

Useful? React with 👍 / 👎.

jlucaso1 added 7 commits July 25, 2026 23:25
…esh rng

Naming an outgoing message went through three avoidable costs per send: a
RequestUtils built only to reach the derivation (cloning the unique id, which
the derivation never reads), a Vec staging the digest input, and a StdRng seeded
from the thread generator to produce sixteen bytes, which runs a full ChaCha key
schedule and showed up in the CPU profile.

The derivation is now an associated function fed incrementally, using the
thread-local generator directly. Tests pin the wire shape ("3EB0" plus eighteen
upper-case hex chars), uniqueness inside one second, and the JID-less form.
The drain task exited as soon as the pending map emptied, and under a steady
stream that is one spawned task per message: the next capture always arrives
after the previous one has been written. Replace it with a single worker woken
through a capacity-one channel, so a burst still collapses into one wakeup.

The worker is not permanent by ownership. It holds a Weak and the Sender lives
in the buffer, so dropping the buffer closes the channel and the worker returns;
seal() closes it explicitly because every queue writes inline from that point.
Write-behind semantics, the high-water mark and the terminal flush are
unchanged. A new test covers captures spread across scheduler turns, which is
what a worker that exited early would strand.
Every deferred ack spawned its own task. Feed the persistent-worker pattern
already used by delivery receipts instead: a channel slot plus a FlushGuard per
ack, with the worker holding a Weak so a dropped client ends it.

Two behaviours improve as a side effect. Acks now leave in arrival order, which
spawning never guaranteed, and disconnect()'s flush waits for a queued ack
because the guard rides the queue until the send returns; previously a spawned
ack could still be in flight when the flush completed. A closed scope drops the
ack exactly like the spawned path, whose send would have failed on an
unavailable transport.
Validating the phash cost a oneshot channel, a spawned task and a ten second
timer for every outgoing message, to run a string comparison that almost always
succeeds. The waiter map now holds what the check needs, so the read loop
compares when the ack lands and spawns only when the server disagrees, which is
the path that re-reads caches and can force sender-key redistribution.

Ordering is preserved: the waiter is still registered before the stanza goes
out, because a fast link can deliver the ack while send_node is still returning.

An unanswered ack no longer has a timer to clean up after it, so the keepalive
tick sweeps expired phash entries before deciding whether IQs are pending; a
leftover waiter would otherwise read as "pending" and silence pings for the life
of the connection. The deadline comes from the wall second the send already
sampled rather than a fresh read, which the send clock budget test enforces.
The phash rework replaced the only caller I checked, but the VoIP offer also
registers an ack waiter and reads the relay out of the response node, and that
call site only compiles under the voip feature. Restore the helper and document
which of the two registration paths each caller wants: the offer needs the node,
a phash check does not and therefore pays no channel per message.
…conds

Three review findings on the phash waiter, all real.

The sweep ran inside send_keepalive, which the loop skips entirely whenever
inbound traffic is recent. A busy connection therefore never swept, and every
lost ack left a waiter behind for as long as traffic kept flowing. It now runs
on the interval tick, next to the retention cleanup that is placed before the
same early return for the same reason.

Expiry no longer uses a wall deadline derived from the instant the send started.
That instant is sampled well before the waiter is registered, so a slow
preparation could register an already-expired waiter and lose the invalidation a
mismatching ack must trigger, and the wall clock is explicitly allowed to jump.
Waiters now record the sweep epoch, read under the lock the insert already
takes, and expire after living through one full sweep: no clock read on the send
path, and immune to clock adjustments. The window becomes one keepalive tick
(15 to 30 s) rather than a fixed 10 s.

Also report the two worker queues in MemoryReport. Both are unbounded and retain
inbound nodes plus flush guards, which is exactly the growth the persistent
workers introduce, and the report is the documented way to find it.

ResponseWaiterEntry.sender is renamed to waiter: half its variants carry no
channel now.
Restoring the helper fixed the voip build but broke the default one: the VoIP
facade is the sole caller and lives behind voip-runtime, so without that feature
the method is dead code and clippy's -D warnings rejects it.
@jlucaso1
jlucaso1 force-pushed the perf/pingpong-round2 branch from 6d32f66 to 1828234 Compare July 26, 2026 02:36
@jlucaso1
jlucaso1 merged commit dde51e7 into main Jul 26, 2026
17 of 19 checks passed
@jlucaso1
jlucaso1 deleted the perf/pingpong-round2 branch July 26, 2026 02:42

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/msg_secret_buffer.rs (1)

300-308: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make seal-raced wakeups self-healing

schedule_drain() drops any send error, and seal() closes wake_tx. If it races a producer that just observed sealed == false, the wakeup for that pending entry is lost and the detached worker will not write it. Use an inline flush fallback when try_send(()) fails after seal(). Also remove the “worker is gone with the buffer” language: a live seal() closes the channel while MsgSecretWriteBuffer is still alive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/msg_secret_buffer.rs` around lines 300 - 308, Update schedule_drain and
its caller schedule_or_flush to handle a failed wake-channel try_send after
seal(): fall back to an inline flush so pending entries are written even when
the worker wakeup is lost. Revise nearby comments to remove any claim that the
worker is gone with the buffer, noting that seal() closes the channel while
MsgSecretWriteBuffer remains alive.
src/send/mod.rs (1)

1694-1730: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Register group stanza phash waits in send_message_impl.

send_group_branch builds group stanzas with a phash attribute, but it returns dm_phash: None, so this branch never reaches register_phash_waiter. Thread that group phash back through send_message_impl or the group branch output so group sends can self-correct stale participant/device lists; with the current path, group dm_phash stays unreachable and we lose the same ack-based stale-list detection that status sends get.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/send/mod.rs` around lines 1694 - 1730, Update send_group_branch and its
SendBranchOutput so the phash generated for group stanzas is returned instead of
dm_phash: None. Preserve that value through the group arm in send_message_impl,
allowing the existing register_phash_waiter path to register group sends and
perform ack-based stale participant/device-list detection.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/msg_secret_buffer.rs`:
- Around line 300-308: Update schedule_drain and its caller schedule_or_flush to
handle a failed wake-channel try_send after seal(): fall back to an inline flush
so pending entries are written even when the worker wakeup is lost. Revise
nearby comments to remove any claim that the worker is gone with the buffer,
noting that seal() closes the channel while MsgSecretWriteBuffer remains alive.

In `@src/send/mod.rs`:
- Around line 1694-1730: Update send_group_branch and its SendBranchOutput so
the phash generated for group stanzas is returned instead of dm_phash: None.
Preserve that value through the group arm in send_message_impl, allowing the
existing register_phash_waiter path to register group sends and perform
ack-based stale participant/device-list detection.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ce5ca7c9-3801-446f-9690-896be26b1414

📥 Commits

Reviewing files that changed from the base of the PR and between 66bf9ed and 1828234.

📒 Files selected for processing (13)
  • .gitignore
  • src/client.rs
  • src/client/accessors.rs
  • src/client/lifecycle.rs
  • src/client/messaging.rs
  • src/client/node_io.rs
  • src/client/tests.rs
  • src/keepalive.rs
  • src/msg_secret_buffer.rs
  • src/request.rs
  • src/send/mod.rs
  • src/test_utils.rs
  • wacore/src/request.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant