From a836b726b49edbca5d180439063763ec48f9953a Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 8 Dec 2025 12:43:46 +0200 Subject: [PATCH 1/3] change(notification_client): use cross process locking event and media stores instead of in-memory ones This will allow the NSE process to populate the event cache store with even data after retrieving it from sync, as opposed to dropping after rendering the notification it as it did previously. --- crates/matrix-sdk-base/src/client.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index aace8d0f2f2..ed3b3ec4df8 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -221,14 +221,16 @@ impl BaseClient { cross_process_store_locks_holder_name: &str, handle_verification_events: bool, ) -> Result { - let config = StoreConfig::new(cross_process_store_locks_holder_name.to_owned()) - .state_store(MemoryStore::new()); - let config = config.crypto_store(self.crypto_store.clone()); + let in_memory_store_config = + StoreConfig::new(cross_process_store_locks_holder_name.to_owned()) + .state_store(MemoryStore::new()) + .crypto_store(self.crypto_store.clone()); let copy = Self { - state_store: BaseStateStore::new(config.state_store), - event_cache_store: config.event_cache_store, - media_store: config.media_store, + state_store: BaseStateStore::new(in_memory_store_config.state_store), + // The event and media stores both have cross process locking support. + event_cache_store: self.event_cache_store.clone(), + media_store: self.media_store.clone(), // We copy the crypto store as well as the `OlmMachine` for two reasons: // 1. The `self.crypto_store` is the same as the one used inside the `OlmMachine`. // 2. We need to ensure that the parent and child use the same data and caches inside From 7a4263d5052330edf0ec6549a5e1fef062b25fa7 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 19 Dec 2025 14:48:46 +0100 Subject: [PATCH 2/3] fixup --- crates/matrix-sdk-base/src/client.rs | 60 +++++++++++++------ crates/matrix-sdk-base/src/error.rs | 4 ++ .../src/event_cache/store/mod.rs | 16 +++++ crates/matrix-sdk-base/src/media/store/mod.rs | 16 +++++ 4 files changed, 79 insertions(+), 17 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index ed3b3ec4df8..d1e90f9157b 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -64,8 +64,8 @@ use crate::{ Room, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomMembersUpdate, RoomState, }, store::{ - BaseStateStore, DynStateStore, MemoryStore, Result as StoreResult, RoomLoadSettings, - StateChanges, StateStoreDataKey, StateStoreDataValue, StateStoreExt, StoreConfig, + BaseStateStore, DynStateStore, Result as StoreResult, RoomLoadSettings, StateChanges, + StateStoreDataKey, StateStoreDataValue, StateStoreExt, StoreConfig, ambiguity_map::AmbiguityCache, }, sync::{RoomUpdates, SyncResponse}, @@ -221,24 +221,50 @@ impl BaseClient { cross_process_store_locks_holder_name: &str, handle_verification_events: bool, ) -> Result { - let in_memory_store_config = + let StoreConfig { state_store, event_cache_store, media_store, .. } = StoreConfig::new(cross_process_store_locks_holder_name.to_owned()) - .state_store(MemoryStore::new()) - .crypto_store(self.crypto_store.clone()); + .event_cache_store({ + if cross_process_store_locks_holder_name == self.event_cache_store.lock_holder() + { + return Err(Error::DuplicatedCrossProcessLockHolder( + cross_process_store_locks_holder_name.to_owned(), + )); + } + + // SAFETY: the store will be used behind another lock holder, so it's safe to + // use a clone of it. + unsafe { self.event_cache_store.clone_store() } + }) + .media_store({ + if cross_process_store_locks_holder_name == self.media_store.lock_holder() { + return Err(Error::DuplicatedCrossProcessLockHolder( + cross_process_store_locks_holder_name.to_owned(), + )); + } + + // SAFETY: the store will be used behind another lock holder, so it's safe to + // use a clone of it. + unsafe { self.media_store.clone_store() } + }); + + // Clone the Crypto store. + // We copy the crypto store as well as the `OlmMachine` for two reasons: + // 1. The `self.crypto_store` is the same as the one used inside the + // `OlmMachine`. + // 2. We need to ensure that the parent and child use the same data and caches + // inside the `OlmMachine` so the various ratchets and places where new + // randomness gets introduced don't diverge, i.e. one-time keys that get + // generated by the Olm Account or Olm sessions when they encrypt or decrypt + // messages. + let crypto_store = self.crypto_store.clone(); + let olm_machine = self.olm_machine.clone(); let copy = Self { - state_store: BaseStateStore::new(in_memory_store_config.state_store), - // The event and media stores both have cross process locking support. - event_cache_store: self.event_cache_store.clone(), - media_store: self.media_store.clone(), - // We copy the crypto store as well as the `OlmMachine` for two reasons: - // 1. The `self.crypto_store` is the same as the one used inside the `OlmMachine`. - // 2. We need to ensure that the parent and child use the same data and caches inside - // the `OlmMachine` so the various ratchets and places where new randomness gets - // introduced don't diverge, i.e. one-time keys that get generated by the Olm Account - // or Olm sessions when they encrypt or decrypt messages. - crypto_store: self.crypto_store.clone(), - olm_machine: self.olm_machine.clone(), + state_store: BaseStateStore::new(state_store), + event_cache_store, + media_store, + crypto_store, + olm_machine, ignore_user_list_changes: Default::default(), room_info_notable_update_sender: self.room_info_notable_update_sender.clone(), room_key_recipient_strategy: self.room_key_recipient_strategy.clone(), diff --git a/crates/matrix-sdk-base/src/error.rs b/crates/matrix-sdk-base/src/error.rs index 86e0586e498..8151d022c61 100644 --- a/crates/matrix-sdk-base/src/error.rs +++ b/crates/matrix-sdk-base/src/error.rs @@ -58,6 +58,10 @@ pub enum Error { #[error(transparent)] CryptoStore(#[from] CryptoStoreError), + /// The given cross-process lock holder name is already used by a store. + #[error("The given cross-process lock holder name is already used by a store: `{0}`")] + DuplicatedCrossProcessLockHolder(String), + /// An error occurred during a E2EE operation. #[cfg(feature = "e2e-encryption")] #[error(transparent)] diff --git a/crates/matrix-sdk-base/src/event_cache/store/mod.rs b/crates/matrix-sdk-base/src/event_cache/store/mod.rs index d6d9e932fcd..03c57a7d76d 100644 --- a/crates/matrix-sdk-base/src/event_cache/store/mod.rs +++ b/crates/matrix-sdk-base/src/event_cache/store/mod.rs @@ -91,6 +91,22 @@ impl EventCacheStoreLock { Ok(lock_state) } + + /// Clone the inner store, by-passing the lock. + /// + /// # Safety + /// + /// This method is useful when you want to build a new client with another + /// lock holder name for example. But the lock is fully by-passed in this + /// method. Be extremely careful! + pub(crate) unsafe fn clone_store(&self) -> Arc { + self.store.clone() + } + + /// Get the lock holder name. + pub(crate) fn lock_holder(&self) -> &str { + self.cross_process_lock.lock_holder() + } } /// The equivalent of [`CrossProcessLockState`] but for the [`EventCacheStore`]. diff --git a/crates/matrix-sdk-base/src/media/store/mod.rs b/crates/matrix-sdk-base/src/media/store/mod.rs index b7f6b4ba02b..13007b3072c 100644 --- a/crates/matrix-sdk-base/src/media/store/mod.rs +++ b/crates/matrix-sdk-base/src/media/store/mod.rs @@ -150,6 +150,22 @@ impl MediaStoreLock { Ok(MediaStoreLockGuard { cross_process_lock_guard, store: self.store.deref() }) } + + /// Clone the inner store, by-passing the lock. + /// + /// # Safety + /// + /// This method is useful when you want to build a new client with another + /// lock holder name for example. But the lock is fully by-passed in this + /// method. Be extremely careful! + pub(crate) unsafe fn clone_store(&self) -> Arc { + self.store.clone() + } + + /// Get the lock holder name. + pub(crate) fn lock_holder(&self) -> &str { + self.cross_process_lock.lock_holder() + } } /// An RAII implementation of a “scoped lock” of an [`MediaStoreLock`]. From 116003a69c3fcf221dc5533d66a433a9e501e06a Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Fri, 19 Dec 2025 14:55:59 +0100 Subject: [PATCH 3/3] refactor(base): Rename `clone_with_in_memory_state_store`. This patch renames `clone_with_in_memory_state_store` to `derive_states_for_notification_client`. At least it clarifies what it does. --- crates/matrix-sdk-base/src/client.rs | 14 +++++++++++--- crates/matrix-sdk/src/client/mod.rs | 5 ++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index d1e90f9157b..cf334986816 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -213,10 +213,18 @@ impl BaseClient { } } - /// Clones the current base client to use the same crypto store but a - /// different, in-memory store config, and resets transient state. + /// Derives the current states but for a notification client. + /// + /// The stores will be as follow: + /// + /// - state store will be in-memory only, + /// - event cache store will be cloned (it's behind a cross-process lock), + /// - media store will be cloned (it's behind a cross-process lock), + /// - crypto store will be cloned (custom mechanism for the moment). + /// + /// The transient state will be reset. #[cfg(feature = "e2e-encryption")] - pub async fn clone_with_in_memory_state_store( + pub async fn derive_states_for_notification_client( &self, cross_process_store_locks_holder_name: &str, handle_verification_events: bool, diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index f48102a55e0..d40c615b849 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -3054,7 +3054,10 @@ impl Client { self.inner.http_client.clone(), self.inner .base_client - .clone_with_in_memory_state_store(&cross_process_store_locks_holder_name, false) + .derive_states_for_notification_client( + &cross_process_store_locks_holder_name, + false, + ) .await?, self.inner.caches.supported_versions.read().await.clone(), self.inner.caches.well_known.read().await.clone(),