Skip to content

perf(signal-cache): share cached sessions via Arc, peek without deep clone - #809

Merged
jlucaso1 merged 1 commit into
mainfrom
perf/arc-session-records
Jun 9, 2026
Merged

perf(signal-cache): share cached sessions via Arc, peek without deep clone#809
jlucaso1 merged 1 commit into
mainfrom
perf/arc-session-records

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Problem

The session object cache stores SessionEntry::Present(Box<SessionRecord>), so peek_session — the non-destructive read used by retry-receipt handling and LID-migration checks — deep-clones the record on every call (signal_cache.rs): a SessionRecord carries archived session states plus skipped message keys, typically 1-2 KB. The backend-miss path cloned a second time to populate the cache. The sender-key cache already solved this exact problem with Arc ("bumps a refcount instead of deep-cloning the record's VecDeque<SenderKeyState>"); sessions just never got the same treatment.

Change

SessionEntry::Present(Arc<SessionRecord>), aligning sessions with the sender-key cache pattern:

  • peek: returns Option<Arc<SessionRecord>> — cache hit is a refcount bump; the backend-miss path wraps once and shares the same Arc between the cache slot and the return value (the old code cloned here too).
  • checkout (get_session): Arc::try_unwrap(record).unwrap_or_else(|arc| (*arc).clone()) — the entry is unique in steady state, so this is a move exactly like the old Box deref; it clones only if a peek's short-lived Arc is still alive at checkout time, which is the rare race the old code paid for on every peek instead.
  • flush/eviction/has_session: unchanged (serialize reads through the Arc).

Verification

Callers (retry.rs ×3, message tests ×2) only call read methods through the record, so the Arc return is source-compatible at every site — no caller changes beyond what deref provides. cargo test -p wacore --lib: 947 passed; cargo test -p whatsapp-rust --lib: 744 passed; cargo clippy --all-targets -- -D warnings clean; fmt clean.

Breaking

peek_session return type changes Option<SessionRecord>Option<Arc<SessionRecord>> (pre-1.0; read-only consumers are unaffected by deref).


Generated by Claude Code

…clone

SessionEntry stored Box<SessionRecord>, so peek_session deep-cloned the
record (KBs with archived states + skipped message keys) on every call —
it runs on the retry-receipt and LID-migration check paths. Entries are
now Arc<SessionRecord>, matching the sender-key cache's existing pattern:

- peek_session returns Option<Arc<SessionRecord>> — a refcount bump on
  cache hit, and the backend-miss path no longer clones to populate the
  cache either.
- get_session (checkout) unwraps via Arc::try_unwrap: unique in steady
  state (a move, exactly like the old Box), cloning only if a peek's
  short-lived Arc is still alive.
- flush serializes through the Arc unchanged.

Callers (retry.rs, message tests) only read through the record, so the
signature change is source-compatible at every site.
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 655a6613-61e6-4ce9-b7b1-3390e9802dd6

📥 Commits

Reviewing files that changed from the base of the PR and between 89f5487 and eab707f.

📒 Files selected for processing (1)
  • wacore/src/store/signal_cache.rs

📝 Walkthrough

Summary by CodeRabbit

  • Refactor
    • Session caching now uses an optimized memory management approach, improving retrieval performance and reducing memory overhead for cached session data.

Walkthrough

SignalStoreCache's session cache storage type migrates from Box<SessionRecord> to Arc<SessionRecord>. Cache insertion wraps records in Arc::new(). Session retrieval in get_session attempts zero-copy unwrapping via Arc::try_unwrap, cloning only when other references exist. The peek_session public API signature changes to return Option<Arc<SessionRecord>> instead of cloning records on warm hits or backend misses.

Changes

Session cache Arc refactor

Layer / File(s) Summary
Cache storage type and insertion
wacore/src/store/signal_cache.rs
SessionEntry::Present is redefined to store Arc<SessionRecord> with refcounting semantics documented; SessionStoreState::put creates cache entries by wrapping sessions in Arc::new().
Session retrieval and peek APIs
wacore/src/store/signal_cache.rs
get_session warm hits use Arc::try_unwrap for efficient ownership transfer, falling back to clone if other Arc references exist. peek_session return type becomes Option<Arc<SessionRecord>>; warm hits and backend misses now work with shared Arc references instead of deep-cloning records.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • oxidezap/whatsapp-rust#413: Both PRs modify wacore/src/store/signal_cache.rs's session caching to store/load SessionRecord objects and adjust cache-return types like peek_session/get_session.
  • oxidezap/whatsapp-rust#721: Both PRs modify wacore/src/store/signal_cache.rs read/populate paths for peek_session/get_session, overlapping on the new Arc<SessionRecord> caching and cloned return semantics.
  • oxidezap/whatsapp-rust#478: Both PRs modify wacore/src/store/signal_cache.rs session caching logic; this PR changes the session cache value type to Arc<SessionRecord>, while the related PR adds bounded eviction that runs after session cache updates.

