Skip to content

perf!: Arc<MessageInfo> across message, retry, and PDO paths - #520

Merged
jlucaso1 merged 2 commits into
mainfrom
perf/encrypt-loop-and-messageinfo-arc
Apr 12, 2026
Merged

perf!: Arc<MessageInfo> across message, retry, and PDO paths#520
jlucaso1 merged 2 commits into
mainfrom
perf/encrypt-loop-and-messageinfo-arc

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Apr 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Eliminates all MessageInfo deep clones in the message processing pipeline by threading Arc<MessageInfo> through every layer.

Event types:

  • Event::Message and UndecryptableMessage now hold Arc<MessageInfo>
  • Event::as_message() still returns (&wa::Message, &MessageInfo) via Arc deref

Message dispatch:

  • dispatch_parsed_message: uses Arc::make_mut for ephemeral mutation — zero deep clones when no mutation needed (common path), in-place mutation when Arc is uniquely owned
  • spawn_retry_receipt: takes &Arc<MessageInfo>, Arc::clone instead of deep clone

PDO path:

  • PendingPdoRequest.message_info: Arc<MessageInfo> instead of owned
  • spawn_pdo_request / spawn_pdo_request_with_options / send_pdo_placeholder_resend_request: take &Arc<MessageInfo>, Arc::clone instead of deep clone
  • handle_pdo_response: Arc::make_mut for mutations, passes Arc directly to event dispatch

Internal chain:

  • process_session_enc_batch, process_group_enc_batch, handle_decrypted_plaintext, try_pn_to_lid_migration_decrypt, handle_decrypt_failure, handle_newsletter_message all take &Arc<MessageInfo> to avoid re-cloning at each layer

Serde:

  • Enable serde rc feature for Arc<T> serialization

Breaking changes

  • Event::Message(Box<wa::Message>, MessageInfo)Event::Message(Box<wa::Message>, Arc<MessageInfo>)
  • UndecryptableMessage.info: MessageInfoArc<MessageInfo>
  • PendingPdoRequest.message_info: MessageInfoArc<MessageInfo>

Before / After

Path Before After
Happy path (dispatch) 2× deep clone 0 (Arc::clone + make_mut)
Retry receipt 1× deep clone 0 (Arc::clone)
PDO spawn 1× deep clone 0 (Arc::clone)
PDO cache store 1× deep clone 0 (Arc::clone)
Undecryptable event 1× deep clone ((*arc).clone()) 0 (Arc::clone)

Test plan

  • cargo fmt --all
  • cargo clippy --all --tests
  • cargo test --all --exclude e2e-tests (724 tests pass)

@coderabbitai

coderabbitai Bot commented Apr 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Convert message metadata to shared ownership by using Arc<MessageInfo> across event types, PDO flows, and message-handling APIs; update call sites and tests. Also enable serde's rc feature in the workspace Cargo.toml to support Arc (de)serialization.

Changes

