matrix_sdk/event_cache/states/mod.rs
1// Copyright 2026 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! This module handles the state of the [`EventCache`].
16
17use std::{
18 collections::HashMap,
19 fmt,
20 ops::{Deref, DerefMut},
21 sync::Arc,
22};
23
24use matrix_sdk_base::{
25 event_cache::store::{EventCacheStoreLock, EventCacheStoreLockGuard, EventCacheStoreLockState},
26 timer,
27 tracing_timer::TracingTimer,
28};
29use ruma::{OwnedEventId, OwnedRoomId, RoomId};
30use tokio::sync::{Mutex, RwLock, RwLockMappedWriteGuard, RwLockReadGuard, RwLockWriteGuard};
31use tracing::{instrument, trace};
32
33use super::{
34 CachesByRoom, EventCacheError, EventsOrigin, Result,
35 caches::{
36 TimelineVectorDiffs,
37 event_focused::{EventFocusedCacheKey, EventFocusedCacheState},
38 pinned_events::PinnedEventsCacheState,
39 room::{self, RoomEventCacheState},
40 thread::{self, ThreadEventCacheState},
41 },
42};
43
44pub(in super::super) mod selectors;
45
46/// The type containing all the states, for real.
47pub struct State {
48 store: EventCacheStoreLock,
49 by_room: HashMap<OwnedRoomId, StateForRoom>,
50}
51
52#[derive(Default)]
53pub(super) struct StateForRoom {
54 room: Option<RoomEventCacheState>,
55 threads: HashMap<OwnedEventId, ThreadEventCacheState>,
56 pinned_events: Option<PinnedEventsCacheState>,
57 event_focused: HashMap<EventFocusedCacheKey, EventFocusedCacheState>,
58}
59
60/// State for the entire Event Cache.
61///
62/// This aims at containing all the inner mutable states that ought to be
63/// updated, behind a per-process lock and a cross-process lock.
64///
65/// This type can be cloned at low-cost. It will do a shallow clone.
66#[derive(Clone)]
67pub struct StateLock {
68 inner: Arc<StateLockInner>,
69}
70
71struct StateLockInner {
72 /// The per-process lock around the real state.
73 locked_state: RwLock<State>,
74
75 /// A lock taken to avoid multiple attempts to upgrade from a read lock
76 /// to a write lock.
77 ///
78 /// Please see inline comment of [`Self::read`] to understand why it
79 /// exists.
80 state_lock_upgrade_mutex: Mutex<()>,
81}
82
83impl StateLock {
84 /// Construct a new [`EventCacheStateLock`].
85 pub fn new(store: EventCacheStoreLock) -> Self {
86 Self {
87 inner: Arc::new(StateLockInner {
88 locked_state: RwLock::new(State { store, by_room: HashMap::new() }),
89 state_lock_upgrade_mutex: Mutex::new(()),
90 }),
91 }
92 }
93
94 /// Lock this [`StateLock`] with per-thread shared access.
95 ///
96 /// This method locks the per-thread lock over the state, and then locks
97 /// the cross-process lock over the store. It returns an RAII guard
98 /// which will drop the read access to the state and to the store when
99 /// dropped.
100 ///
101 /// If the cross-process lock over the store is dirty (see
102 /// [`EventCacheStoreLockState`]), the state is reloaded.
103 #[instrument(skip_all)]
104 pub(super) async fn read<'state>(&'state self) -> Result<StateLockReadGuard<'state, State>> {
105 trace!("Acquiring the lock");
106 let tracing_timer = timer!("`read` lock");
107
108 // Only one call at a time to `read` is allowed.
109 //
110 // Why? Because in case the cross-process lock over the store is dirty, we need
111 // to upgrade the read lock over the state to a write lock.
112 //
113 // ## Upgradable read lock
114 //
115 // One may argue that this upgrades can be done with an _upgradable read lock_
116 // [^1] [^2]. We don't want to use this solution: an upgradable read lock is
117 // basically a mutex because we are losing the shared access property, i.e.
118 // having multiple read locks at the same time. This is an important property to
119 // hold for performance concerns.
120 //
121 // ## Downgradable write lock
122 //
123 // One may also argue we could first obtain a write lock over the state from the
124 // beginning, thus removing the need to upgrade the read lock to a write lock.
125 // The write lock is then downgraded to a read lock once the dirty is cleaned
126 // up. It can potentially create a deadlock in the following situation:
127 //
128 // - `read` is called once, it takes a write lock, then downgrades it to a read
129 // lock: the guard is kept alive somewhere,
130 // - `read` is called again, and waits to obtain the write lock, which is
131 // impossible as long as the guard from the previous call is not dropped.
132 //
133 // ## “Atomic” read and write
134 //
135 // One may finally argue to first obtain a read lock over the state, then drop
136 // it if the cross-process lock over the store is dirty, and immediately obtain
137 // a write lock (which can later be downgraded to a read lock). The problem is
138 // that this write lock is async: anything can happen between the drop and the
139 // new lock acquisition, and it's not possible to pause the runtime in the
140 // meantime.
141 //
142 // ## Semaphore with 1 permit, aka a Mutex
143 //
144 // The chosen idea is to allow only one execution at a time of this method: it
145 // becomes a critical section. That way we are free to “upgrade” the read lock
146 // by dropping it and obtaining a new write lock. All callers to this method are
147 // waiting, so nothing can happen in the meantime.
148 //
149 // Note that it doesn't conflict with the `write` method because this latter
150 // immediately obtains a write lock, which avoids any conflict with this method.
151 //
152 // [^1]: https://docs.rs/lock_api/0.4.14/lock_api/struct.RwLock.html#method.upgradable_read
153 // [^2]: https://docs.rs/async-lock/3.4.1/async_lock/struct.RwLock.html#method.upgradable_read
154 let _state_lock_upgrade_guard = self.inner.state_lock_upgrade_mutex.lock().await;
155
156 // Obtain a read lock.
157 let state_guard = self.inner.locked_state.read().await;
158
159 Ok(match state_guard.store.lock().await? {
160 EventCacheStoreLockState::Clean(store_guard) => {
161 trace!("Lock acquired (from clean)");
162
163 StateLockReadGuard {
164 state: StateLockReadGuardKind::Owned(state_guard),
165 store: store_guard,
166 tracing_timer: Some(tracing_timer),
167 }
168 }
169 EventCacheStoreLockState::Dirty(store_guard) => {
170 // Drop the read lock, and take a write lock to modify the state.
171 // This is safe because only one reader at a time (see
172 // `Self::state_lock_upgrade_mutex`) is allowed.
173 drop(state_guard);
174
175 let mut guard = ReloadableStateLockWriteGuard {
176 state: self.inner.locked_state.write().await,
177 store: store_guard,
178 tracing_timer,
179 };
180
181 // Reload the state.
182 guard.reload(ReloadPreprocessing::None).await?;
183
184 // All good now, mark the cross-process lock as non-dirty.
185 EventCacheStoreLockGuard::clear_dirty(&guard.store);
186
187 trace!("Lock acquired (from dirty)");
188
189 // Downgrade the write guard to a read guard, and map it into a cache state.
190 guard.downgrade()
191 }
192 })
193 }
194
195 /// Lock this [`StateLock`] with exclusive per-thread write access.
196 ///
197 /// This method locks the per-thread lock over the state, and then locks
198 /// the cross-process lock over the store. It returns an RAII guard
199 /// which will drop the write access to the state and to the store when
200 /// dropped.
201 ///
202 /// If the cross-process lock over the store is dirty (see
203 /// [`EventCacheStoreLockState`]), the state is reloaded automatically.
204 #[instrument(skip_all)]
205 async fn write<'state>(&'state self) -> Result<ReloadableStateLockWriteGuard<'state>> {
206 trace!("Acquiring lock");
207 let tracing_timer = timer!("`write` lock");
208
209 let state_guard = self.inner.locked_state.write().await;
210
211 Ok(match state_guard.store.lock().await? {
212 EventCacheStoreLockState::Clean(store_guard) => {
213 trace!("Lock acquired (from clean)");
214
215 ReloadableStateLockWriteGuard {
216 state: state_guard,
217 store: store_guard,
218 tracing_timer,
219 }
220 }
221 EventCacheStoreLockState::Dirty(store_guard) => {
222 let mut guard = ReloadableStateLockWriteGuard {
223 state: state_guard,
224 store: store_guard,
225 tracing_timer,
226 };
227
228 // Reload the state.
229 guard.reload(ReloadPreprocessing::None).await?;
230
231 // All good now, mark the cross-process lock as non-dirty.
232 EventCacheStoreLockGuard::clear_dirty(&guard.store);
233
234 trace!("Lock acquired (from dirty)");
235
236 guard
237 }
238 })
239 }
240
241 /// Clear and reload all states —in-memory and in-store— for all rooms if
242 /// `room` is `None`, otherwise for a single room.
243 ///
244 /// The `caches_for_all_rooms_exclusive_lock_guard` argument ensures an
245 /// exclusive lock over all the caches has been acquired. This is required
246 /// to ensure safety for this method.
247 #[instrument(skip_all)]
248 pub(super) async fn clear_and_reload(
249 &self,
250 _caches_for_all_rooms_exclusive_lock_guard: &RwLockWriteGuard<'_, CachesByRoom>,
251 room_id: Option<&RoomId>,
252 ) -> Result<()> {
253 let tracing_timer = timer!("`clear_and_reload` lock");
254
255 let state_guard = self.inner.locked_state.write().await;
256
257 let mut guard = match state_guard.store.lock().await? {
258 EventCacheStoreLockState::Clean(store_guard)
259 | EventCacheStoreLockState::Dirty(store_guard) => ReloadableStateLockWriteGuard {
260 state: state_guard,
261 store: store_guard,
262 tracing_timer,
263 },
264 };
265
266 // Clear all the events.
267 guard.store.clear_all_events(room_id).await?;
268
269 // At this point, all the in-memory `LinkedChunk`s are desynchronised
270 // from the storage. Resynchronise them manually by reloading them.
271 guard.reload(ReloadPreprocessing::ForgetAll).await?;
272
273 if EventCacheStoreLockGuard::is_dirty(&guard.store) {
274 // All good because the state has been reloaded, mark the
275 // cross-process lock as non-dirty.
276 EventCacheStoreLockGuard::clear_dirty(&guard.store);
277 }
278
279 Ok(())
280 }
281
282 /// Insert a new cache state at location `cache_state_selector` if none
283 /// exists.
284 ///
285 /// This method calls [`Self::write`] to acquire an exclusive access to the
286 /// [`State`] in order to insert the cache.
287 #[instrument(skip_all)]
288 pub(super) async fn try_insert_once_with<Selector, Constructor>(
289 &self,
290 cache_state_selector: Selector,
291 cache_constructor: Constructor,
292 ) -> Result<CacheStateLock<Selector>>
293 where
294 Selector: selectors::CacheState,
295 Constructor: AsyncFnOnce(EventCacheStoreLockGuard) -> Result<Selector::Item>,
296 {
297 let mut state = self.write().await?;
298 let cache_state = cache_constructor(state.store).await?;
299
300 cache_state_selector
301 .insert_once(&mut state.state, cache_state)
302 .then(|| CacheStateLock::new(cache_state_selector, self.clone()))
303 .ok_or_else(|| EventCacheError::CacheStateAlreadyExists)
304 }
305}
306
307impl fmt::Debug for StateLock {
308 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
309 formatter.debug_struct("StateLock").finish_non_exhaustive()
310 }
311}
312
313/// The read lock guard returned by [`StateLock::read`].
314pub struct StateLockReadGuard<'state, S> {
315 /// The per-thread read lock guard over the state `S`.
316 pub state: StateLockReadGuardKind<'state, S>,
317
318 /// The cross-process lock guard over the store.
319 pub store: EventCacheStoreLockGuard,
320
321 /// The [`timer!`] value, used to compute the time the lock is live.
322 tracing_timer: Option<TracingTimer>,
323}
324
325impl<'state> StateLockReadGuard<'state, State> {
326 /// Try to map this read lock guard over a [`State`] to over a
327 /// [`selectors::CacheState::Item`].
328 ///
329 /// In other words, it returns a subset of the state, selected by
330 /// `cache_state_selector`.
331 fn try_map_into_cache_state<'selector, Selector>(
332 self,
333 cache_state_selector: &'selector Selector,
334 ) -> Result<StateLockReadGuard<'state, Selector::Item>>
335 where
336 Selector: selectors::CacheState,
337 EventCacheError: From<&'selector Selector>,
338 {
339 Ok(StateLockReadGuard {
340 state: match self.state {
341 StateLockReadGuardKind::Reference(state) => StateLockReadGuardKind::Reference(
342 cache_state_selector
343 .select(state)
344 .ok_or_else(|| EventCacheError::from(cache_state_selector))?,
345 ),
346
347 StateLockReadGuardKind::Owned(state) => StateLockReadGuardKind::Owned(
348 RwLockReadGuard::try_map(state, |state| cache_state_selector.select(state))
349 .map_err(|_| EventCacheError::from(cache_state_selector))?,
350 ),
351 },
352 store: self.store,
353 tracing_timer: self.tracing_timer,
354 })
355 }
356}
357
358impl<'state> StateLockReadGuard<'state, StateForRoom> {
359 /// Project the current read lock guard onto the room cache state.
360 pub(super) fn room(&'state self) -> Option<StateLockReadGuard<'state, RoomEventCacheState>> {
361 self.state.room.as_ref().map(|room| StateLockReadGuard {
362 state: StateLockReadGuardKind::Reference(room),
363 store: self.store.clone(),
364 tracing_timer: None,
365 })
366 }
367
368 /// Project the current read lock guard onto all thread cache states.
369 pub(super) fn threads(
370 &'state self,
371 ) -> StateLockReadGuard<'state, HashMap<OwnedEventId, ThreadEventCacheState>> {
372 StateLockReadGuard {
373 state: StateLockReadGuardKind::Reference(&self.state.threads),
374 store: self.store.clone(),
375 tracing_timer: None,
376 }
377 }
378}
379
380impl<'state> StateLockReadGuard<'state, HashMap<OwnedEventId, ThreadEventCacheState>> {
381 /// Project the current read lock guard onto all thread cache states via an
382 /// iterator.
383 pub(super) fn values(
384 &'state self,
385 ) -> impl Iterator<Item = StateLockReadGuard<'state, ThreadEventCacheState>> {
386 self.state.values().map(|item| StateLockReadGuard {
387 state: StateLockReadGuardKind::Reference(item),
388 store: self.store.clone(),
389 tracing_timer: None,
390 })
391 }
392}
393
394impl<'state, S> Deref for StateLockReadGuard<'state, S> {
395 type Target = S;
396
397 fn deref(&self) -> &Self::Target {
398 &self.state
399 }
400}
401
402/// The kind of guard [`StateLockReadGuard`] owns.
403pub enum StateLockReadGuardKind<'state, S> {
404 /// A read lock over the state is acquired, and this is a reference to a
405 /// cache (sub-)state.
406 ///
407 /// This is useful if one needs to run operations over multiple cache
408 /// (sub-)states without mapping the read lock guard over the state
409 /// (because it would consume it).
410 Reference(&'state S),
411
412 /// The read lock over the state `S` is acquired, and this is a mapped
413 /// guard to a cache (sub-)state.
414 Owned(RwLockReadGuard<'state, S>),
415}
416
417impl<'state, S> Deref for StateLockReadGuardKind<'state, S> {
418 type Target = S;
419
420 fn deref(&self) -> &Self::Target {
421 match self {
422 Self::Reference(state) => state,
423 Self::Owned(state) => state.deref(),
424 }
425 }
426}
427
428/// Private type to hold a “reloadable” write lock guard around the state and
429/// the store.
430///
431/// This type aims at being transient: either it maps to a
432/// [`StateLockReadGuard`] with [`Self::downgrade`], or it maps to a
433/// [`StateLockWriteGuard`] with [`Self::try_map_into_cache_state`]. Its main
434/// goal remains to provide the [`Self::reload`] method to reload all the state
435/// of the Event Cache.
436struct ReloadableStateLockWriteGuard<'state> {
437 /// The per-thread read lock guard over the state `S`.
438 state: RwLockWriteGuard<'state, State>,
439
440 /// The cross-process lock guard over the store.
441 store: EventCacheStoreLockGuard,
442
443 /// The [`timer!`] value, used to compute the time the lock is live.
444 tracing_timer: TracingTimer,
445}
446
447impl<'state> ReloadableStateLockWriteGuard<'state> {
448 /// Try to map this write lock guard over a [`State`] to over a
449 /// [`selectors::CacheState::Item`].
450 ///
451 /// In other words, it returns a subset of the state, selected by
452 /// `cache_state_selector`.
453 fn try_map_into_cache_state<'selector, Selector>(
454 self,
455 cache_state_selector: &'selector Selector,
456 ) -> Result<StateLockWriteGuard<'state, Selector::Item>>
457 where
458 Selector: selectors::CacheState,
459 EventCacheError: From<&'selector Selector>,
460 {
461 Ok(StateLockWriteGuard {
462 state: StateLockWriteGuardKind::Owned(
463 RwLockWriteGuard::try_map(self.state, |state| {
464 cache_state_selector.select_mut(state)
465 })
466 .map_err(|_| EventCacheError::from(cache_state_selector))?,
467 ),
468 store: self.store,
469 _tracing_timer: Some(self.tracing_timer),
470 })
471 }
472
473 /// Synchronously downgrades a write lock into a read lock.
474 ///
475 /// The per-thread/state lock is downgraded atomically, without allowing
476 /// any writers to take exclusive access of the lock in the meantime.
477 ///
478 /// It returns an RAII guard which will drop the read access to the
479 /// state and to the store when dropped.
480 fn downgrade(self) -> StateLockReadGuard<'state, State> {
481 StateLockReadGuard {
482 state: StateLockReadGuardKind::Owned(self.state.downgrade()),
483 store: self.store,
484 tracing_timer: Some(self.tracing_timer),
485 }
486 }
487
488 async fn reload(&mut self, preprocessing: ReloadPreprocessing) -> Result<()> {
489 trace!("Reloading the state");
490
491 // Iterate over all states and reload them.
492 for (room_id, StateForRoom { room, threads, pinned_events, event_focused }) in
493 self.state.by_room.iter_mut()
494 {
495 // Room.
496 if let Some(room_state) = room {
497 let mut room_state = StateLockWriteGuard {
498 state: StateLockWriteGuardKind::Reference(room_state),
499 store: self.store.clone(),
500 _tracing_timer: None,
501 };
502
503 let updates_as_vector_diffs = room_state.reload(preprocessing).await?;
504 room_state.update_sender.send(
505 room::RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
506 diffs: updates_as_vector_diffs,
507 origin: EventsOrigin::Cache,
508 }),
509 Some(room::RoomEventCacheGenericUpdate { room_id: room_id.clone() }),
510 );
511 }
512
513 // Threads.
514 for thread_state in threads.values_mut() {
515 let mut thread_state = StateLockWriteGuard {
516 state: StateLockWriteGuardKind::Reference(thread_state),
517 store: self.store.clone(),
518 _tracing_timer: None,
519 };
520
521 let updates_as_vector_diffs = thread_state.reload(preprocessing).await?;
522 thread_state.update_sender.send(
523 thread::ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
524 diffs: updates_as_vector_diffs,
525 origin: EventsOrigin::Cache,
526 }),
527 Some(room::RoomEventCacheGenericUpdate { room_id: room_id.clone() }),
528 );
529 }
530
531 // Pinned events.
532 if let Some(pinned_events_state) = pinned_events {
533 let mut pinned_events_state = StateLockWriteGuard {
534 state: StateLockWriteGuardKind::Reference(pinned_events_state),
535 store: self.store.clone(),
536 _tracing_timer: None,
537 };
538
539 let updates_as_vector_diffs = pinned_events_state.reload(preprocessing).await?;
540 pinned_events_state.update_sender.send(TimelineVectorDiffs {
541 diffs: updates_as_vector_diffs,
542 origin: EventsOrigin::Cache,
543 });
544 }
545
546 // Event-focused.
547 for event_focused_state in event_focused.values_mut() {
548 let mut event_focused_state = StateLockWriteGuard {
549 state: StateLockWriteGuardKind::Reference(event_focused_state),
550 store: self.store.clone(),
551 _tracing_timer: None,
552 };
553
554 let updates_as_vector_diffs = event_focused_state.reload(preprocessing).await?;
555 let _ = event_focused_state.update_sender.send(TimelineVectorDiffs {
556 diffs: updates_as_vector_diffs,
557 origin: EventsOrigin::Cache,
558 });
559 }
560 }
561
562 Ok(())
563 }
564}
565
566/// The write lock guard returned by [`StateLock::write`].
567pub struct StateLockWriteGuard<'state, S> {
568 /// The per-thread write lock guard over the state `S`.
569 pub state: StateLockWriteGuardKind<'state, S>,
570
571 /// The cross-process lock guard over the store.
572 pub store: EventCacheStoreLockGuard,
573
574 /// The [`timer!`] value, used to compute the time the lock is live.
575 _tracing_timer: Option<TracingTimer>,
576}
577
578impl<'state, S> Deref for StateLockWriteGuard<'state, S> {
579 type Target = S;
580
581 fn deref(&self) -> &Self::Target {
582 &self.state
583 }
584}
585
586impl<'state, S> DerefMut for StateLockWriteGuard<'state, S> {
587 fn deref_mut(&mut self) -> &mut Self::Target {
588 &mut self.state
589 }
590}
591
592/// The kind of guard [`StateLockWriteGuard`] owns.
593pub enum StateLockWriteGuardKind<'state, S> {
594 /// A write lock over the state is acquired, and this is a reference to a
595 /// cache (sub-)state.
596 ///
597 /// This is useful if one needs to run operations over multiple cache
598 /// (sub-)states without mapping the write lock guard over the state
599 /// (because it would consume it).
600 Reference(&'state mut S),
601
602 /// The write lock over the state `S` is acquired, and this is a mapped
603 /// guard to a cache (sub-)state.
604 Owned(RwLockMappedWriteGuard<'state, S>),
605}
606
607impl<'state, S> Deref for StateLockWriteGuardKind<'state, S> {
608 type Target = S;
609
610 fn deref(&self) -> &Self::Target {
611 match self {
612 Self::Reference(state) => state,
613 Self::Owned(state) => state.deref(),
614 }
615 }
616}
617
618impl<'state, S> DerefMut for StateLockWriteGuardKind<'state, S> {
619 fn deref_mut(&mut self) -> &mut Self::Target {
620 match self {
621 Self::Reference(state) => state,
622 Self::Owned(state) => state.deref_mut(),
623 }
624 }
625}
626
627/// A wrapper around [`State`] with a [`CacheStateSelector`], facilitating the
628/// embedding of these API in a single type.
629pub struct CacheStateLock<Selector> {
630 cache_state_selector: Selector,
631 state_lock: StateLock,
632}
633
634impl<Selector> CacheStateLock<Selector>
635where
636 Selector: selectors::CacheState,
637{
638 pub(super) fn new(cache_state_selector: Selector, state_lock: StateLock) -> Self {
639 Self { cache_state_selector, state_lock }
640 }
641}
642
643// Fallible methods.
644impl<Selector> CacheStateLock<Selector>
645where
646 Selector: selectors::CacheState,
647 EventCacheError: for<'a> From<&'a Selector>,
648{
649 /// Lock this [`CacheStateLock`] by locking the full [`State`] with
650 /// per-thread shared access.
651 ///
652 /// This method locks the per-thread lock over the state, and then locks
653 /// the cross-process lock over the store. It returns an RAII guard
654 /// which will drop the read access to the state and to the store when
655 /// dropped.
656 ///
657 /// If the cross-process lock over the store is dirty (see
658 /// [`EventCacheStoreLockState`]), the state is reloaded.
659 pub async fn read(&self) -> Result<StateLockReadGuard<'_, Selector::Item>> {
660 self.state_lock.read().await?.try_map_into_cache_state(&self.cache_state_selector)
661 }
662
663 /// Lock this [`CacheStateLock`] by locking the full [`State`] with
664 /// exclusive per-thread write access.
665 ///
666 /// This method locks the per-thread lock over the state, and then locks
667 /// the cross-process lock over the store. It returns an RAII guard
668 /// which will drop the write access to the state and to the store when
669 /// dropped.
670 ///
671 /// If the cross-process lock over the store is dirty (see
672 /// [`EventCacheStoreLockState`]), the state is reloaded.
673 pub async fn write(&self) -> Result<StateLockWriteGuard<'_, Selector::Item>> {
674 self.state_lock.write().await?.try_map_into_cache_state(&self.cache_state_selector)
675 }
676
677 /// Shortcut to reload (with no preprocessing) the state cache just for
678 /// test.
679 #[cfg(test)]
680 pub async fn reload_no_preprocessing(&self) -> Result<()> {
681 self.state_lock.write().await?.reload(ReloadPreprocessing::None).await
682 }
683}
684
685/// Kind of pre-processing to do when reloading a cache.
686#[derive(Clone, Copy)]
687pub enum ReloadPreprocessing {
688 /// Erase all events before reloading.
689 ForgetAll,
690
691 /// Do nothing before reloading.
692 None,
693}