Suggested labels

api-design, breaking-change

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main performance optimization: replacing Box with Arc for session caching and eliminating deep clones in peek operations.
Description check ✅ Passed The description clearly explains the problem (unnecessary deep clones), the solution (Arc-based sharing), and provides verification details including test results.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/arc-session-records

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 and usage tips.

@jlucaso1
jlucaso1 merged commit 62ca9b9 into main Jun 9, 2026
11 checks passed
@jlucaso1
jlucaso1 deleted the perf/arc-session-records branch June 9, 2026 20:22
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

Benchmark Results

67 unchanged benchmark(s)
Benchmark Current Baseline Change
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message() 2,927 2,927 +0.0%
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message() 8,448 8,448 +0.0%
reporting_token_benchmark::key_derivation_group::bench_key_derivation 31,317 31,317 +0.0%
reporting_token_benchmark::token_calculation_group::bench_token_calculation 13,827 13,827 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple() 49,487 49,487 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended() 55,003 55,003 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message() 1,679 1,679 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message() 4,393 4,393 +0.0%
send_receive_benchmark::dm_send::bench_dm_send text:setup_dm_send() 113,198 112,952 +0.2%
send_receive_benchmark::dm_recv::bench_dm_recv text:setup_dm_recv() 1,656,619 1,656,732 -0.0%
send_receive_benchmark::group_send::bench_group_send group_10:setup_group_send_10() 644,032 643,876 +0.0%
send_receive_benchmark::group_send::bench_group_send group_50:setup_group_send_50() 870,064 870,044 +0.0%
send_receive_benchmark::group_send::bench_group_send group_256:setup_group_send_256() 2,075,564 2,075,883 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_10:setup_group_skdm_10() 742,279 742,277 +0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_50:setup_group_skdm_50() 1,320,679 1,320,628 +0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_256:setup_group_skdm_256() 4,386,648 4,363,858 +0.5%
send_receive_benchmark::group_recv::bench_group_recv text:setup_group_recv() 513,649 515,856 -0.4%
binary_benchmark::marshal_group::bench_marshal_allocating 45,381 45,381 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_allocating 45,431 45,431 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_allocating 66,334 66,334 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer 43,492 43,492 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer 45,487 45,487 +0.0%
binary_benchmark::marshal_group::bench_marshal_long_string 4,945 4,945 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_long_string 4,976 4,976 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_long_string 6,747 6,747 +0.0%
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating 528,544 528,544 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating 528,165 528,165 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating 529,411 529,411 +0.0%
binary_benchmark::marshal_group::bench_marshal_many_children_allocating 5,417,732 5,417,732 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating 5,362,047 5,362,047 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating 13,276,365 13,276,365 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled() 1,850 1,850 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled() 29,217 29,217 +0.0%
binary_benchmark::unpack_group::bench_unpack_uncompressed 618 618 +0.0%
binary_benchmark::unpack_group::bench_unpack_compressed 672,890 672,890 +0.0%
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled() 3,736 3,736 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled() 3,840 3,840 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled() 48,274 48,274 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled() 3,866 3,866 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled() 48,335 48,335 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled() 5,206 5,206 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled() 66,659 66,659 +0.0%
binary_benchmark::child_iteration_group::bench_get_children_by_tag 310,312 310,312 +0.0%
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled() 8,291 8,291 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u32 254 254 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u32 91 91 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u64 292 292 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u64 137 137 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_i64 317 317 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_i64 145 145 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_loop_100_u64 27,425 27,425 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_loop_100_u64 10,725 10,725 +0.0%
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users() 4,139,473 4,139,538 -0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session() 100,133 100,131 +0.0%
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message() 4,264,189 4,264,189 +0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session() 100,399 100,399 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender() 210,262 210,262 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution() 496,921 496,921 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message() 507,293 504,005 +0.7%
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data() 11,976,176 11,979,056 -0.0%
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message() 2,466,138 2,466,138 +0.0%
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message() 4,892,192 4,921,132 -0.6%
libsignal_benchmark::signature_group::bench_key_generation keygen 2,043,351 2,043,351 +0.0%
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions() 37,404 37,414 -0.0%
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages() 3,617,967 3,617,967 +0.0%
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session() 230,648 230,658 -0.0%
libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction() 9,980,959 9,980,959 +0.0%
No significant changes detected.

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.

2 participants