Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f0a2585
chore(common) Rename `CrossProcessLockKind` to `CrossProcessLockState`.
Hywan Nov 11, 2025
1346561
feat(common) Add `#[must_use]` on `CrossProcessLockGuard` and `*State`.
Hywan Nov 11, 2025
b8e6bda
feat(common): Add `CrossProcessLockState::map`.
Hywan Nov 11, 2025
cfb7752
feat(base) Create the `EventCacheStoreLockState` type.
Hywan Nov 11, 2025
1ae1ea5
chore(sdk): Clean up imports.
Hywan Nov 11, 2025
4c83655
feat(common): Add `CrossProcessLockGuard::is_dirty` and `::clear_dirty`.
Hywan Nov 11, 2025
2426225
feat(base): Add `EventCacheStoreLockGuard::clear_dirty`.
Hywan Nov 11, 2025
dc87b1c
refactor(base): `EventCacheStoreLockState` owns a clone of the inner …
Hywan Nov 11, 2025
81cac32
feat(common): `CrossProcessLockGuard` can be cloned.
Hywan Nov 12, 2025
7d5e95d
feat(base): `EventCacheStoreLockGuard` can be cloned.
Hywan Nov 12, 2025
5abbf3c
fix(base): Use the `EventCacheStoreLockState`.
Hywan Nov 11, 2025
486d7c3
test(sdk): Update to use `EventCacheStoreLockState`.
Hywan Nov 11, 2025
7fe199c
refactor(sdk) Introduce `RoomEventCacheStateLock` and read/write guards.
Hywan Nov 12, 2025
12dfa6e
feat(sdk): Reset `RoomEventCacheState` when the cross-process lock is…
Hywan Nov 18, 2025
ab5099a
doc(sdk): Add missing or fix documentation.
Hywan Nov 18, 2025
deb4dd7
fix(sdk): Remove a warning for `wasm32`.
Hywan Nov 18, 2025
a085814
test(common): Test dirtiness of the cross-process lock.
Hywan Nov 18, 2025
82c8ab2
test(common): Make tests run faster.
Hywan Nov 19, 2025
24afefc
feat(sdk): Implement `RoomEventCacheStateLockWriteGuard::downgrade`.
Hywan Nov 19, 2025
d994cd6
feat(sdk): Allow shared access on `RoomEventCacheStateLock::read`.
Hywan Nov 19, 2025
0da872e
test(sdk): Add the `test_reset_when_dirty` test.
Hywan Nov 19, 2025
955d001
refactor(sdk): Rename `RoomEventCacheInner::sender` to `update_sender`.
Hywan Nov 21, 2025
95a12ee
feat(sdk): Send updates when `RoomEventCacheStateLock` is reloaded.
Hywan Nov 21, 2025
e4ff5d8
test(sdk): Ensure `EventCacheStoreLockGuard::clear_dirty` is called!
Hywan Nov 21, 2025
d206cf5
test(sdk): Add a test for dirtiness handling in `RoomEventCacheStateL…
Hywan Nov 21, 2025
6ec3441
chore: Replace `unwrap` by `expect`.
Hywan Nov 25, 2025
913e293
refactor(sdk): Change a `Semaphore(permit=1)` for a `Mutex`.
Hywan Nov 25, 2025
1b8980e
chore(sdk): Remove an `unwrap` in `debug_string`.
Hywan Nov 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions benchmarks/benches/event_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ fn find_event_relations(c: &mut Criterion) {
let (target, relations) = room_event_cache
.find_event_with_relations(target_event_id, filter)
.await
.unwrap()
.unwrap();
assert_eq!(target.event_id().as_deref().unwrap(), target_event_id);
assert_eq!(relations.len(), num_related_events as usize);
Expand Down
2 changes: 2 additions & 0 deletions benchmarks/benches/room_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) {
.lock()
.await
.unwrap()
.as_clean()
.unwrap()
.clear_all_linked_chunks()
.await
.unwrap();
Expand Down
12 changes: 10 additions & 2 deletions crates/matrix-sdk-base/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ use crate::{
InviteAcceptanceDetails, RoomStateFilter, SessionMeta,
deserialized_responses::DisplayName,
error::{Error, Result},
event_cache::store::EventCacheStoreLock,
event_cache::store::{EventCacheStoreLock, EventCacheStoreLockState},
media::store::MediaStoreLock,
response_processors::{self as processors, Context},
room::{
Expand Down Expand Up @@ -1062,7 +1062,15 @@ impl BaseClient {
self.state_store.forget_room(room_id).await?;

// Remove the room in the event cache store too.
self.event_cache_store().lock().await?.remove_room(room_id).await?;
match self.event_cache_store().lock().await? {
// If the lock is clear, we can do the operation as expected.
// If the lock is dirty, we can ignore to refresh the state, we just need to remove a
// room. Also, we must not mark the lock as non-dirty because other operations may be
// critical and may need to refresh the `EventCache`' state.
EventCacheStoreLockState::Clean(guard) | EventCacheStoreLockState::Dirty(guard) => {
guard.remove_room(room_id).await?
}
}

Ok(())
}
Expand Down
42 changes: 33 additions & 9 deletions crates/matrix-sdk-base/src/event_cache/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ mod traits;

use matrix_sdk_common::cross_process_lock::{
CrossProcessLock, CrossProcessLockError, CrossProcessLockGeneration, CrossProcessLockGuard,
TryLock,
MappedCrossProcessLockState, TryLock,
};
pub use matrix_sdk_store_encryption::Error as StoreEncryptionError;
use ruma::{OwnedEventId, events::AnySyncTimelineEvent, serde::Raw};
Expand Down Expand Up @@ -83,37 +83,61 @@ impl EventCacheStoreLock {
}

/// Acquire a spin lock (see [`CrossProcessLock::spin_lock`]).
pub async fn lock(&self) -> Result<EventCacheStoreLockGuard<'_>, CrossProcessLockError> {
let cross_process_lock_guard = self.cross_process_lock.spin_lock(None).await??.into_guard();
pub async fn lock(&self) -> Result<EventCacheStoreLockState, CrossProcessLockError> {
let lock_state =
self.cross_process_lock.spin_lock(None).await??.map(|cross_process_lock_guard| {
EventCacheStoreLockGuard { cross_process_lock_guard, store: self.store.clone() }
});

Ok(EventCacheStoreLockGuard { cross_process_lock_guard, store: self.store.deref() })
Ok(lock_state)
}
}

/// The equivalent of [`CrossProcessLockState`] but for the [`EventCacheStore`].
///
/// [`CrossProcessLockState`]: matrix_sdk_common::cross_process_lock::CrossProcessLockState
pub type EventCacheStoreLockState = MappedCrossProcessLockState<EventCacheStoreLockGuard>;

/// An RAII implementation of a “scoped lock” of an [`EventCacheStoreLock`].
/// When this structure is dropped (falls out of scope), the lock will be
/// unlocked.
pub struct EventCacheStoreLockGuard<'a> {
#[derive(Clone)]
pub struct EventCacheStoreLockGuard {
/// The cross process lock guard.
#[allow(unused)]
cross_process_lock_guard: CrossProcessLockGuard,

/// A reference to the store.
store: &'a DynEventCacheStore,
store: Arc<DynEventCacheStore>,
}

impl EventCacheStoreLockGuard {
/// Forward to [`CrossProcessLockGuard::clear_dirty`].
///
/// This is an associated method to avoid colliding with the [`Deref`]
/// implementation.
pub fn clear_dirty(this: &Self) {
this.cross_process_lock_guard.clear_dirty();
}

/// Force to [`CrossProcessLockGuard::is_dirty`].
pub fn is_dirty(this: &Self) -> bool {
this.cross_process_lock_guard.is_dirty()
}
}

#[cfg(not(tarpaulin_include))]
impl fmt::Debug for EventCacheStoreLockGuard<'_> {
impl fmt::Debug for EventCacheStoreLockGuard {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_struct("EventCacheStoreLockGuard").finish_non_exhaustive()
}
}

impl Deref for EventCacheStoreLockGuard<'_> {
impl Deref for EventCacheStoreLockGuard {
type Target = DynEventCacheStore;

fn deref(&self) -> &Self::Target {
self.store
self.store.as_ref()
}
}

Expand Down
8 changes: 4 additions & 4 deletions crates/matrix-sdk-base/src/media/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use std::{ops::Deref, sync::Arc};

use matrix_sdk_common::cross_process_lock::{
CrossProcessLock, CrossProcessLockError, CrossProcessLockGeneration, CrossProcessLockGuard,
CrossProcessLockKind, TryLock,
CrossProcessLockState, TryLock,
};
use matrix_sdk_store_encryption::Error as StoreEncryptionError;
pub use traits::{DynMediaStore, IntoMediaStore, MediaStore, MediaStoreInner};
Expand Down Expand Up @@ -135,14 +135,14 @@ impl MediaStoreLock {
pub async fn lock(&self) -> Result<MediaStoreLockGuard<'_>, CrossProcessLockError> {
let cross_process_lock_guard = match self.cross_process_lock.spin_lock(None).await?? {
// The lock is clean: no other hold acquired it, all good!
CrossProcessLockKind::Clean(guard) => guard,
CrossProcessLockState::Clean(guard) => guard,

// The lock is dirty: another holder acquired it since the last time we acquired it.
// It's not a problem in the case of the `MediaStore` because this API is “stateless” at
// the time of writing (2025-11-11). There is nothing that can be out-of-sync: all the
// state is in the database, nothing in memory.
CrossProcessLockKind::Dirty(guard) => {
self.cross_process_lock.clear_dirty();
CrossProcessLockState::Dirty(guard) => {
guard.clear_dirty();

guard
}
Expand Down
Loading
Loading