Skip to main content

Performance

Device snapshot cached as Arc<Device> (#808) PersistenceManager::get_device_snapshot() previously deep-cloned the entire Device struct on every call — two Jids, push_name, props_hash, edge_routing_info, nct_salt, roughly 5–8 heap allocations. With ~72 call sites including once per inbound message, this added up. PersistenceManager now keeps a device_snapshot: std::sync::RwLock<Arc<Device>> that is rebuilt under the existing device write guard inside modify_device — the single mutation funnel. Mutations are rare (pairing, push-name sync, prekey counter); reads now pay only a std::sync::RwLock read plus a refcount bump, with no clone and no contention against writers. Breaking changes:
  • get_device_snapshot(): async fn → Devicefn → Arc<Device>.
  • get_pn() / get_lid() / get_push_name() / require_pn(): no longer async.
  • generate_message_id(): no longer async.
Migration is mechanical: drop .await at every call site. The return of get_device_snapshot() is now Arc<Device> — borrow fields directly from the held Arc (snapshot.pn.as_ref()), or clone individual fields where ownership is needed. Do not hold the snapshot longer than the current scope; doing so pins an old Arc allocation rather than paying for a fresh borrow.
// Before
let device = client.persistence_manager().get_device_snapshot().await;
let pn = device.pn.clone();

let push_name = client.get_push_name().await;
let id = client.generate_message_id().await;

// After
let snapshot = client.persistence_manager().get_device_snapshot();   // fn, no .await
let pn = snapshot.pn.clone();

let push_name = client.get_push_name();         // fn, no .await
let id = client.generate_message_id();          // fn, no .await
get_device_arc() is kept but its role is narrowed to store-adapter internals that need &mut Device trait access. All plain read paths should use persistence_manager().get_device_snapshot() instead. No other breaking changes. modify_device, process_command, and DeviceCommand are unchanged.