Cohort / File(s) Summary
Dependency feature flag
Cargo.toml
Add rc to serde workspace dependency features (["derive"]["derive","rc"]).
Event type definitions
wacore/src/types/events.rs
Change Event::Message to hold Arc<MessageInfo> and update Event::as_message(); UndecryptableMessage.info now Arc<MessageInfo>.
Message handling functions & tests
src/message.rs, tests/*
APIs updated to accept/propagate Arc<MessageInfo> (dispatch_parsed_message, dispatch_undecryptable_event, handle_decrypt_failure, spawn_retry_receipt, process_session_enc_batch, process_group_enc_batch, helpers); call sites adjusted and unit tests updated to construct/clone Arc<MessageInfo>.
PDO request/response flow
src/pdo.rs
PendingPdoRequest.message_infoArc<MessageInfo>; PDO request spawn/send signatures changed to accept &Arc<MessageInfo>; reconstructed MessageInfo wrapped in Arc::new(...) and conditionally mutated via Arc::make_mut before dispatching.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble code in cozy arcs,
Shared MessageInfo skips lonely marks,
Counted tails hop, clone, and stay,
Arc::make_mut tweaks the way,
A rabbit cheers each threaded spark.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: introducing Arc across message dispatch, retry, and PDO code paths to eliminate deep clones and improve performance.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/encrypt-loop-and-messageinfo-arc

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

Copy link
Copy Markdown
Collaborator Author

@claude review

@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 the current code and only fix it if needed.

Inline comments:
In `@src/message.rs`:
- Around line 123-128: The retry path still clones MessageInfo deeply because
spawn_retry_receipt accepts an owned MessageInfo; change spawn_retry_receipt's
signature to take Arc<MessageInfo> (or &Arc<MessageInfo>) and update its
implementation to use the Arc without cloning the inner MessageInfo, then update
this caller in the retry helper to pass Arc::clone(info) (or the reference)
instead of a deep clone; adjust any other callers of spawn_retry_receipt to
match the new signature and remove the unnecessary MessageInfo::clone
allocations.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 75124258-97e7-4d06-a48f-fb279c521760

📥 Commits

Reviewing files that changed from the base of the PR and between 291ab58 and f1d00e2.

📒 Files selected for processing (4)
  • Cargo.toml
  • src/message.rs
  • src/pdo.rs
  • wacore/src/types/events.rs

Comment thread src/message.rs
@claude

claude Bot commented Apr 12, 2026

Copy link
Copy Markdown

Claude finished @jlucaso1's task in 3m 12s —— View job


Code Review: Arc<MessageInfo> in Event dispatch

  • Gather context and read changed files
  • Analyze the diff against origin/main
  • Review for correctness, performance, and style
  • Post review feedback

Overall this is a clean, well-motivated optimization. The Arc wrapping is introduced at the right boundary (after mutation, before fan-out), and the as_message() accessor hides the Arc from downstream consumers via deref — nice API preservation.

Looks good

  • Correct Arc introduction points: handle_incoming_message wraps in Arc right after parse_message_info, dispatch_parsed_message wraps after the ephemeral-expiration mutation, pdo.rs wraps at dispatch. All correct — the Arc is created once the MessageInfo is finalized.
  • as_message() transparency: The &**info deref in events.rs:438 correctly exposes &MessageInfo to consumers, so the breaking change is limited to code that pattern-matches Event::Message directly (and even there, auto-deref handles most usage like info.id, info.source.sender, etc.).
  • serde rc feature: Necessary since Event derives Serialize and now holds Arc<MessageInfo>. Correct addition.
  • Test updates: Clean — just wrapping MessageInfo construction in Arc::new().

Suggestion: carry Arc through spawn_retry_receipt and spawn_pdo_request_with_options

I agree with CodeRabbit's finding here. There are two remaining deep-clone sites that this PR could eliminate:

1. spawn_retry_receipt (src/message.rs:194-196):

fn spawn_retry_receipt(self: &Arc<Self>, info: &MessageInfo, reason: RetryReason) {
    let info = info.clone(); // ← deep clone

Both callers (handle_decrypt_failure at line 128 and the group decrypt path at line 1097) already have &Arc<MessageInfo>. Changing the signature to info: &Arc<MessageInfo> and using let info = Arc::clone(info) would eliminate this deep clone on every retry.

2. spawn_pdo_request_with_options (src/pdo.rs:457-471):

pub(crate) fn spawn_pdo_request_with_options(self: &Arc<Self>, info: &MessageInfo, immediate: bool) {
    // ...
    let info_clone = info.clone(); // ← deep clone

Same pattern — callers already hold Arc<MessageInfo> and pass it through auto-deref. Taking &Arc<MessageInfo> here would eliminate another deep clone.

These are the retry/error paths so they're not as hot as the happy path, but since the Arc is already available at every call site it's essentially free to propagate. This would make the clone reduction end-to-end across all message handling paths. That said, this can also be a follow-up — the PR as-is is correct and delivers the main win on the hot path.

Minor note on dispatch_parsed_message

At src/message.rs:38-41, the function takes &MessageInfo and immediately deep-clones to allow ephemeral-expiration mutation:

fn dispatch_parsed_message(self: &Arc<Self>, msg: wa::Message, info: &MessageInfo) {
    let mut info = info.clone(); // deep clone for potential mutation
    if info.ephemeral_expiration.is_none() {
        info.ephemeral_expiration = msg.get_base_message().get_ephemeral_expiration();
    }
    let info = Arc::new(info);

This is the happy path (successfully decrypted messages), so the clone here is more impactful than the retry paths above. A possible future optimization: check ephemeral first, and only clone+mutate when needed (i.e., take Arc<MessageInfo> and use Arc::make_mut or conditionally unwrap). Not blocking — just noting for the "future optimization target" tracking mentioned in the PR description.

Verdict

LGTM — the core optimization is correct and well-scoped. The Arc introduction points are sound, the API surface is preserved for consumers, and the breaking changes are clearly documented. The remaining deep-clone sites in spawn_retry_receipt and spawn_pdo_request_with_options are a natural follow-up.


@github-actions

github-actions Bot commented Apr 12, 2026

Copy link
Copy Markdown

Benchmark Results

59 unchanged benchmark(s)
Benchmark Current Baseline Change
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message() 3,879 3,879 +0.0%
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message() 11,855 11,851 +0.0%
reporting_token_benchmark::key_derivation_group::bench_key_derivation 43,398 43,398 +0.0%
reporting_token_benchmark::token_calculation_group::bench_token_calculation 19,365 19,365 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple() 68,814 68,814 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended() 76,785 76,781 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message() 2,214 2,214 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message() 5,943 5,939 +0.1%
send_receive_benchmark::dm_send::bench_dm_send text:setup_dm_send() 177,552 177,971 -0.2%
send_receive_benchmark::dm_recv::bench_dm_recv text:setup_dm_recv() 191,878 191,870 +0.0%
send_receive_benchmark::group_send::bench_group_send group_10:setup_group_send_10() 888,294 888,988 -0.1%
send_receive_benchmark::group_send::bench_group_send group_50:setup_group_send_50() 980,399 979,477 +0.1%
send_receive_benchmark::group_send::bench_group_send group_256:setup_group_send_256() 1,464,712 1,464,707 +0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_10:setup_group_skdm_10() 2,727,478 2,727,788 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_50:setup_group_skdm_50() 10,057,770 10,025,768 +0.3%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_256:setup_group_skdm_256() 47,714,407 47,880,208 -0.3%
send_receive_benchmark::group_recv::bench_group_recv text:setup_group_recv() 12,773,156 12,685,187 +0.7%
binary_benchmark::marshal_group::bench_marshal_allocating 95,585 95,585 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_allocating 95,618 95,618 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_allocating 113,974 113,974 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer 102,895 102,895 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer 95,685 95,685 +0.0%
binary_benchmark::marshal_group::bench_marshal_long_string 15,762 15,762 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_long_string 15,806 15,806 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_long_string 17,592 17,592 +0.0%
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating 533,122 533,122 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating 532,688 532,688 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating 534,046 534,046 +0.0%
binary_benchmark::marshal_group::bench_marshal_many_children_allocating 13,413,012 13,413,012 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating 13,357,245 13,357,245 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating 26,652,528 26,652,528 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled() 2,498 2,498 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled() 38,500 38,500 +0.0%
binary_benchmark::unpack_group::bench_unpack_uncompressed 773 773 +0.0%
binary_benchmark::unpack_group::bench_unpack_compressed 556,090 556,090 +0.0%
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled() 5,039 5,039 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled() 7,484 7,484 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled() 90,824 90,824 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled() 7,511 7,511 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled() 90,860 90,860 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled() 8,838 8,838 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled() 104,670 104,670 +0.0%
binary_benchmark::child_iteration_group::bench_get_children_by_tag 475,970 475,970 +0.0%
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled() 13,493 13,493 +0.0%
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users() 17,064,561 17,331,403 -1.5%
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session() 160,994 160,994 +0.0%
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message() 5,511,244 5,511,244 +0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session() 161,731 161,731 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender() 298,353 298,353 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution() 712,883 712,883 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message() 12,464,011 12,696,591 -1.8%
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data() 27,422,783 27,238,692 +0.7%
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message() 3,467,011 3,467,011 +0.0%
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message() 125,410,053 124,961,413 +0.4%
libsignal_benchmark::signature_group::bench_key_generation keygen 2,830,452 2,830,493 -0.0%
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions() 46,003 46,003 +0.0%
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages() 5,105,272 5,105,272 +0.0%
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session() 297,627 297,627 +0.0%
libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction() 14,247,117 14,247,117 +0.0%
No significant changes detected.

- Event::Message and UndecryptableMessage now hold Arc<MessageInfo>
- dispatch_parsed_message: conditionally clones only when ephemeral
  mutation is needed, otherwise Arc::clone (zero deep clones on the
  common path)
- spawn_retry_receipt: takes &Arc<MessageInfo>, Arc::clone instead of
  deep clone for the spawned async task
- Thread &Arc<MessageInfo> through the internal message processing
  chain (process_session_enc_batch, process_group_enc_batch,
  handle_decrypted_plaintext, try_pn_to_lid_migration_decrypt,
  handle_decrypt_failure) to avoid re-cloning at each layer
- Enable serde "rc" feature for Arc<T> serialization
@jlucaso1
jlucaso1 force-pushed the perf/encrypt-loop-and-messageinfo-arc branch from f1d00e2 to f97e2bc Compare April 12, 2026 20:13

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/message.rs`:
- Around line 41-49: The code currently deep-clones the MessageInfo via
(**info).clone() when setting ephemeral_expiration; instead perform
copy-on-write with Arc::make_mut to avoid the clone when the Arc is uniquely
owned: clone the Arc with Arc::clone(info) into a mutable binding (e.g., let mut
info = Arc::clone(info)), then inside the branch call Arc::make_mut(&mut info)
to get &mut MessageInfo and set ephemeral_expiration to
msg.get_base_message().get_ephemeral_expiration(); otherwise leave the cloned
Arc untouched and continue using info. This replaces the (**info).clone() path
while preserving semantics.

In `@src/pdo.rs`:
- Around line 374-376: PendingPdoRequest.message_info and
Client::send_pdo_placeholder_resend_request currently use owned MessageInfo so
you allocate a new Arc in the fallback dispatch; change
PendingPdoRequest.message_info to Arc<MessageInfo> and update all construction
sites to wrap or pass an Arc, then change
Client::send_pdo_placeholder_resend_request signature to take Arc<MessageInfo>
(or &Arc<MessageInfo>) and propagate that Arc through the code so you can reuse
Arc::clone rather than calling Arc::new in the dispatch (replace
Arc::new(message_info) with the existing Arc instance or Arc::clone). Ensure all
call sites and tests that previously passed MessageInfo are updated to either
pass an Arc or convert once at creation points.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9f0c99c5-f2b9-4c77-8ce6-36640eb621d3

📥 Commits

Reviewing files that changed from the base of the PR and between f1d00e2 and f97e2bc.

📒 Files selected for processing (4)
  • Cargo.toml
  • src/message.rs
  • src/pdo.rs
  • wacore/src/types/events.rs

Comment thread src/message.rs Outdated
Comment thread src/pdo.rs Outdated
- dispatch_parsed_message: use Arc::make_mut for ephemeral mutation
  instead of deep clone + Arc::new
- PendingPdoRequest.message_info: Arc<MessageInfo> instead of owned
- spawn_pdo_request / spawn_pdo_request_with_options: take
  &Arc<MessageInfo>, Arc::clone instead of deep clone
- send_pdo_placeholder_resend_request: take &Arc<MessageInfo>
- handle_pdo_response: Arc::make_mut for mutations, pass Arc directly
  to Event dispatch without re-wrapping
@jlucaso1 jlucaso1 changed the title perf!: Arc<MessageInfo> in Event dispatch to eliminate deep clones perf!: Arc<MessageInfo> across message, retry, and PDO paths Apr 12, 2026
@jlucaso1
jlucaso1 merged commit eef4c27 into main Apr 12, 2026
7 of 8 checks passed
@jlucaso1
jlucaso1 deleted the perf/encrypt-loop-and-messageinfo-arc branch April 12, 2026 20:34
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