Review of upstream PR #983 (oxidezap/whatsapp-rust) - #3
Open
marchugon wants to merge 6 commits into
Open
Conversation
process_session_enc_batch held the per-sender ratchet lock across the whole batch, including handle_decrypted_plaintext (protobuf decode, SKDM/app-state/ LID/PDO/history handling, and app dispatch). None of that touches the Signal session ratchet — only message_decrypt does — so the lock only needs to cover the crypto. Buffer each decrypted plaintext during the locked decrypt loop, release the guard, then run handle_decrypted_plaintext unlocked. A concurrent stanza from the same sender (e.g. the same device across two group chats, or the detached LID-migration task) can now start decrypting while this one dispatches. This matches whatsmeow, which holds the per-sender lock only around the libsignal decrypt. Correctness is preserved: - Per-chat delivery order is owned by the serial chat-lane worker, not this guard, so releasing it early cannot reorder delivery. - The buffer drains before the function returns, so a pkmsg's SKDM is still applied before PASS 2's group (skmsg) decrypt reads the sender key. - The PN->LID migration retry helper now buffers its plaintext too and returns a small result enum, keeping multi-payload dispatch order uniform and collapsing the four verbose outcome-merge call sites into a match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0116cfRpumbEv3H4qUNmtNk4
Self-review cleanup: replace the positional (&'static str, Vec<u8>, u8) buffer tuple with a named DeferredPlaintext struct so the migration helper signature and each push/drain site read for themselves, and trim the two verbose block comments to their load-bearing invariant. No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0116cfRpumbEv3H4qUNmtNk4
Self-review follow-up: the comment said the guard is held "across the entire batch", but the lock-scope change releases it before the plaintext drain. Say "across the decrypt loop" and note the early release. Comment only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0116cfRpumbEv3H4qUNmtNk4
try_pn_to_lid_migration_decrypt drops and re-acquires the per-sender session guard around the migration loop; every return after the re-acquire must leave the guard Some or the caller silently loses same-sender serialization for the next batch payload. Make that load-bearing invariant explicit with a debug_assert at the re-acquire boundary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0116cfRpumbEv3H4qUNmtNk4
…ng it The debug_assert added in the previous commit sat immediately after `*session_guard = Some(...)`, so it was tautological — it could never observe a missing hand-back on a later return path. Drop it and keep the invariant as a comment at the reacquire boundary (flagged by cubic and CodeRabbit). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0116cfRpumbEv3H4qUNmtNk4
…ypt bench handle_decrypted_plaintext never moves self — every callee takes &self / &Arc<Self> — so take self: &Arc<Self> and drop the per-payload Arc clone at the session-drain and group-path call sites. Also add an ignored steady-state session-decrypt benchmark (bench_session_decrypt_throughput) that drives the real process_session_enc_batch against established Signal sessions; deterministic CPU/allocation profiles come from running the same binary under valgrind (callgrind/dhat). Profiling shows the decrypt path is crypto-bound (~93% message_decrypt: SHA-256 + curve25519); the batch orchestration this touches is ~0.06% of instructions, so the clone removal is a hygiene win, not a throughput change. 933 lib tests pass, clippy clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0116cfRpumbEv3H4qUNmtNk4
Hi there 👋
The Merge Request introduces two key architectural improvements: lock narrowing in Worth checking
Small things (take or leave)
Review time: 3m 4s |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
FriendlyReviewer review of oxidezap/whatsapp-rust#983.
Original PR description:
Summary
Second of the recv post-decrypt dispatch series (follow-up to oxidezap#981). This one narrows the per-sender session lock so it covers only the Signal ratchet crypto, not the downstream plaintext handling and app dispatch.
process_session_enc_batchheld the per-sender ratchet guard (session_lock_for(signal_address)) across the entire batch — includinghandle_decrypted_plaintext, which does protobuf decode, SKDM / app-state-key / LID-migration / PDO / history-sync handling, and the app dispatch. None of that touches the Signal session ratchet — onlymessage_decryptdoes — so the lock only needs to cover the crypto.Change
DeferredPlaintextduring the locked decrypt loop, release the guard, then runhandle_decrypted_plaintextover the drained buffer, unlocked.MigrationDecryptResultenum, so multi-payload dispatch order stays uniform. This also collapses four verbose outcome-merge call sites into amatch.A concurrent stanza from the same sender device — e.g. the same participant across two group chats, or the detached LID-migration task, both of which contend on
session_lock_for(signal_address)— can now start decrypting while this one dispatches.Motivation
This matches whatsmeow, which holds the per-sender session lock only around the libsignal decrypt call and handles the decrypted message unlocked. Holding it across dispatch serializes same-sender cross-chat work behind unrelated downstream I/O.
Correctness
handlers/message.rs), which processes one stanza at a time through full dispatch — not by this guard. Releasing the guard earlier cannot reorder delivery.skmsgdecrypt reads that sender key.message_decrypt(the only session-ratchet mutation) still runs under the guard;handle_decrypted_plaintextonly touches sender-key / app-state stores + dispatch, and is already invoked lock-free on the group, newsletter, and PDO paths today.decrypted/duplicate/dispatched/skdm_only/plaintext_failed/had_failure) are all finalized before the function returns, so the PASS 2 gate and the ack fallbacks are unaffected.Testing
cargo test -p whatsapp-rust --lib— 933 passed, 0 failed (incl. themessage::testsdecrypt-path suite), deterministic across runs.cargo clippy -p whatsapp-rust --tests— clean.Notes
permit=N, no cross-sender lock), so this change only marginally widens the racing set, and on the session path it touches that is almost entirely SKDM-only decrypts (no durable row, acked by design) or already-serialized 1:1 chats (chat == sender). A proper fix — a live-path row-before-flush barrier, like the offline drain's commit-batcher — is a separate follow-up.Generated by Claude Code