perf(signal): cache Arc<SenderKeyRecord> to avoid deep-cloning the message-key backlog - #713
Conversation
…ssage-key backlog
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR refactors the sender-key cache to store records as reference-counted pointers ( ChangesSender-Key Arc Ownership Refactor
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Look, here's what matters. You're moving sender-key records behind Arc pointers in the cache layer. That's smart—no deep clones on warm hits, better memory efficiency. But I need to see that every call site handles this correctly. The adapter is doing the right thing: it's unwrapping the Arc into an owned copy for mutation in group_decrypt. That works. The send path is borrowing from the owned record—correct approach, no unnecessary clones. But this is a breaking API change. The risk here is subtle: Arc gives you safe sharing, but shared state can hide mutations. Make sure the owner of that Arc—the adapter—is the only place mutating the record. If other code paths grab the Arc and try to mutate through it, you've got a problem. The test doesn't catch that. Check the full call graph. Make sure no one else is holding an Arc and mutating in parallel. If this is the sender-key decrypt path and it's single-threaded per record, you're fine. If there's concurrent access, you need Arc<Mutex<>> or Arc<RwLock<>>, not just Arc<>. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Benchmark Results67 unchanged benchmark(s)
|
Problem
The sender-key cache stored
SenderKeyRecordby value, soget_sender_keyreturnedcached.clone()on every hit. ASenderKeyRecordis aVecDeque<SenderKeyState>where each state holds a message-key backlog that grows up toMAX_MESSAGE_KEYS(2000), so the clone reallocates the deque plus a vector of up to ~2000 entries per call.get_sender_keyis hit on several paths that only need to read:send.rs) cloned the entire record just to read the current chain iteration,key_existschecks on the send path,and on the per-decrypt load via the
load_sender_keytrait.This mirrors an anti-pattern already fixed elsewhere for the device-registry and group caches: cache under
Arc.Change
Cache
Arc<SenderKeyRecord>.get_sender_keynow returnsOption<Arc<SenderKeyRecord>>, so a warm hit is a refcount bump instead of a deep clone.Arc. The rotation check now borrows the current state (sender_key_state()) to read the iteration instead ofsender_key_state_mut().cloned(), so it no longer clones the record.load_sender_keytrait impl still returns an owned record (viaArc::unwrap_or_clone), becausegroup_decryptmutates the loaded record (catch-up + ratchet advance) and stores it back. The cache keeps itsArc, so this clone is unchanged from the prior behavior. Net effect: the decrypt path is neutral, the read-only paths stop cloning.flushserializes through theArc(auto-deref), unchanged.Safety
An earlier draft considered a checkout/take pattern (move the record out of the cache for the mutating decrypt path). That was rejected:
group_decrypthas several earlyreturn Errpaths between the load and the store, so taking the record out and erroring would drop unflushed message keys from the cache, which can make out-of-order messages undecryptable. CachingArcavoids that entirely: the cache always retains the record, and the mutating path clones exactly as before.Tests
warm_sender_key_hit_shares_arc_not_deep_clone(new): twoget_sender_keycalls on the same key return the same allocation (Arc::ptr_eq).cargo clippy --all-targets -- -D warningsclean.The deep-clone cost the read-only paths now avoid scales with the message-key backlog (about 1.3M instructions at the 2000-key cap); the
Arcclone is an O(1) refcount bump.