Skip to main content

matrix_sdk/event_cache/caches/room/
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
15pub mod pagination;
16mod state;
17mod updates;
18
19use std::{collections::BTreeMap, fmt, sync::Arc};
20
21use eyeball::SharedObservable;
22use matrix_sdk_base::{
23    deserialized_responses::{AmbiguityChange, ThreadSummary},
24    event_cache::Event,
25    sync::Timeline,
26};
27use ruma::{
28    EventId, OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId,
29    events::{AnyRoomAccountDataEvent, relation::RelationType},
30    serde::Raw,
31};
32use tokio::sync::{Notify, mpsc};
33use tracing::{instrument, trace, warn};
34
35use self::pagination::RoomPagination;
36pub use self::{
37    state::RoomEventCacheState,
38    updates::{
39        LinkedChunkUpdateSender, RoomEventCacheGenericUpdate, RoomEventCacheLinkedChunkUpdate,
40        RoomEventCacheUpdate, RoomEventCacheUpdateSender,
41    },
42};
43use super::{
44    super::{
45        EventsOrigin, Result,
46        states::{CacheStateLock, StateLockWriteGuard, selectors::RoomStateSelector},
47    },
48    TimelineVectorDiffs,
49    event_linked_chunk::sort_positions_descending,
50    pagination::SharedPaginationStatus,
51    read_receipts::MaybeReceiptEventContent,
52    subscriber::{AutoShrinkMessage, Subscriber},
53};
54use crate::room::WeakRoom;
55
56/// The outcome of [`RoomEventCache::rfind_map_event_until_gap_by`].
57#[derive(Debug)]
58pub(crate) enum RFindMapOutcome<O> {
59    /// An event matched the predicate.
60    Found(O),
61
62    /// No event matched before a gap: there may be more events to fetch.
63    ReachedGap,
64
65    /// No event matched, all the way to the start of the room.
66    ReachedStart,
67
68    /// No event matched within the maximum number of stored chunks to read.
69    ReachedLimit,
70}
71
72/// A subset of an event cache, for a room.
73///
74/// Cloning is shallow, and thus is cheap to do.
75#[derive(Clone)]
76pub struct RoomEventCache {
77    inner: Arc<RoomEventCacheInner>,
78}
79
80impl fmt::Debug for RoomEventCache {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        f.debug_struct("RoomEventCache").finish_non_exhaustive()
83    }
84}
85
86impl RoomEventCache {
87    /// Create a new [`RoomEventCache`] using the given room and store.
88    pub(super) fn new(
89        room_id: OwnedRoomId,
90        weak_room: WeakRoom,
91        own_user_id: OwnedUserId,
92        state: CacheStateLock<RoomStateSelector>,
93        shared_pagination_status: SharedObservable<SharedPaginationStatus>,
94        auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
95        update_sender: RoomEventCacheUpdateSender,
96    ) -> Self {
97        Self {
98            inner: Arc::new(RoomEventCacheInner {
99                room_id,
100                weak_room,
101                own_user_id,
102                state,
103                update_sender,
104                pagination_batch_token_notifier: Notify::new(),
105                auto_shrink_sender,
106                shared_pagination_status,
107            }),
108        }
109    }
110
111    /// Get the room ID for this [`RoomEventCache`].
112    pub fn room_id(&self) -> &RoomId {
113        &self.inner.room_id
114    }
115
116    /// Get the owner of this [`RoomEventCache`].
117    pub(super) fn own_user_id(&self) -> &OwnedUserId {
118        &self.inner.own_user_id
119    }
120
121    /// Get the weak room of this [`RoomEventCache`].
122    pub(super) fn weak_room(&self) -> &WeakRoom {
123        &self.inner.weak_room
124    }
125
126    /// Read all current events.
127    ///
128    /// Use [`RoomEventCache::subscribe`] to get all current events, plus a
129    /// subscriber.
130    pub async fn events(&self) -> Result<Vec<Event>> {
131        let state = self.inner.state.read().await?;
132
133        Ok(state.room_linked_chunk().events().map(|(_position, item)| item.clone()).collect())
134    }
135
136    /// Subscribe to this room updates, after getting the initial list of
137    /// events.
138    ///
139    /// Use [`RoomEventCache::events`] to get all current events without the
140    /// subscriber. Creating, and especially dropping, a [`Subscriber`] isn't
141    /// free, as it triggers side-effects.
142    pub async fn subscribe(&self) -> Result<(Vec<Event>, Subscriber<RoomEventCacheUpdate>)> {
143        let state = self.inner.state.read().await?;
144        let events =
145            state.room_linked_chunk().events().map(|(_position, item)| item.clone()).collect();
146
147        let subscribers_handle = state.subscribers_handle();
148
149        let subscriber = Subscriber::new(
150            self.inner.update_sender.new_room_receiver(),
151            AutoShrinkMessage::Room { room_id: self.inner.room_id.clone() },
152            self.inner.auto_shrink_sender.clone(),
153            subscribers_handle,
154        );
155
156        trace!("added a room event cache subscriber; new count: {}", subscribers_handle.count());
157
158        Ok((events, subscriber))
159    }
160
161    /// Return a [`RoomPagination`] type useful for running back-pagination
162    /// queries in the current room.
163    pub fn pagination(&self) -> RoomPagination {
164        RoomPagination::new(self.inner.clone())
165    }
166
167    /// Try to find a single event in this room, starting from the most recent
168    /// event.
169    ///
170    /// The `predicate` receives the current event as its single argument.
171    ///
172    /// **Warning**! It looks into the loaded events from the in-memory linked
173    /// chunk **only**. It doesn't look inside the storage.
174    pub async fn rfind_map_event_in_memory_by<O, P>(&self, predicate: P) -> Result<Option<O>>
175    where
176        P: FnMut(&Event) -> Option<O>,
177    {
178        Ok(self.inner.state.read().await?.rfind_map_event_in_memory_by(predicate))
179    }
180
181    /// Try to find a single event in this room, starting from the most recent
182    /// event, and carrying on in the storage until a gap or the start of the
183    /// room.
184    ///
185    /// Contrary to [`Self::rfind_map_event_in_memory_by`], it also reads up to
186    /// `max_stored_chunkds` stored chunks preceding the in-memory ones.
187    pub(crate) async fn rfind_map_event_until_gap_by<O, P>(
188        &self,
189        max_stored_chunks: usize,
190        predicate: P,
191    ) -> Result<RFindMapOutcome<O>>
192    where
193        P: FnMut(&Event) -> Option<O>,
194    {
195        self.inner
196            .state
197            .read()
198            .await?
199            .rfind_map_event_until_gap_by(max_stored_chunks, predicate)
200            .await
201    }
202
203    /// Try to find an event by ID in this room.
204    ///
205    /// It starts by looking into loaded events before looking inside the
206    /// storage.
207    pub async fn find_event(&self, event_id: &EventId) -> Result<Option<Event>> {
208        Ok(self
209            .inner
210            .state
211            .read()
212            .await?
213            .find_event(event_id)
214            .await
215            .ok()
216            .flatten()
217            .map(|(_loc, event)| event))
218    }
219
220    /// Try to find an event by ID in this room, along with its related events.
221    ///
222    /// You can filter which types of related events to retrieve using `filter`.
223    /// `None` will retrieve related events of any type.
224    ///
225    /// The related events are sorted like this:
226    ///
227    /// - events saved out-of-band (with `RoomEventCache::save_events`) will be
228    ///   located at the beginning of the array.
229    /// - events present in the linked chunk (be it in memory or in the storage)
230    ///   will be sorted according to their ordering in the linked chunk.
231    pub async fn find_event_with_relations(
232        &self,
233        event_id: &EventId,
234        filter: Option<Vec<RelationType>>,
235    ) -> Result<Option<(Event, Vec<Event>)>> {
236        // Search in all loaded or stored events.
237        Ok(self
238            .inner
239            .state
240            .read()
241            .await?
242            .find_event_with_relations(event_id, filter)
243            .await
244            .ok()
245            .flatten())
246    }
247
248    /// Try to find the related events for an event by ID in this room.
249    ///
250    /// You can filter which types of related events to retrieve using `filter`.
251    /// `None` will retrieve related events of any type.
252    ///
253    /// The related events are sorted like this:
254    ///
255    /// - events saved out-of-band (with `RoomEventCache::save_events`) will be
256    ///   located at the beginning of the array.
257    /// - events present in the linked chunk (be it in memory or in the storage)
258    ///   will be sorted according to their ordering in the linked chunk.
259    pub async fn find_event_relations(
260        &self,
261        event_id: &EventId,
262        filter: Option<Vec<RelationType>>,
263    ) -> Result<Vec<Event>> {
264        // Search in all loaded or stored events.
265        self.inner.state.read().await?.find_event_relations(event_id, filter.clone()).await
266    }
267
268    /// Return a reference to the state.
269    pub(in super::super) fn state(&self) -> &CacheStateLock<RoomStateSelector> {
270        &self.inner.state
271    }
272
273    /// Handle an update from a joined room.
274    #[instrument(skip_all, fields(room_id = %self.room_id()))]
275    pub(super) async fn handle_joined_room_update(
276        &self,
277        timeline: Timeline,
278        read_receipts: MaybeReceiptEventContent,
279        account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
280        ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
281        avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
282    ) -> Result<()> {
283        self.inner
284            .handle_timeline(timeline, read_receipts, ambiguity_changes, avatar_changes)
285            .await?;
286        self.inner.handle_account_data(account_data);
287
288        Ok(())
289    }
290
291    /// Handle an update from a left room.
292    #[instrument(skip_all, fields(room_id = %self.room_id()))]
293    pub(super) async fn handle_left_room_update(
294        &self,
295        timeline: Timeline,
296        ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
297    ) -> Result<()> {
298        self.inner
299            .handle_timeline(timeline, MaybeReceiptEventContent::none(), ambiguity_changes, None)
300            .await?;
301
302        Ok(())
303    }
304
305    pub(in super::super) fn update_thread_summary(
306        &self,
307        thread_id: &EventId,
308        new_thread_summary: ThreadSummary,
309    ) -> Result<()> {
310        // Nothing to do here apart of sending an update. The `ThreadSummary` is
311        // stored inside `ThreadInfo` already, this cache doesn't need
312        // to hold it.
313        self.inner.update_sender.send(
314            RoomEventCacheUpdate::UpdateThreadSummary {
315                thread_root: thread_id.to_owned(),
316                thread_summary: new_thread_summary,
317            },
318            Some(RoomEventCacheGenericUpdate { room_id: self.inner.room_id.clone() }),
319        );
320
321        Ok(())
322    }
323
324    /// Get a reference to the [`RoomEventCacheUpdateSender`].
325    pub(in super::super) fn update_sender(&self) -> &RoomEventCacheUpdateSender {
326        &self.inner.update_sender
327    }
328
329    /// Handle a single event from the `SendQueue`.
330    pub(crate) async fn insert_sent_event_from_send_queue(&self, event: Event) -> Result<()> {
331        self.inner.insert_sent_event_from_send_queue(event).await
332    }
333
334    /// Return a nice debug string (a vector of lines) for the linked chunk of
335    /// events for this room.
336    pub async fn debug_string(&self) -> Vec<String> {
337        match self.inner.state.read().await {
338            Ok(read_guard) => read_guard.room_linked_chunk().debug_string(),
339            Err(err) => {
340                warn!(?err, "Failed to obtain the read guard for the `RoomEventCache`");
341
342                vec![]
343            }
344        }
345    }
346}
347
348/// The (non-cloneable) details of the `RoomEventCache`.
349pub(super) struct RoomEventCacheInner {
350    /// The room id for this room.
351    room_id: OwnedRoomId,
352
353    weak_room: WeakRoom,
354
355    /// The user's own user id.
356    own_user_id: OwnedUserId,
357
358    /// State for this room's cache.
359    state: CacheStateLock<RoomStateSelector>,
360
361    /// A notifier that we received a new pagination token.
362    pagination_batch_token_notifier: Notify,
363
364    shared_pagination_status: SharedObservable<SharedPaginationStatus>,
365
366    /// Sender to the auto-shrink channel.
367    ///
368    /// See doc comment around [`EventCache::auto_shrink_linked_chunk_task`] for
369    /// more details.
370    auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
371
372    /// Update sender for this room.
373    update_sender: RoomEventCacheUpdateSender,
374}
375
376impl RoomEventCacheInner {
377    fn handle_account_data(&self, account_data: Vec<Raw<AnyRoomAccountDataEvent>>) {
378        if account_data.is_empty() {
379            return;
380        }
381
382        let mut handled_read_marker = false;
383
384        trace!("Handling account data");
385
386        for raw_event in account_data {
387            match raw_event.deserialize() {
388                Ok(AnyRoomAccountDataEvent::FullyRead(ev)) => {
389                    // If duplicated, do not forward read marker multiple times
390                    // to avoid clutter the update channel.
391                    if handled_read_marker {
392                        continue;
393                    }
394
395                    handled_read_marker = true;
396
397                    // Propagate to observers. (We ignore the error if there
398                    // aren't any.)
399                    self.update_sender.send(
400                        RoomEventCacheUpdate::MoveReadMarkerTo { event_id: ev.content.event_id },
401                        None,
402                    );
403                }
404
405                Ok(_) => {
406                    // We're not interested in other room account data updates,
407                    // at this point.
408                }
409
410                Err(e) => {
411                    let event_type = raw_event.get_field::<String>("type").ok().flatten();
412                    warn!(event_type, "Failed to deserialize account data: {e}");
413                }
414            }
415        }
416    }
417
418    /// Handle a [`Timeline`], i.e. new events received by a sync for this room.
419    async fn handle_timeline(
420        &self,
421        timeline: Timeline,
422        read_receipts: MaybeReceiptEventContent,
423        ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
424        avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
425    ) -> Result<()> {
426        self.handle_timeline_inner(
427            self.state.write().await?,
428            timeline,
429            read_receipts,
430            ambiguity_changes,
431            avatar_changes,
432        )
433        .await
434    }
435
436    /// Handle a single event from the `SendQueue`.
437    ///
438    /// The event is inserted if and only if the cache is not empty.
439    async fn insert_sent_event_from_send_queue(&self, event: Event) -> Result<()> {
440        let state = self.state.write().await?;
441
442        // Insert the event if the room is not empty, otherwise it can break the
443        // pagination logic when detecting the start of the timeline because no
444        // gap can be inserted properly: it is impossible to compute a
445        // `prev_batch` token here.
446        if state.room_linked_chunk().events().next().is_some() {
447            return self
448                .handle_timeline_inner(
449                    state,
450                    Timeline { limited: false, prev_batch: None, events: vec![event] },
451                    MaybeReceiptEventContent::none(),
452                    BTreeMap::new(),
453                    None,
454                )
455                .await;
456        }
457
458        Ok(())
459    }
460
461    async fn handle_timeline_inner(
462        &self,
463        mut state: StateLockWriteGuard<'_, RoomEventCacheState>,
464        timeline: Timeline,
465        read_receipts: MaybeReceiptEventContent,
466        ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
467        avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
468    ) -> Result<()> {
469        if timeline.events.is_empty()
470            && timeline.prev_batch.is_none()
471            && read_receipts.is_none()
472            && ambiguity_changes.is_empty()
473            && avatar_changes.as_ref().is_none_or(|avatars| avatars.is_empty())
474        {
475            return Ok(());
476        }
477
478        trace!("adding new events");
479
480        let (stored_prev_batch_token, timeline_event_diffs) =
481            state.handle_sync(timeline, &read_receipts).await?;
482
483        // Now that all events have been added, we can trigger the
484        // `pagination_token_notifier`.
485        if stored_prev_batch_token {
486            self.pagination_batch_token_notifier.notify_one();
487        }
488
489        // The order matters here: first send the timeline event diffs, then
490        // only the related events (read receipts, etc.).
491        //
492        // The diffs must be sent while the state lock is still held: the
493        // indices they carry are only valid if no other change to the linked
494        // chunk is sent before them.
495        if !timeline_event_diffs.is_empty() {
496            state.update_sender.send(
497                RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
498                    diffs: timeline_event_diffs,
499                    origin: EventsOrigin::Sync,
500                }),
501                Some(RoomEventCacheGenericUpdate { room_id: self.room_id.clone() }),
502            );
503        }
504
505        if let Some(read_receipts) = read_receipts.into_inner() {
506            self.update_sender
507                .send(RoomEventCacheUpdate::AddReadReceiptEvent { event: read_receipts }, None);
508        }
509
510        if !ambiguity_changes.is_empty() || avatar_changes.as_ref().is_some_and(|c| !c.is_empty()) {
511            self.update_sender.send(
512                RoomEventCacheUpdate::UpdateMembers { ambiguity_changes, avatar_changes },
513                None,
514            );
515        }
516
517        Ok(())
518    }
519}
520
521#[cfg(test)]
522mod tests {
523    use matrix_sdk_base::{RoomState, event_cache::Event};
524    use matrix_sdk_test::{async_test, event_factory::EventFactory};
525    use ruma::{
526        RoomId, event_id,
527        events::{relation::RelationType, room::message::RoomMessageEventContentWithoutRelation},
528        room_id, user_id,
529    };
530
531    use crate::test_utils::logged_in_client;
532
533    #[async_test]
534    async fn test_find_event_by_id_with_edit_relation() {
535        let original_id = event_id!("$original");
536        let related_id = event_id!("$related");
537        let room_id = room_id!("!galette:saucisse.bzh");
538        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
539
540        assert_relations(
541            room_id,
542            f.text_msg("Original event").event_id(original_id).into(),
543            f.text_msg("* An edited event")
544                .edit(
545                    original_id,
546                    RoomMessageEventContentWithoutRelation::text_plain("And edited event"),
547                )
548                .event_id(related_id)
549                .into(),
550            f,
551        )
552        .await;
553    }
554
555    #[async_test]
556    async fn test_find_event_by_id_with_thread_reply_relation() {
557        let original_id = event_id!("$original");
558        let related_id = event_id!("$related");
559        let room_id = room_id!("!galette:saucisse.bzh");
560        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
561
562        assert_relations(
563            room_id,
564            f.text_msg("Original event").event_id(original_id).into(),
565            f.text_msg("A reply").in_thread(original_id, related_id).event_id(related_id).into(),
566            f,
567        )
568        .await;
569    }
570
571    #[async_test]
572    async fn test_find_event_by_id_with_reaction_relation() {
573        let original_id = event_id!("$original");
574        let related_id = event_id!("$related");
575        let room_id = room_id!("!galette:saucisse.bzh");
576        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
577
578        assert_relations(
579            room_id,
580            f.text_msg("Original event").event_id(original_id).into(),
581            f.reaction(original_id, ":D").event_id(related_id).into(),
582            f,
583        )
584        .await;
585    }
586
587    #[async_test]
588    async fn test_find_event_by_id_with_poll_response_relation() {
589        let original_id = event_id!("$original");
590        let related_id = event_id!("$related");
591        let room_id = room_id!("!galette:saucisse.bzh");
592        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
593
594        assert_relations(
595            room_id,
596            f.poll_start("Poll start event", "A poll question", vec!["An answer"])
597                .event_id(original_id)
598                .into(),
599            f.poll_response(vec!["1"], original_id).event_id(related_id).into(),
600            f,
601        )
602        .await;
603    }
604
605    #[async_test]
606    async fn test_find_event_by_id_with_poll_end_relation() {
607        let original_id = event_id!("$original");
608        let related_id = event_id!("$related");
609        let room_id = room_id!("!galette:saucisse.bzh");
610        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
611
612        assert_relations(
613            room_id,
614            f.poll_start("Poll start event", "A poll question", vec!["An answer"])
615                .event_id(original_id)
616                .into(),
617            f.poll_end("Poll ended", original_id).event_id(related_id).into(),
618            f,
619        )
620        .await;
621    }
622
623    #[async_test]
624    async fn test_find_event_by_id_with_filtered_relationships() {
625        let original_id = event_id!("$original");
626        let related_id = event_id!("$related");
627        let associated_related_id = event_id!("$recursive_related");
628        let room_id = room_id!("!galette:saucisse.bzh");
629        let event_factory = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
630
631        let original_event = event_factory.text_msg("Original event").event_id(original_id).into();
632        let related_event = event_factory
633            .text_msg("* Edited event")
634            .edit(original_id, RoomMessageEventContentWithoutRelation::text_plain("Edited event"))
635            .event_id(related_id)
636            .into();
637        let associated_related_event =
638            event_factory.reaction(related_id, "🤡").event_id(associated_related_id).into();
639
640        let client = logged_in_client(None).await;
641
642        client.base_client().get_or_create_room(room_id, RoomState::Joined);
643
644        let (room_event_cache, _drop_handles) =
645            client.event_cache().unwrap().room(room_id).await.unwrap();
646
647        {
648            let mut state = room_event_cache.inner.state.write().await.unwrap();
649
650            // Save the original event.
651            state.save_events([original_event]).await.unwrap();
652
653            // Save the related event.
654            state.save_events([related_event]).await.unwrap();
655
656            // Save the associated related event, which redacts the related
657            // event.
658            state.save_events([associated_related_event]).await.unwrap();
659        }
660
661        let filter = Some(vec![RelationType::Replacement]);
662        let (event, related_events) = room_event_cache
663            .find_event_with_relations(original_id, filter)
664            .await
665            .expect("Failed to find the event with relations")
666            .expect("Event has no relation");
667        // Fetched event is the right one.
668        let cached_event_id = event.event_id().unwrap();
669        assert_eq!(cached_event_id, original_id);
670
671        // There's only the edit event (an edit event can't have its own edit
672        // event).
673        assert_eq!(related_events.len(), 1);
674
675        let related_event_id = related_events[0].event_id().unwrap();
676        assert_eq!(related_event_id, related_id);
677
678        // Now we'll filter threads instead, there should be no related events
679        let filter = Some(vec![RelationType::Thread]);
680        let (event, related_events) = room_event_cache
681            .find_event_with_relations(original_id, filter)
682            .await
683            .expect("Failed to find the event with relations")
684            .expect("Event has no relation");
685
686        // Fetched event is the right one.
687        let cached_event_id = event.event_id().unwrap();
688        assert_eq!(cached_event_id, original_id);
689        // No Thread related events found
690        assert!(related_events.is_empty());
691    }
692
693    #[async_test]
694    async fn test_find_event_by_id_with_recursive_relation() {
695        let original_id = event_id!("$original");
696        let related_id = event_id!("$related");
697        let associated_related_id = event_id!("$recursive_related");
698        let room_id = room_id!("!galette:saucisse.bzh");
699        let event_factory = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
700
701        let original_event = event_factory.text_msg("Original event").event_id(original_id).into();
702        let related_event = event_factory
703            .text_msg("* Edited event")
704            .edit(original_id, RoomMessageEventContentWithoutRelation::text_plain("Edited event"))
705            .event_id(related_id)
706            .into();
707        let associated_related_event =
708            event_factory.reaction(related_id, "👍").event_id(associated_related_id).into();
709
710        let client = logged_in_client(None).await;
711
712        client.base_client().get_or_create_room(room_id, RoomState::Joined);
713
714        let (room_event_cache, _drop_handles) =
715            client.event_cache().unwrap().room(room_id).await.unwrap();
716
717        {
718            let mut state = room_event_cache.inner.state.write().await.unwrap();
719
720            // Save the original event.
721            state.save_events([original_event]).await.unwrap();
722
723            // Save the related event.
724            state.save_events([related_event]).await.unwrap();
725
726            // Save the associated related event, which redacts the related
727            // event.
728            state.save_events([associated_related_event]).await.unwrap();
729        }
730
731        let (event, related_events) = room_event_cache
732            .find_event_with_relations(original_id, None)
733            .await
734            .expect("Failed to find the event with relations")
735            .expect("Event has no relation");
736        // Fetched event is the right one.
737        let cached_event_id = event.event_id().unwrap();
738        assert_eq!(cached_event_id, original_id);
739
740        // There are both the related id and the associatively related id
741        assert_eq!(related_events.len(), 2);
742
743        let related_event_id = related_events[0].event_id().unwrap();
744        assert_eq!(related_event_id, related_id);
745        let related_event_id = related_events[1].event_id().unwrap();
746        assert_eq!(related_event_id, associated_related_id);
747    }
748
749    async fn assert_relations(
750        room_id: &RoomId,
751        original_event: Event,
752        related_event: Event,
753        event_factory: EventFactory,
754    ) {
755        let client = logged_in_client(None).await;
756
757        client.base_client().get_or_create_room(room_id, RoomState::Joined);
758
759        let (room_event_cache, _drop_handles) =
760            client.event_cache().unwrap().room(room_id).await.unwrap();
761
762        let original_event_id = original_event.event_id().unwrap().to_owned();
763        let related_id = related_event.event_id().unwrap().to_owned();
764
765        {
766            let mut state = room_event_cache.inner.state.write().await.unwrap();
767
768            // Save the original event.
769            state.save_events([original_event]).await.unwrap();
770
771            // Save an unrelated event to check it's not in the related events
772            // list.
773            let unrelated_id = event_id!("$2");
774            state
775                .save_events([event_factory
776                    .text_msg("An unrelated event")
777                    .event_id(unrelated_id)
778                    .into()])
779                .await
780                .unwrap();
781
782            // Save the related event.
783            state.save_events([related_event]).await.unwrap();
784        }
785
786        let (event, related_events) = room_event_cache
787            .find_event_with_relations(&original_event_id, None)
788            .await
789            .expect("Failed to find the event with relations")
790            .expect("Event has no relation");
791        // Fetched event is the right one.
792        let cached_event_id = event.event_id().unwrap();
793        assert_eq!(cached_event_id, original_event_id);
794
795        // There is only the actually related event in the related ones
796        let related_event_id = related_events[0].event_id().unwrap();
797        assert_eq!(related_event_id, related_id);
798    }
799}
800
801#[cfg(all(test, not(target_family = "wasm")))] // This uses the cross-process lock, so needs time support.
802mod timed_tests {
803    use std::{ops::Not, sync::Arc};
804
805    use assert_matches::assert_matches;
806    use eyeball_im::VectorDiff;
807    use futures_util::FutureExt;
808    use matrix_sdk_base::{
809        RoomState,
810        event_cache::{
811            Gap,
812            store::{ControlledMemoryStore, EventCacheStore as _, MemoryStore},
813        },
814        linked_chunk::{
815            ChunkContent, ChunkIdentifier, LinkedChunkId, Position, Update,
816            lazy_loader::from_all_chunks,
817        },
818        store::StoreConfig,
819        sync::Timeline,
820    };
821    use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
822    use matrix_sdk_test::{User, async_test, event_factory::EventFactory};
823    use ruma::{
824        EventId, event_id,
825        events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent},
826        room_id,
827        serde::Raw,
828        user_id,
829    };
830    use serde_json::json;
831    use strass::assert_let;
832    use tokio::task::yield_now;
833
834    use super::{
835        super::{super::TimelineVectorDiffs, pagination::LoadMoreEventsBackwardsOutcome},
836        MaybeReceiptEventContent, RoomEventCache, RoomEventCacheGenericUpdate,
837        RoomEventCacheUpdate,
838    };
839    use crate::{assert_let_timeout, test_utils::client::MockClientBuilder};
840
841    #[async_test]
842    async fn test_write_to_storage() {
843        let room_id = room_id!("!galette:saucisse.bzh");
844        let event_id_0 = event_id!("$ev0");
845        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
846
847        let event_cache_store = Arc::new(MemoryStore::new());
848
849        let client = MockClientBuilder::new(None)
850            .on_builder(|builder| {
851                builder.store_config(
852                    StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
853                        .event_cache_store(event_cache_store.clone()),
854                )
855            })
856            .build()
857            .await;
858
859        let event_cache = client.event_cache().unwrap();
860
861        client.base_client().get_or_create_room(room_id, RoomState::Joined);
862
863        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
864        let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
865
866        // Propagate an update for a message and a prev-batch token.
867        let timeline = Timeline {
868            limited: true,
869            prev_batch: Some("raclette".to_owned()),
870            events: vec![f.text_msg("hey yo").event_id(event_id_0).into_event()],
871        };
872
873        room_event_cache
874            .handle_joined_room_update(
875                timeline,
876                MaybeReceiptEventContent::none(),
877                Default::default(),
878                Default::default(),
879                Default::default(),
880            )
881            .await
882            .unwrap();
883
884        // Just checking the generic update is correct.
885        assert_matches!(
886            generic_stream.recv().await,
887            Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
888                assert_eq!(expected_room_id, room_id);
889            }
890        );
891        assert!(generic_stream.is_empty());
892
893        // Check the storage.
894        let linked_chunk = from_all_chunks::<3, _, _>(
895            event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(),
896        )
897        .unwrap()
898        .unwrap();
899
900        assert_eq!(linked_chunk.chunks().count(), 2);
901
902        let mut chunks = linked_chunk.chunks();
903
904        // We start with the gap.
905        assert_matches!(chunks.next().unwrap().content(), ChunkContent::Gap(gap) => {
906            assert_eq!(gap.token, "raclette");
907        });
908
909        // Then we have the stored event.
910        assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
911            assert_eq!(events.len(), 1);
912            assert_eq!(events[0].event_id(), Some(event_id_0));
913        });
914
915        // That's all, folks!
916        assert!(chunks.next().is_none());
917    }
918
919    #[async_test]
920    async fn test_write_to_storage_strips_bundled_relations() {
921        let room_id = room_id!("!galette:saucisse.bzh");
922        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
923
924        let event_cache_store = Arc::new(MemoryStore::new());
925
926        let client = MockClientBuilder::new(None)
927            .on_builder(|builder| {
928                builder.store_config(
929                    StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
930                        .event_cache_store(event_cache_store.clone()),
931                )
932            })
933            .build()
934            .await;
935
936        let event_cache = client.event_cache().unwrap();
937
938        client.base_client().get_or_create_room(room_id, RoomState::Joined);
939
940        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
941        let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
942
943        // Propagate an update for a message with bundled relations.
944        let ev = f
945            .text_msg("hey yo")
946            .sender(User::Alice)
947            .with_bundled_edit(f.text_msg("Hello, Kind Sir").sender(User::Alice))
948            .into_event();
949
950        let timeline = Timeline { limited: false, prev_batch: None, events: vec![ev] };
951
952        room_event_cache
953            .handle_joined_room_update(
954                timeline,
955                MaybeReceiptEventContent::none(),
956                Default::default(),
957                Default::default(),
958                Default::default(),
959            )
960            .await
961            .unwrap();
962
963        // Just checking the generic update is correct.
964        assert_matches!(
965            generic_stream.recv().await,
966            Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
967                assert_eq!(expected_room_id, room_id);
968            }
969        );
970        assert!(generic_stream.is_empty());
971
972        // The in-memory linked chunk keeps the bundled relation.
973        {
974            let events = room_event_cache.events().await.unwrap();
975
976            assert_eq!(events.len(), 1);
977
978            let ev = events[0].raw().deserialize().unwrap();
979            assert_let!(
980                AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = ev
981            );
982
983            let original = msg.as_original().unwrap();
984            assert_eq!(original.content.body(), "hey yo");
985            assert!(original.unsigned.relations.replace.is_some());
986        }
987
988        // The one in storage does not.
989        let linked_chunk = from_all_chunks::<3, _, _>(
990            event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(),
991        )
992        .unwrap()
993        .unwrap();
994
995        assert_eq!(linked_chunk.chunks().count(), 1);
996
997        let mut chunks = linked_chunk.chunks();
998        assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
999            assert_eq!(events.len(), 1);
1000
1001            let ev = events[0].raw().deserialize().unwrap();
1002            assert_let!(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = ev);
1003
1004            let original = msg.as_original().unwrap();
1005            assert_eq!(original.content.body(), "hey yo");
1006            assert!(original.unsigned.relations.replace.is_none());
1007        });
1008
1009        // That's all, folks!
1010        assert!(chunks.next().is_none());
1011    }
1012
1013    #[async_test]
1014    async fn test_clear() {
1015        let room_id = room_id!("!galette:saucisse.bzh");
1016        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
1017
1018        let event_id1 = event_id!("$1");
1019        let event_id2 = event_id!("$2");
1020
1021        let ev1 = f.text_msg("hello world").event_id(event_id1).into_event();
1022        let ev2 = f.text_msg("how's it going").event_id(event_id2).into_event();
1023
1024        // Prefill the store with some data.
1025        let event_cache_store = Arc::new(MemoryStore::new());
1026        event_cache_store
1027            .handle_linked_chunk_updates(
1028                LinkedChunkId::Room(room_id),
1029                vec![
1030                    // An empty items chunk.
1031                    Update::NewItemsChunk {
1032                        previous: None,
1033                        new: ChunkIdentifier::new(0),
1034                        next: None,
1035                    },
1036                    // A gap chunk.
1037                    Update::NewGapChunk {
1038                        previous: Some(ChunkIdentifier::new(0)),
1039                        // Chunk IDs aren't supposed to be ordered, so use a random value here.
1040                        new: ChunkIdentifier::new(42),
1041                        next: None,
1042                        gap: Gap { token: "comté".to_owned() },
1043                    },
1044                    // Another items chunk, non-empty this time.
1045                    Update::NewItemsChunk {
1046                        previous: Some(ChunkIdentifier::new(42)),
1047                        new: ChunkIdentifier::new(1),
1048                        next: None,
1049                    },
1050                    Update::PushItems {
1051                        at: Position::new(ChunkIdentifier::new(1), 0),
1052                        items: vec![ev1.clone()],
1053                    },
1054                    // And another items chunk, non-empty again.
1055                    Update::NewItemsChunk {
1056                        previous: Some(ChunkIdentifier::new(1)),
1057                        new: ChunkIdentifier::new(2),
1058                        next: None,
1059                    },
1060                    Update::PushItems {
1061                        at: Position::new(ChunkIdentifier::new(2), 0),
1062                        items: vec![ev2.clone()],
1063                    },
1064                ],
1065            )
1066            .await
1067            .unwrap();
1068
1069        let client = MockClientBuilder::new(None)
1070            .on_builder(|builder| {
1071                builder.store_config(
1072                    StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
1073                        .event_cache_store(event_cache_store.clone()),
1074                )
1075            })
1076            .build()
1077            .await;
1078
1079        let event_cache = client.event_cache().unwrap();
1080
1081        client.base_client().get_or_create_room(room_id, RoomState::Joined);
1082
1083        let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1084
1085        let (items, mut stream) = room_event_cache.subscribe().await.unwrap();
1086        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1087
1088        // The room knows about all cached events.
1089        {
1090            assert!(room_event_cache.find_event(event_id1).await.unwrap().is_some());
1091            assert!(room_event_cache.find_event(event_id2).await.unwrap().is_some());
1092        }
1093
1094        // But only part of events are loaded from the store
1095        {
1096            // The room must contain only one event because only one chunk has
1097            // been loaded.
1098            assert_eq!(items.len(), 1);
1099            assert_eq!(items[0].event_id().unwrap(), event_id2);
1100
1101            assert!(stream.is_empty());
1102        }
1103
1104        // Let's load more chunks to load all events.
1105        {
1106            room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1107
1108            assert_let_timeout!(
1109                Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1110                    stream.recv()
1111            );
1112            assert_eq!(diffs.len(), 1);
1113            assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
1114                // Here you are `event_id1`!
1115                assert_eq!(event.event_id().unwrap(), event_id1);
1116            });
1117
1118            assert!(stream.is_empty());
1119
1120            assert_let_timeout!(
1121                Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) =
1122                    generic_stream.recv()
1123            );
1124            assert_eq!(room_id, expected_room_id);
1125            assert!(generic_stream.is_empty());
1126        }
1127
1128        // After clearing,…
1129        event_cache.clear_all_rooms().await.unwrap();
1130
1131        //… we get an update that the content has been cleared.
1132        assert_let_timeout!(
1133            Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1134                stream.recv()
1135        );
1136        assert_eq!(diffs.len(), 1);
1137        assert_let!(VectorDiff::Clear = &diffs[0]);
1138
1139        // … same with a generic update.
1140        assert_let_timeout!(
1141            Ok(RoomEventCacheGenericUpdate { room_id: received_room_id }) = generic_stream.recv()
1142        );
1143        assert_eq!(received_room_id, room_id);
1144        assert!(generic_stream.is_empty());
1145
1146        // Events are forgotten by the event cache, after clearing a room.
1147        assert!(room_event_cache.find_event(event_id1).await.unwrap().is_none());
1148
1149        // And their presence in a linked chunk is forgotten.
1150        let items = room_event_cache.events().await.unwrap();
1151        assert!(items.is_empty());
1152
1153        // The event cache store is fully empty.
1154        assert!(
1155            event_cache_store
1156                .load_all_chunks(LinkedChunkId::Room(room_id))
1157                .await
1158                .unwrap()
1159                .is_empty()
1160        );
1161    }
1162
1163    #[async_test]
1164    async fn test_load_from_storage() {
1165        let room_id = room_id!("!galette:saucisse.bzh");
1166        let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
1167
1168        let event_id1 = event_id!("$1");
1169        let event_id2 = event_id!("$2");
1170
1171        let ev1 = f.text_msg("hello world").sender(User::Alice).event_id(event_id1).into_event();
1172        let ev2 = f.text_msg("how's it going").sender(User::Bob).event_id(event_id2).into_event();
1173
1174        // Prefill the store with some data.
1175        let event_cache_store = Arc::new(MemoryStore::new());
1176        event_cache_store
1177            .handle_linked_chunk_updates(
1178                LinkedChunkId::Room(room_id),
1179                vec![
1180                    // An empty items chunk.
1181                    Update::NewItemsChunk {
1182                        previous: None,
1183                        new: ChunkIdentifier::new(0),
1184                        next: None,
1185                    },
1186                    // A gap chunk.
1187                    Update::NewGapChunk {
1188                        previous: Some(ChunkIdentifier::new(0)),
1189                        // Chunk IDs aren't supposed to be ordered, so use a random value here.
1190                        new: ChunkIdentifier::new(42),
1191                        next: None,
1192                        gap: Gap { token: "cheddar".to_owned() },
1193                    },
1194                    // Another items chunk, non-empty this time.
1195                    Update::NewItemsChunk {
1196                        previous: Some(ChunkIdentifier::new(42)),
1197                        new: ChunkIdentifier::new(1),
1198                        next: None,
1199                    },
1200                    Update::PushItems {
1201                        at: Position::new(ChunkIdentifier::new(1), 0),
1202                        items: vec![ev1.clone()],
1203                    },
1204                    // And another items chunk, non-empty again.
1205                    Update::NewItemsChunk {
1206                        previous: Some(ChunkIdentifier::new(1)),
1207                        new: ChunkIdentifier::new(2),
1208                        next: None,
1209                    },
1210                    Update::PushItems {
1211                        at: Position::new(ChunkIdentifier::new(2), 0),
1212                        items: vec![ev2.clone()],
1213                    },
1214                ],
1215            )
1216            .await
1217            .unwrap();
1218
1219        let client = MockClientBuilder::new(None)
1220            .on_builder(|builder| {
1221                builder.store_config(
1222                    StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
1223                        .event_cache_store(event_cache_store.clone()),
1224                )
1225            })
1226            .build()
1227            .await;
1228
1229        let event_cache = client.event_cache().unwrap();
1230
1231        // Let's check whether the generic updates are received for the
1232        // initialisation.
1233        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1234
1235        client.base_client().get_or_create_room(room_id, RoomState::Joined);
1236
1237        let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1238
1239        // The room event cache has been loaded. A generic update must have been
1240        // triggered.
1241        assert_matches!(
1242            generic_stream.recv().await,
1243            Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1244                assert_eq!(room_id, expected_room_id);
1245            }
1246        );
1247        assert!(generic_stream.is_empty());
1248
1249        let (items, mut stream) = room_event_cache.subscribe().await.unwrap();
1250
1251        // The initial items contain one event because only the last chunk is
1252        // loaded by default.
1253        assert_eq!(items.len(), 1);
1254        assert_eq!(items[0].event_id().unwrap(), event_id2);
1255        assert!(stream.is_empty());
1256
1257        // The event cache knows only all events though, even if they aren't
1258        // loaded.
1259        assert!(room_event_cache.find_event(event_id1).await.unwrap().is_some());
1260        assert!(room_event_cache.find_event(event_id2).await.unwrap().is_some());
1261
1262        // Let's paginate to load more events.
1263        room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1264
1265        assert_let_timeout!(
1266            Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1267                stream.recv()
1268        );
1269        assert_eq!(diffs.len(), 1);
1270        assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
1271            assert_eq!(event.event_id().unwrap(), event_id1);
1272        });
1273
1274        assert!(stream.is_empty());
1275
1276        // A generic update is triggered too.
1277        assert_matches!(
1278            generic_stream.recv().await,
1279            Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1280                assert_eq!(expected_room_id, room_id);
1281            }
1282        );
1283        assert!(generic_stream.is_empty());
1284
1285        // A new update with one of these events leads to deduplication.
1286        let timeline = Timeline { limited: false, prev_batch: None, events: vec![ev2] };
1287
1288        room_event_cache
1289            .handle_joined_room_update(
1290                timeline,
1291                MaybeReceiptEventContent::none(),
1292                Default::default(),
1293                Default::default(),
1294                Default::default(),
1295            )
1296            .await
1297            .unwrap();
1298
1299        // Just checking the generic update is correct. There is a duplicate
1300        // event, so no generic changes whatsoever!
1301        assert!(generic_stream.recv().now_or_never().is_none());
1302
1303        // The stream doesn't report these changes _yet_. Use the items vector
1304        // given when subscribing, to check that the items correspond to their
1305        // new positions. The duplicated item is removed (so it's not the first
1306        // element anymore), and it's added to the back of the list.
1307        let items = room_event_cache.events().await.unwrap();
1308        assert_eq!(items.len(), 2);
1309        assert_eq!(items[0].event_id().unwrap(), event_id1);
1310        assert_eq!(items[1].event_id().unwrap(), event_id2);
1311    }
1312
1313    #[async_test]
1314    async fn test_load_from_storage_resilient_to_failure() {
1315        let room_id = room_id!("!fondue:patate.ch");
1316
1317        let event = EventFactory::new()
1318            .room(room_id)
1319            .sender(user_id!("@ben:saucisse.bzh"))
1320            .text_msg("foo")
1321            .event_id(event_id!("$42"))
1322            .into_event();
1323
1324        // Prefill the store with invalid data: two chunks that form a cycle.
1325        let event_cache_store = Arc::new(MemoryStore::new());
1326        event_cache_store
1327            .handle_linked_chunk_updates(
1328                LinkedChunkId::Room(room_id),
1329                vec![
1330                    Update::NewItemsChunk {
1331                        previous: None,
1332                        new: ChunkIdentifier::new(0),
1333                        next: None,
1334                    },
1335                    Update::PushItems {
1336                        at: Position::new(ChunkIdentifier::new(0), 0),
1337                        items: vec![event],
1338                    },
1339                    Update::NewItemsChunk {
1340                        previous: Some(ChunkIdentifier::new(0)),
1341                        new: ChunkIdentifier::new(1),
1342                        next: Some(ChunkIdentifier::new(0)),
1343                    },
1344                ],
1345            )
1346            .await
1347            .unwrap();
1348
1349        let client = MockClientBuilder::new(None)
1350            .on_builder(|builder| {
1351                builder.store_config(
1352                    StoreConfig::new(CrossProcessLockConfig::multi_process("holder"))
1353                        .event_cache_store(event_cache_store.clone()),
1354                )
1355            })
1356            .build()
1357            .await;
1358
1359        client.base_client().get_or_create_room(room_id, RoomState::Joined);
1360        let event_cache = client.event_cache().unwrap();
1361
1362        let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1363
1364        let items = room_event_cache.events().await.unwrap();
1365
1366        // Because the persisted content was invalid, the room store is reset:
1367        // there are no events in the cache.
1368        assert!(items.is_empty());
1369
1370        // Storage doesn't contain anything. It would also be valid that it
1371        // contains a single initial empty items chunk.
1372        let raw_chunks =
1373            event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap();
1374        assert!(raw_chunks.is_empty());
1375    }
1376
1377    #[async_test]
1378    async fn test_no_useless_gaps() {
1379        let room_id = room_id!("!galette:saucisse.bzh");
1380
1381        let client = MockClientBuilder::new(None).build().await;
1382
1383        let event_cache = client.event_cache().unwrap();
1384
1385        client.base_client().get_or_create_room(room_id, RoomState::Joined);
1386        let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1387        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1388
1389        let f = EventFactory::new().room(room_id).sender(User::Alice);
1390
1391        // Propagate an update including a limited timeline with one message and
1392        // a prev-batch token.
1393        room_event_cache
1394            .handle_joined_room_update(
1395                Timeline {
1396                    limited: true,
1397                    prev_batch: Some("raclette".to_owned()),
1398                    events: vec![f.text_msg("hey yo").into_event()],
1399                },
1400                MaybeReceiptEventContent::none(),
1401                Default::default(),
1402                Default::default(),
1403                Default::default(),
1404            )
1405            .await
1406            .unwrap();
1407
1408        // Just checking the generic update is correct.
1409        assert_matches!(
1410            generic_stream.recv().await,
1411            Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1412                assert_eq!(expected_room_id, room_id);
1413            }
1414        );
1415        assert!(generic_stream.is_empty());
1416
1417        {
1418            let state = room_event_cache.inner.state.read().await.unwrap();
1419
1420            let mut num_gaps = 0;
1421            let mut num_events = 0;
1422
1423            for c in state.room_linked_chunk().chunks() {
1424                match c.content() {
1425                    ChunkContent::Items(items) => num_events += items.len(),
1426                    ChunkContent::Gap(_) => num_gaps += 1,
1427                }
1428            }
1429
1430            // The limited sync unloads the chunk, so it will appear as if there
1431            // are only the events.
1432            assert_eq!(num_gaps, 0);
1433            assert_eq!(num_events, 1);
1434        }
1435
1436        // But if I manually reload more of the chunk, the gap will be present.
1437        assert_matches!(
1438            room_event_cache.pagination().load_more_events_backwards().await.unwrap(),
1439            LoadMoreEventsBackwardsOutcome::Gap { .. }
1440        );
1441
1442        {
1443            let state = room_event_cache.inner.state.read().await.unwrap();
1444
1445            let mut num_gaps = 0;
1446            let mut num_events = 0;
1447
1448            for c in state.room_linked_chunk().chunks() {
1449                match c.content() {
1450                    ChunkContent::Items(items) => num_events += items.len(),
1451                    ChunkContent::Gap(_) => num_gaps += 1,
1452                }
1453            }
1454
1455            // The gap must have been stored.
1456            assert_eq!(num_gaps, 1);
1457            assert_eq!(num_events, 1);
1458        }
1459
1460        // Now, propagate an update for another message, but the timeline isn't
1461        // limited this time.
1462        room_event_cache
1463            .handle_joined_room_update(
1464                Timeline {
1465                    limited: false,
1466                    prev_batch: Some("fondue".to_owned()),
1467                    events: vec![f.text_msg("sup").into_event()],
1468                },
1469                MaybeReceiptEventContent::none(),
1470                Default::default(),
1471                Default::default(),
1472                Default::default(),
1473            )
1474            .await
1475            .unwrap();
1476
1477        // Just checking the generic update is correct.
1478        assert_matches!(
1479            generic_stream.recv().await,
1480            Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1481                assert_eq!(expected_room_id, room_id);
1482            }
1483        );
1484        assert!(generic_stream.is_empty());
1485
1486        {
1487            let state = room_event_cache.inner.state.read().await.unwrap();
1488
1489            let mut num_gaps = 0;
1490            let mut num_events = 0;
1491
1492            for c in state.room_linked_chunk().chunks() {
1493                match c.content() {
1494                    ChunkContent::Items(items) => num_events += items.len(),
1495                    ChunkContent::Gap(gap) => {
1496                        assert_eq!(gap.token, "raclette");
1497                        num_gaps += 1;
1498                    }
1499                }
1500            }
1501
1502            // There's only the previous gap, no new ones.
1503            assert_eq!(num_gaps, 1);
1504            assert_eq!(num_events, 2);
1505        }
1506    }
1507
1508    #[async_test]
1509    async fn test_shrink_to_last_chunk() {
1510        let room_id = room_id!("!galette:saucisse.bzh");
1511
1512        let f = EventFactory::new().room(room_id);
1513
1514        let evid1 = event_id!("$1");
1515        let evid2 = event_id!("$2");
1516
1517        let ev1 = f.text_msg("hello world").sender(User::Alice).event_id(evid1).into_event();
1518        let ev2 = f.text_msg("howdy").sender(User::Bob).event_id(evid2).into_event();
1519
1520        // Fill the event cache store with an initial linked chunk with 2 events
1521        // chunks.
1522        let event_cache_store = MemoryStore::new();
1523        event_cache_store
1524            .handle_linked_chunk_updates(
1525                LinkedChunkId::Room(room_id),
1526                vec![
1527                    Update::NewItemsChunk {
1528                        previous: None,
1529                        new: ChunkIdentifier::new(0),
1530                        next: None,
1531                    },
1532                    Update::PushItems {
1533                        at: Position::new(ChunkIdentifier::new(0), 0),
1534                        items: vec![ev1],
1535                    },
1536                    Update::NewItemsChunk {
1537                        previous: Some(ChunkIdentifier::new(0)),
1538                        new: ChunkIdentifier::new(1),
1539                        next: None,
1540                    },
1541                    Update::PushItems {
1542                        at: Position::new(ChunkIdentifier::new(1), 0),
1543                        items: vec![ev2],
1544                    },
1545                ],
1546            )
1547            .await
1548            .unwrap();
1549
1550        let client = MockClientBuilder::new(None)
1551            .on_builder(move |builder| {
1552                builder.store_config(
1553                    StoreConfig::new(CrossProcessLockConfig::MultiProcess {
1554                        holder_name: "foo".to_owned(),
1555                    })
1556                    .event_cache_store(event_cache_store),
1557                )
1558            })
1559            .build()
1560            .await;
1561
1562        let event_cache = client.event_cache().unwrap();
1563
1564        client.base_client().get_or_create_room(room_id, RoomState::Joined);
1565        let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1566
1567        // Sanity check: lazily loaded, so only includes one item at start.
1568        let (events, mut stream) = room_event_cache.subscribe().await.unwrap();
1569        assert_eq!(events.len(), 1);
1570        assert_eq!(events[0].event_id(), Some(evid2));
1571        assert!(stream.is_empty());
1572
1573        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1574
1575        // Force loading the full linked chunk by back-paginating.
1576        let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1577        assert_eq!(outcome.events.len(), 1);
1578        assert_eq!(outcome.events[0].event_id(), Some(evid1));
1579        assert!(outcome.reached_start);
1580
1581        // We also get an update about the loading from the store.
1582        assert_let_timeout!(
1583            Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1584                stream.recv()
1585        );
1586        assert_eq!(diffs.len(), 1);
1587        assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1588            assert_eq!(value.event_id(), Some(evid1));
1589        });
1590
1591        assert!(stream.is_empty());
1592
1593        // Same for the generic update.
1594        assert_let_timeout!(
1595            Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1596        );
1597        assert_eq!(expected_room_id, room_id);
1598        assert!(generic_stream.is_empty());
1599
1600        // Shrink the linked chunk to the last chunk.
1601        room_event_cache
1602            .inner
1603            .state
1604            .reload_no_preprocessing()
1605            .await
1606            .expect("shrinking should succeed");
1607
1608        // We receive updates about the changes to the linked chunk.
1609        assert_let_timeout!(
1610            Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1611                stream.recv()
1612        );
1613        assert_eq!(diffs.len(), 2);
1614        assert_matches!(&diffs[0], VectorDiff::Clear);
1615        assert_matches!(&diffs[1], VectorDiff::Append { values} => {
1616            assert_eq!(values.len(), 1);
1617            assert_eq!(values[0].event_id(), Some(evid2));
1618        });
1619
1620        assert!(stream.is_empty());
1621
1622        // A generic update has been received.
1623        assert_let_timeout!(Ok(RoomEventCacheGenericUpdate { .. }) = generic_stream.recv());
1624        assert!(generic_stream.is_empty());
1625
1626        // When reading the events, we do get only the last one.
1627        let events = room_event_cache.events().await.unwrap();
1628        assert_eq!(events.len(), 1);
1629        assert_eq!(events[0].event_id(), Some(evid2));
1630
1631        // But if we back-paginate, we don't need access to network to find out
1632        // about the previous event.
1633        let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1634        assert_eq!(outcome.events.len(), 1);
1635        assert_eq!(outcome.events[0].event_id(), Some(evid1));
1636        assert!(outcome.reached_start);
1637    }
1638
1639    #[async_test]
1640    async fn test_shrink_when_loading_the_last_chunk_fails() {
1641        let room_id = room_id!("!galette:saucisse.bzh");
1642
1643        let f = EventFactory::new().room(room_id);
1644
1645        let ev1 =
1646            f.text_msg("hello world").sender(User::Alice).event_id(event_id!("$1")).into_event();
1647        let ev2 = f.text_msg("howdy").sender(User::Bob).event_id(event_id!("$2")).into_event();
1648
1649        // Fill the event cache store with an initial linked chunk with 2 events
1650        // chunks.
1651        let event_cache_store = ControlledMemoryStore::new();
1652        event_cache_store
1653            .handle_linked_chunk_updates(
1654                LinkedChunkId::Room(room_id),
1655                vec![
1656                    Update::NewItemsChunk {
1657                        previous: None,
1658                        new: ChunkIdentifier::new(0),
1659                        next: None,
1660                    },
1661                    Update::PushItems {
1662                        at: Position::new(ChunkIdentifier::new(0), 0),
1663                        items: vec![ev1],
1664                    },
1665                    Update::NewItemsChunk {
1666                        previous: Some(ChunkIdentifier::new(0)),
1667                        new: ChunkIdentifier::new(1),
1668                        next: None,
1669                    },
1670                    Update::PushItems {
1671                        at: Position::new(ChunkIdentifier::new(1), 0),
1672                        items: vec![ev2],
1673                    },
1674                ],
1675            )
1676            .await
1677            .unwrap();
1678
1679        let client = MockClientBuilder::new(None)
1680            .on_builder({
1681                let event_cache_store = event_cache_store.clone();
1682
1683                move |builder| {
1684                    builder.store_config(
1685                        StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
1686                            .event_cache_store(event_cache_store),
1687                    )
1688                }
1689            })
1690            .build()
1691            .await;
1692
1693        let event_cache = client.event_cache().unwrap();
1694
1695        client.base_client().get_or_create_room(room_id, RoomState::Joined);
1696        let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1697
1698        let events = room_event_cache.events().await.unwrap();
1699        assert_eq!(events.len(), 1);
1700
1701        // Shrink while loading the last chunk fails: the chunks' metadata are
1702        // loaded, but the storage is then cleared.
1703        event_cache_store.fail_next_last_chunk_load();
1704        room_event_cache
1705            .inner
1706            .state
1707            .reload_no_preprocessing()
1708            .await
1709            .expect("shrinking should succeed");
1710
1711        // Reading the events lazily creates a new first chunk.
1712        assert!(room_event_cache.events().await.unwrap().is_empty());
1713
1714        event_cache_store.stop_failing_next_last_chunk_load();
1715
1716        // Shrinking again must not panic: the order tracker must not have kept
1717        // the metadata of the chunks that have been cleared.
1718        room_event_cache
1719            .inner
1720            .state
1721            .reload_no_preprocessing()
1722            .await
1723            .expect("shrinking should succeed");
1724
1725        assert!(room_event_cache.events().await.unwrap().is_empty());
1726    }
1727
1728    #[async_test]
1729    async fn test_room_ordering() {
1730        let room_id = room_id!("!galette:saucisse.bzh");
1731
1732        let f = EventFactory::new().room(room_id).sender(User::Alice);
1733
1734        let evid1 = event_id!("$1");
1735        let evid2 = event_id!("$2");
1736        let evid3 = event_id!("$3");
1737
1738        let ev1 = f.text_msg("hello world").event_id(evid1).into_event();
1739        let ev2 = f.text_msg("howdy").sender(User::Bob).event_id(evid2).into_event();
1740        let ev3 = f.text_msg("yo").event_id(evid3).into_event();
1741
1742        // Fill the event cache store with an initial linked chunk with 2 events
1743        // chunks.
1744        let event_cache_store = MemoryStore::new();
1745        event_cache_store
1746            .handle_linked_chunk_updates(
1747                LinkedChunkId::Room(room_id),
1748                vec![
1749                    Update::NewItemsChunk {
1750                        previous: None,
1751                        new: ChunkIdentifier::new(0),
1752                        next: None,
1753                    },
1754                    Update::PushItems {
1755                        at: Position::new(ChunkIdentifier::new(0), 0),
1756                        items: vec![ev1, ev2],
1757                    },
1758                    Update::NewItemsChunk {
1759                        previous: Some(ChunkIdentifier::new(0)),
1760                        new: ChunkIdentifier::new(1),
1761                        next: None,
1762                    },
1763                    Update::PushItems {
1764                        at: Position::new(ChunkIdentifier::new(1), 0),
1765                        items: vec![ev3.clone()],
1766                    },
1767                ],
1768            )
1769            .await
1770            .unwrap();
1771
1772        let client = MockClientBuilder::new(None)
1773            .on_builder(move |builder| {
1774                builder.store_config(
1775                    StoreConfig::new(CrossProcessLockConfig::MultiProcess {
1776                        holder_name: "foo".to_owned(),
1777                    })
1778                    .event_cache_store(event_cache_store),
1779                )
1780            })
1781            .build()
1782            .await;
1783
1784        client.base_client().get_or_create_room(room_id, RoomState::Joined);
1785        let event_cache = client.event_cache().unwrap();
1786
1787        let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1788
1789        // Initially, the linked chunk only contains the last chunk, so only ev3
1790        // is loaded.
1791        {
1792            let state = room_event_cache.inner.state.read().await.unwrap();
1793            let room_linked_chunk = state.room_linked_chunk();
1794
1795            // But we can get the order of ev1.
1796            assert_eq!(
1797                room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 0)),
1798                Some(0)
1799            );
1800
1801            // And that of ev2 as well.
1802            assert_eq!(
1803                room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 1)),
1804                Some(1)
1805            );
1806
1807            // ev3, which is loaded, also has a known ordering.
1808            let mut events = room_linked_chunk.events();
1809            let (pos, ev) = events.next().unwrap();
1810            assert_eq!(pos, Position::new(ChunkIdentifier::new(1), 0));
1811            assert_eq!(ev.event_id(), Some(evid3));
1812            assert_eq!(room_linked_chunk.event_order(pos), Some(2));
1813
1814            // No other loaded events.
1815            assert!(events.next().is_none());
1816        }
1817
1818        // Force loading the full linked chunk by back-paginating.
1819        let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1820        assert!(outcome.reached_start);
1821
1822        // All events are now loaded, so their order is precisely their
1823        // enumerated index in a linear iteration.
1824        {
1825            let state = room_event_cache.inner.state.read().await.unwrap();
1826            let room_linked_chunk = state.room_linked_chunk();
1827
1828            for (i, (pos, _)) in room_linked_chunk.events().enumerate() {
1829                assert_eq!(room_linked_chunk.event_order(pos), Some(i));
1830            }
1831        }
1832
1833        // Handle a gappy sync with two events (including one duplicate, so
1834        // deduplication kicks in), so that the linked chunk is shrunk to the
1835        // last chunk, and that the linked chunk only contains the last two
1836        // events.
1837        let evid4 = event_id!("$4");
1838        room_event_cache
1839            .handle_joined_room_update(
1840                Timeline {
1841                    limited: true,
1842                    prev_batch: Some("fondue".to_owned()),
1843                    events: vec![ev3, f.text_msg("sup").event_id(evid4).into_event()],
1844                },
1845                MaybeReceiptEventContent::none(),
1846                Default::default(),
1847                Default::default(),
1848                Default::default(),
1849            )
1850            .await
1851            .unwrap();
1852
1853        {
1854            let state = room_event_cache.inner.state.read().await.unwrap();
1855            let room_linked_chunk = state.room_linked_chunk();
1856
1857            // After the shrink, only evid3 and evid4 are loaded.
1858            let mut events = room_linked_chunk.events();
1859
1860            let (pos, ev) = events.next().unwrap();
1861            assert_eq!(ev.event_id(), Some(evid3));
1862            assert_eq!(room_linked_chunk.event_order(pos), Some(2));
1863
1864            let (pos, ev) = events.next().unwrap();
1865            assert_eq!(ev.event_id(), Some(evid4));
1866            assert_eq!(room_linked_chunk.event_order(pos), Some(3));
1867
1868            // No other loaded events.
1869            assert!(events.next().is_none());
1870
1871            // But we can still get the order of previous events.
1872            assert_eq!(
1873                room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 0)),
1874                Some(0)
1875            );
1876            assert_eq!(
1877                room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 1)),
1878                Some(1)
1879            );
1880
1881            // ev3 doesn't have an order with its previous position, since it's
1882            // been deduplicated.
1883            assert_eq!(
1884                room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(1), 0)),
1885                None
1886            );
1887        }
1888    }
1889
1890    #[async_test]
1891    async fn test_auto_shrink_after_all_subscribers_are_gone() {
1892        let room_id = room_id!("!galette:saucisse.bzh");
1893
1894        let f = EventFactory::new().room(room_id);
1895
1896        let evid1 = event_id!("$1");
1897        let evid2 = event_id!("$2");
1898
1899        let ev1 = f.text_msg("hello world").sender(User::Alice).event_id(evid1).into_event();
1900        let ev2 = f.text_msg("howdy").sender(User::Bob).event_id(evid2).into_event();
1901
1902        // Fill the event cache store with an initial linked chunk with 2 events
1903        // chunks.
1904        let event_cache_store = MemoryStore::new();
1905        event_cache_store
1906            .handle_linked_chunk_updates(
1907                LinkedChunkId::Room(room_id),
1908                vec![
1909                    Update::NewItemsChunk {
1910                        previous: None,
1911                        new: ChunkIdentifier::new(0),
1912                        next: None,
1913                    },
1914                    Update::PushItems {
1915                        at: Position::new(ChunkIdentifier::new(0), 0),
1916                        items: vec![ev1],
1917                    },
1918                    Update::NewItemsChunk {
1919                        previous: Some(ChunkIdentifier::new(0)),
1920                        new: ChunkIdentifier::new(1),
1921                        next: None,
1922                    },
1923                    Update::PushItems {
1924                        at: Position::new(ChunkIdentifier::new(1), 0),
1925                        items: vec![ev2],
1926                    },
1927                ],
1928            )
1929            .await
1930            .unwrap();
1931
1932        let client = MockClientBuilder::new(None)
1933            .on_builder(move |builder| {
1934                builder.store_config(
1935                    StoreConfig::new(CrossProcessLockConfig::MultiProcess {
1936                        holder_name: "foo".to_owned(),
1937                    })
1938                    .event_cache_store(event_cache_store),
1939                )
1940            })
1941            .build()
1942            .await;
1943
1944        let event_cache = client.event_cache().unwrap();
1945
1946        client.base_client().get_or_create_room(room_id, RoomState::Joined);
1947        let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1948
1949        // Sanity check: lazily loaded, so only includes one item at start.
1950        let (events1, mut stream1) = room_event_cache.subscribe().await.unwrap();
1951        assert_eq!(events1.len(), 1);
1952        assert_eq!(events1[0].event_id(), Some(evid2));
1953        assert!(stream1.is_empty());
1954
1955        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1956
1957        // Force loading the full linked chunk by back-paginating.
1958        let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1959        assert_eq!(outcome.events.len(), 1);
1960        assert_eq!(outcome.events[0].event_id(), Some(evid1));
1961        assert!(outcome.reached_start);
1962
1963        // We also get an update about the loading from the store. Ignore it,
1964        // for this test's sake.
1965        assert_let_timeout!(
1966            Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1967                stream1.recv()
1968        );
1969        assert_eq!(diffs.len(), 1);
1970        assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1971            assert_eq!(value.event_id(), Some(evid1));
1972        });
1973
1974        assert!(stream1.is_empty());
1975
1976        assert_let_timeout!(
1977            Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1978        );
1979        assert_eq!(expected_room_id, room_id);
1980        assert!(generic_stream.is_empty());
1981
1982        // Have another subscriber. Since it's not the first one, and the
1983        // previous one loaded some more events, the second subscribers sees
1984        // them all.
1985        let (events2, stream2) = room_event_cache.subscribe().await.unwrap();
1986        assert_eq!(events2.len(), 2);
1987        assert_eq!(events2[0].event_id(), Some(evid1));
1988        assert_eq!(events2[1].event_id(), Some(evid2));
1989        assert!(stream2.is_empty());
1990
1991        // Grab a receiver for testing no diffs is sent.
1992        let subscriber = {
1993            let state = room_event_cache.inner.state.read().await.unwrap();
1994            state.update_sender.new_room_receiver()
1995        };
1996
1997        // Drop the first stream, and wait a bit.
1998        drop(stream1);
1999        yield_now().await;
2000
2001        // The second stream remains undisturbed.
2002        assert!(stream2.is_empty());
2003
2004        // Now drop the second stream, and wait a bit.
2005        drop(stream2);
2006        yield_now().await;
2007
2008        // The linked chunk must have auto-shrunk by now.
2009
2010        {
2011            // Check the inner state: there's no more shared auto-shrinker.
2012            let state = room_event_cache.inner.state.read().await.unwrap();
2013            assert_eq!(state.subscribers_handle().count(), 0);
2014
2015            // No diff is sent when the linked chunk has auto-shrunk.
2016            assert!(subscriber.is_empty());
2017            assert!(generic_stream.is_empty());
2018        }
2019
2020        // Getting the events will only give us the latest chunk.
2021        let events3 = room_event_cache.events().await.unwrap();
2022        assert_eq!(events3.len(), 1);
2023        assert_eq!(events3[0].event_id(), Some(evid2));
2024    }
2025
2026    #[async_test]
2027    async fn test_rfind_map_event_in_memory_by() {
2028        let user_id = user_id!("@mnt_io:matrix.org");
2029        let room_id = room_id!("!raclette:patate.ch");
2030
2031        let event_factory = EventFactory::new().room(room_id);
2032
2033        let event_id_0 = event_id!("$ev0");
2034        let event_id_1 = event_id!("$ev1");
2035        let event_id_2 = event_id!("$ev2");
2036        let event_id_3 = event_id!("$ev3");
2037
2038        let event_0 =
2039            event_factory.text_msg("hello").sender(User::Bob).event_id(event_id_0).into_event();
2040        let event_1 =
2041            event_factory.text_msg("world").sender(User::Alice).event_id(event_id_1).into_event();
2042        let event_2 =
2043            event_factory.text_msg("!").sender(User::Alice).event_id(event_id_2).into_event();
2044        let event_3 =
2045            event_factory.text_msg("eh!").sender(user_id).event_id(event_id_3).into_event();
2046
2047        // Fill the event cache store with an initial linked chunk of 2 chunks,
2048        // and 4 events.
2049        let event_cache_store = MemoryStore::new();
2050        event_cache_store
2051            .handle_linked_chunk_updates(
2052                LinkedChunkId::Room(room_id),
2053                vec![
2054                    Update::NewItemsChunk {
2055                        previous: None,
2056                        new: ChunkIdentifier::new(0),
2057                        next: None,
2058                    },
2059                    Update::PushItems {
2060                        at: Position::new(ChunkIdentifier::new(0), 0),
2061                        items: vec![event_3],
2062                    },
2063                    Update::NewItemsChunk {
2064                        previous: Some(ChunkIdentifier::new(0)),
2065                        new: ChunkIdentifier::new(1),
2066                        next: None,
2067                    },
2068                    Update::PushItems {
2069                        at: Position::new(ChunkIdentifier::new(1), 0),
2070                        items: vec![event_0, event_1, event_2],
2071                    },
2072                ],
2073            )
2074            .await
2075            .unwrap();
2076
2077        let client = MockClientBuilder::new(None)
2078            .on_builder(move |builder| {
2079                builder.store_config(
2080                    StoreConfig::new(CrossProcessLockConfig::MultiProcess {
2081                        holder_name: "foo".to_owned(),
2082                    })
2083                    .event_cache_store(event_cache_store),
2084                )
2085            })
2086            .build()
2087            .await;
2088
2089        client.base_client().get_or_create_room(room_id, RoomState::Joined);
2090        let event_cache = client.event_cache().unwrap();
2091        let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
2092
2093        // Look for an event from Bob: it must be `event_0`.
2094        assert_matches!(
2095            room_event_cache
2096                .rfind_map_event_in_memory_by(|event| {
2097                    (event.sender().is_some_and(|sender| sender == User::Bob)).then(|| event.event_id().map(ToOwned::to_owned))
2098                })
2099                .await,
2100            Ok(Some(event_id)) => {
2101                assert_eq!(event_id.as_deref(), Some(event_id_0));
2102            }
2103        );
2104
2105        // Look for an event from Alice: it must be `event_2`, right before
2106        // `event_1` because events are looked for in reverse order.
2107        assert_matches!(
2108            room_event_cache
2109                .rfind_map_event_in_memory_by(|event| {
2110                    (event.sender().is_some_and(|sender| sender == User::Alice)).then(|| event.event_id().map(ToOwned::to_owned))
2111                })
2112                .await,
2113            Ok(Some(event_id)) => {
2114                assert_eq!(event_id.as_deref(), Some(event_id_2));
2115            }
2116        );
2117
2118        // Look for an event that is inside the storage, but not loaded.
2119        assert!(
2120            room_event_cache
2121                .rfind_map_event_in_memory_by(|event| {
2122                    (event.sender().as_deref() == Some(user_id))
2123                        .then(|| event.event_id().map(ToOwned::to_owned))
2124                })
2125                .await
2126                .unwrap()
2127                .is_none()
2128        );
2129
2130        // Look for an event that doesn't exist.
2131        assert!(
2132            room_event_cache.rfind_map_event_in_memory_by(|_| None::<()>).await.unwrap().is_none()
2133        );
2134    }
2135
2136    #[async_test]
2137    async fn test_reload_when_dirty() {
2138        let user_id = user_id!("@mnt_io:matrix.org");
2139        let room_id = room_id!("!raclette:patate.ch");
2140
2141        let event_factory = EventFactory::new().room(room_id).sender(user_id);
2142
2143        let ev_id_0 = event_id!("$ev_0");
2144        let ev_id_1 = event_id!("$ev_1");
2145
2146        let ev_0 = event_factory.text_msg("comté").event_id(ev_id_0).into_event();
2147        let ev_1 = event_factory.text_msg("morbier").event_id(ev_id_1).into_event();
2148
2149        // Add events to the storage (shared by the two clients!).
2150        let event_cache_store = MemoryStore::new();
2151        event_cache_store
2152            .handle_linked_chunk_updates(
2153                LinkedChunkId::Room(room_id),
2154                vec![
2155                    Update::NewItemsChunk {
2156                        previous: None,
2157                        new: ChunkIdentifier::new(0),
2158                        next: None,
2159                    },
2160                    Update::PushItems {
2161                        at: Position::new(ChunkIdentifier::new(0), 0),
2162                        items: vec![ev_0],
2163                    },
2164                    Update::NewItemsChunk {
2165                        previous: Some(ChunkIdentifier::new(0)),
2166                        new: ChunkIdentifier::new(1),
2167                        next: None,
2168                    },
2169                    Update::PushItems {
2170                        at: Position::new(ChunkIdentifier::new(1), 0),
2171                        items: vec![ev_1],
2172                    },
2173                ],
2174            )
2175            .await
2176            .unwrap();
2177
2178        // Client for the process 0.
2179        let client_p0 = MockClientBuilder::new(None)
2180            .on_builder(|builder| {
2181                builder.store_config(
2182                    StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
2183                        .event_cache_store(event_cache_store.clone()),
2184                )
2185            })
2186            .build()
2187            .await;
2188
2189        // Client for the process 1.
2190        let client_p1 = MockClientBuilder::new(None)
2191            .on_builder(|builder| {
2192                builder.store_config(
2193                    StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
2194                        .event_cache_store(event_cache_store),
2195                )
2196            })
2197            .build()
2198            .await;
2199
2200        // Subscribe the event caches, and create the room.
2201        let (room_event_cache_p0, room_event_cache_p1) = {
2202            client_p0.base_client().get_or_create_room(room_id, RoomState::Joined);
2203            client_p1.base_client().get_or_create_room(room_id, RoomState::Joined);
2204
2205            let (room_event_cache_p0, _drop_handles) =
2206                client_p0.event_cache().unwrap().room(room_id).await.unwrap();
2207            let (room_event_cache_p1, _drop_handles) =
2208                client_p1.event_cache().unwrap().room(room_id).await.unwrap();
2209
2210            (room_event_cache_p0, room_event_cache_p1)
2211        };
2212
2213        // Okay. We are ready for the test!
2214        //
2215        // First off, let's check `room_event_cache_p0` has access to the first
2216        // event loaded in-memory, then do a pagination, and see more events.
2217        let mut updates_stream_p0 = {
2218            let room_event_cache = &room_event_cache_p0;
2219
2220            let (initial_updates, mut updates_stream) =
2221                room_event_cache_p0.subscribe().await.unwrap();
2222
2223            // Initial updates contain `ev_id_1` only.
2224            assert_eq!(initial_updates.len(), 1);
2225            assert_eq!(initial_updates[0].event_id(), Some(ev_id_1));
2226            assert!(updates_stream.is_empty());
2227
2228            // `ev_id_1` must be loaded in memory.
2229            assert!(event_loaded(room_event_cache, ev_id_1).await);
2230
2231            // `ev_id_0` must NOT be loaded in memory.
2232            assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2233
2234            // Load one more event with a backpagination.
2235            room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2236
2237            // A new update for `ev_id_0` must be present.
2238            assert_matches!(
2239                updates_stream.recv().await.unwrap(),
2240                RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2241                    assert_eq!(diffs.len(), 1, "{diffs:#?}");
2242                    assert_matches!(
2243                        &diffs[0],
2244                        VectorDiff::Insert { index: 0, value: event } => {
2245                            assert_eq!(event.event_id(), Some(ev_id_0));
2246                        }
2247                    );
2248                }
2249            );
2250
2251            // `ev_id_0` must now be loaded in memory.
2252            assert!(event_loaded(room_event_cache, ev_id_0).await);
2253
2254            updates_stream
2255        };
2256
2257        // Second, let's check `room_event_cache_p1` has the same accesses.
2258        let mut updates_stream_p1 = {
2259            let room_event_cache = &room_event_cache_p1;
2260            let (initial_updates, mut updates_stream) =
2261                room_event_cache_p1.subscribe().await.unwrap();
2262
2263            // Initial updates contain `ev_id_1` only.
2264            assert_eq!(initial_updates.len(), 1);
2265            assert_eq!(initial_updates[0].event_id(), Some(ev_id_1));
2266            assert!(updates_stream.is_empty());
2267
2268            // `ev_id_1` must be loaded in memory.
2269            assert!(event_loaded(room_event_cache, ev_id_1).await);
2270
2271            // `ev_id_0` must NOT be loaded in memory.
2272            assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2273
2274            // Load one more event with a backpagination.
2275            room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2276
2277            // A new update for `ev_id_0` must be present.
2278            assert_matches!(
2279                updates_stream.recv().await.unwrap(),
2280                RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2281                    assert_eq!(diffs.len(), 1, "{diffs:#?}");
2282                    assert_matches!(
2283                        &diffs[0],
2284                        VectorDiff::Insert { index: 0, value: event } => {
2285                            assert_eq!(event.event_id(), Some(ev_id_0));
2286                        }
2287                    );
2288                }
2289            );
2290
2291            // `ev_id_0` must now be loaded in memory.
2292            assert!(event_loaded(room_event_cache, ev_id_0).await);
2293
2294            updates_stream
2295        };
2296
2297        // Do this a couple times, for the fun.
2298        for _ in 0..3 {
2299            // Third, because `room_event_cache_p1` has locked the store, the
2300            // lock is dirty for `room_event_cache_p0`, so it will shrink to its
2301            // last chunk!
2302            {
2303                let room_event_cache = &room_event_cache_p0;
2304                let updates_stream = &mut updates_stream_p0;
2305
2306                // `ev_id_1` must be loaded in memory, just like before.
2307                assert!(event_loaded(room_event_cache, ev_id_1).await);
2308
2309                // However, `ev_id_0` must NOT be loaded in memory. It WAS
2310                // loaded, but the state has been reloaded to its last chunk.
2311                assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2312
2313                // The reload can be observed via the updates too.
2314                assert_matches!(
2315                    updates_stream.recv().await.unwrap(),
2316                    RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2317                        assert_eq!(diffs.len(), 2, "{diffs:#?}");
2318                        assert_matches!(&diffs[0], VectorDiff::Clear);
2319                        assert_matches!(
2320                            &diffs[1],
2321                            VectorDiff::Append { values: events } => {
2322                                assert_eq!(events.len(), 1);
2323                                assert_eq!(events[0].event_id(), Some(ev_id_1));
2324                            }
2325                        );
2326                    }
2327                );
2328
2329                // Load one more event with a backpagination.
2330                room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2331
2332                // `ev_id_0` must now be loaded in memory.
2333                assert!(event_loaded(room_event_cache, ev_id_0).await);
2334
2335                // The pagination can be observed via the updates too.
2336                assert_matches!(
2337                    updates_stream.recv().await.unwrap(),
2338                    RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2339                        assert_eq!(diffs.len(), 1, "{diffs:#?}");
2340                        assert_matches!(
2341                            &diffs[0],
2342                            VectorDiff::Insert { index: 0, value: event } => {
2343                                assert_eq!(event.event_id(), Some(ev_id_0));
2344                            }
2345                        );
2346                    }
2347                );
2348            }
2349
2350            // Fourth, because `room_event_cache_p0` has locked the store again,
2351            // the lock is dirty for `room_event_cache_p1` too!, so it will
2352            // shrink to its last chunk!
2353            {
2354                let room_event_cache = &room_event_cache_p1;
2355                let updates_stream = &mut updates_stream_p1;
2356
2357                // `ev_id_1` must be loaded in memory, just like before.
2358                assert!(event_loaded(room_event_cache, ev_id_1).await);
2359
2360                // However, `ev_id_0` must NOT be loaded in memory. It WAS
2361                // loaded, but the state has shrunk to its last chunk.
2362                assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2363
2364                // The reload can be observed via the updates too.
2365                assert_matches!(
2366                    updates_stream.recv().await.unwrap(),
2367                    RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2368                        assert_eq!(diffs.len(), 2, "{diffs:#?}");
2369                        assert_matches!(&diffs[0], VectorDiff::Clear);
2370                        assert_matches!(
2371                            &diffs[1],
2372                            VectorDiff::Append { values: events } => {
2373                                assert_eq!(events.len(), 1);
2374                                assert_eq!(events[0].event_id(), Some(ev_id_1));
2375                            }
2376                        );
2377                    }
2378                );
2379
2380                // Load one more event with a backpagination.
2381                room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2382
2383                // `ev_id_0` must now be loaded in memory.
2384                assert!(event_loaded(room_event_cache, ev_id_0).await);
2385
2386                // The pagination can be observed via the updates too.
2387                assert_matches!(
2388                    updates_stream.recv().await.unwrap(),
2389                    RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2390                        assert_eq!(diffs.len(), 1, "{diffs:#?}");
2391                        assert_matches!(
2392                            &diffs[0],
2393                            VectorDiff::Insert { index: 0, value: event } => {
2394                                assert_eq!(event.event_id(), Some(ev_id_0));
2395                            }
2396                        );
2397                    }
2398                );
2399            }
2400        }
2401
2402        // Repeat that with an explicit read lock (so that we don't rely on
2403        // `event_loaded` to trigger the dirty detection).
2404        for _ in 0..3 {
2405            {
2406                let room_event_cache = &room_event_cache_p0;
2407                let updates_stream = &mut updates_stream_p0;
2408
2409                let guard = room_event_cache.inner.state.read().await.unwrap();
2410
2411                // Guard is kept alive, to ensure we can have multiple read
2412                // guards alive with a shared access. See
2413                // `RoomEventCacheStateLock::read` to learn more.
2414
2415                // The lock is no longer marked as dirty, it's been cleaned.
2416                assert!(guard.is_dirty().not());
2417
2418                // The reload can be observed via the updates too.
2419                assert_matches!(
2420                    updates_stream.recv().await.unwrap(),
2421                    RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2422                        assert_eq!(diffs.len(), 2, "{diffs:#?}");
2423                        assert_matches!(&diffs[0], VectorDiff::Clear);
2424                        assert_matches!(
2425                            &diffs[1],
2426                            VectorDiff::Append { values: events } => {
2427                                assert_eq!(events.len(), 1);
2428                                assert_eq!(events[0].event_id(), Some(ev_id_1));
2429                            }
2430                        );
2431                    }
2432                );
2433
2434                assert!(event_loaded(room_event_cache, ev_id_1).await);
2435                assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2436
2437                // Ensure `guard` is alive up to this point (in case this test
2438                // is refactored, I want to make this super explicit).
2439                //
2440                // We drop need to drop it before the pagination because the
2441                // pagination needs to obtain a write lock.
2442                drop(guard);
2443
2444                room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2445                assert!(event_loaded(room_event_cache, ev_id_0).await);
2446
2447                // The pagination can be observed via the updates too.
2448                assert_matches!(
2449                    updates_stream.recv().await.unwrap(),
2450                    RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2451                        assert_eq!(diffs.len(), 1, "{diffs:#?}");
2452                        assert_matches!(
2453                            &diffs[0],
2454                            VectorDiff::Insert { index: 0, value: event } => {
2455                                assert_eq!(event.event_id(), Some(ev_id_0));
2456                            }
2457                        );
2458                    }
2459                );
2460            }
2461
2462            {
2463                let room_event_cache = &room_event_cache_p1;
2464                let updates_stream = &mut updates_stream_p1;
2465
2466                let guard = room_event_cache.inner.state.read().await.unwrap();
2467
2468                // Guard is kept alive, to ensure we can have multiple read
2469                // guards alive with a shared access.
2470
2471                // The lock is no longer marked as dirty, it's been cleaned.
2472                assert!(guard.is_dirty().not());
2473
2474                // The reload can be observed via the updates too.
2475                assert_matches!(
2476                    updates_stream.recv().await.unwrap(),
2477                    RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2478                        assert_eq!(diffs.len(), 2, "{diffs:#?}");
2479                        assert_matches!(&diffs[0], VectorDiff::Clear);
2480                        assert_matches!(
2481                            &diffs[1],
2482                            VectorDiff::Append { values: events } => {
2483                                assert_eq!(events.len(), 1);
2484                                assert_eq!(events[0].event_id(), Some(ev_id_1));
2485                            }
2486                        );
2487                    }
2488                );
2489
2490                assert!(event_loaded(room_event_cache, ev_id_1).await);
2491                assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2492
2493                // Ensure `guard` is alive up to this point (in case this test
2494                // is refactored, I want to make this super explicit).
2495                //
2496                // We drop need to drop it before the pagination because the
2497                // pagination needs to obtain a write lock.
2498                drop(guard);
2499
2500                room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2501                assert!(event_loaded(room_event_cache, ev_id_0).await);
2502
2503                // The pagination can be observed via the updates too.
2504                assert_matches!(
2505                    updates_stream.recv().await.unwrap(),
2506                    RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2507                        assert_eq!(diffs.len(), 1, "{diffs:#?}");
2508                        assert_matches!(
2509                            &diffs[0],
2510                            VectorDiff::Insert { index: 0, value: event } => {
2511                                assert_eq!(event.event_id(), Some(ev_id_0));
2512                            }
2513                        );
2514                    }
2515                );
2516            }
2517        }
2518
2519        // Repeat that with an explicit write lock.
2520        for _ in 0..3 {
2521            {
2522                let room_event_cache = &room_event_cache_p0;
2523                let updates_stream = &mut updates_stream_p0;
2524
2525                let guard = room_event_cache.inner.state.write().await.unwrap();
2526
2527                // The lock is no longer marked as dirty, it's been cleaned.
2528                assert!(guard.is_dirty().not());
2529
2530                // The reload can be observed via the updates too.
2531                assert_matches!(
2532                    updates_stream.recv().await.unwrap(),
2533                    RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2534                        assert_eq!(diffs.len(), 2, "{diffs:#?}");
2535                        assert_matches!(&diffs[0], VectorDiff::Clear);
2536                        assert_matches!(
2537                            &diffs[1],
2538                            VectorDiff::Append { values: events } => {
2539                                assert_eq!(events.len(), 1);
2540                                assert_eq!(events[0].event_id(), Some(ev_id_1));
2541                            }
2542                        );
2543                    }
2544                );
2545
2546                // Guard isn't kept alive, otherwise `event_loaded` couldn't run
2547                // because it needs to obtain a read lock.
2548                drop(guard);
2549
2550                assert!(event_loaded(room_event_cache, ev_id_1).await);
2551                assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2552
2553                room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2554                assert!(event_loaded(room_event_cache, ev_id_0).await);
2555
2556                // The pagination can be observed via the updates too.
2557                assert_matches!(
2558                    updates_stream.recv().await.unwrap(),
2559                    RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2560                        assert_eq!(diffs.len(), 1, "{diffs:#?}");
2561                        assert_matches!(
2562                            &diffs[0],
2563                            VectorDiff::Insert { index: 0, value: event } => {
2564                                assert_eq!(event.event_id(), Some(ev_id_0));
2565                            }
2566                        );
2567                    }
2568                );
2569            }
2570
2571            {
2572                let room_event_cache = &room_event_cache_p1;
2573                let updates_stream = &mut updates_stream_p1;
2574
2575                let guard = room_event_cache.inner.state.write().await.unwrap();
2576
2577                // The lock is no longer marked as dirty, it's been cleaned.
2578                assert!(guard.is_dirty().not());
2579
2580                // The reload can be observed via the updates too.
2581                assert_matches!(
2582                    updates_stream.recv().await.unwrap(),
2583                    RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2584                        assert_eq!(diffs.len(), 2, "{diffs:#?}");
2585                        assert_matches!(&diffs[0], VectorDiff::Clear);
2586                        assert_matches!(
2587                            &diffs[1],
2588                            VectorDiff::Append { values: events } => {
2589                                assert_eq!(events.len(), 1);
2590                                assert_eq!(events[0].event_id(), Some(ev_id_1));
2591                            }
2592                        );
2593                    }
2594                );
2595
2596                // Guard isn't kept alive, otherwise `event_loaded` couldn't run
2597                // because it needs to obtain a read lock.
2598                drop(guard);
2599
2600                assert!(event_loaded(room_event_cache, ev_id_1).await);
2601                assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2602
2603                room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2604                assert!(event_loaded(room_event_cache, ev_id_0).await);
2605
2606                // The pagination can be observed via the updates too.
2607                assert_matches!(
2608                    updates_stream.recv().await.unwrap(),
2609                    RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2610                        assert_eq!(diffs.len(), 1, "{diffs:#?}");
2611                        assert_matches!(
2612                            &diffs[0],
2613                            VectorDiff::Insert { index: 0, value: event } => {
2614                                assert_eq!(event.event_id(), Some(ev_id_0));
2615                            }
2616                        );
2617                    }
2618                );
2619            }
2620        }
2621    }
2622
2623    #[async_test]
2624    async fn test_load_when_dirty() {
2625        let room_id_0 = room_id!("!raclette:patate.ch");
2626        let room_id_1 = room_id!("!morbiflette:patate.ch");
2627
2628        // The storage shared by the two clients.
2629        let event_cache_store = MemoryStore::new();
2630
2631        // Client for the process 0.
2632        let client_p0 = MockClientBuilder::new(None)
2633            .on_builder(|builder| {
2634                builder.store_config(
2635                    StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
2636                        .event_cache_store(event_cache_store.clone()),
2637                )
2638            })
2639            .build()
2640            .await;
2641
2642        // Client for the process 1.
2643        let client_p1 = MockClientBuilder::new(None)
2644            .on_builder(|builder| {
2645                builder.store_config(
2646                    StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
2647                        .event_cache_store(event_cache_store),
2648                )
2649            })
2650            .build()
2651            .await;
2652
2653        // Subscribe the event caches, and create the room.
2654        let (room_event_cache_0_p0, room_event_cache_0_p1) = {
2655            client_p0.base_client().get_or_create_room(room_id_0, RoomState::Joined);
2656            client_p0.base_client().get_or_create_room(room_id_1, RoomState::Joined);
2657
2658            client_p1.base_client().get_or_create_room(room_id_0, RoomState::Joined);
2659            client_p1.base_client().get_or_create_room(room_id_1, RoomState::Joined);
2660
2661            let (room_event_cache_0_p0, _drop_handles) =
2662                client_p0.event_cache().unwrap().room(room_id_0).await.unwrap();
2663            let (room_event_cache_0_p1, _drop_handles) =
2664                client_p1.event_cache().unwrap().room(room_id_0).await.unwrap();
2665
2666            (room_event_cache_0_p0, room_event_cache_0_p1)
2667        };
2668
2669        // Let's make the cross-process lock over the store dirty.
2670        {
2671            drop(room_event_cache_0_p0.inner.state.read().await.unwrap());
2672            drop(room_event_cache_0_p1.inner.state.read().await.unwrap());
2673        }
2674
2675        // Create the `RoomEventCache` for `room_id_1`. During its creation, the
2676        // cross-process lock over the store MUST be dirty, which makes no
2677        // difference as a clean one: the state is just loaded, not reloaded.
2678        let (room_event_cache_1_p0, _) =
2679            client_p0.event_cache().unwrap().room(room_id_1).await.unwrap();
2680
2681        // Check the lock isn't dirty because it's been cleared.
2682        {
2683            let guard = room_event_cache_1_p0.inner.state.read().await.unwrap();
2684            assert!(guard.is_dirty().not());
2685        }
2686
2687        // The only way to test this behaviour is to see that the dirty block in
2688        // `RoomEventCacheStateLock` is covered by this test.
2689    }
2690
2691    #[async_test]
2692    async fn test_uniq_read_marker() {
2693        let client = MockClientBuilder::new(None).build().await;
2694        let room_id = room_id!("!galette:saucisse.bzh");
2695        client.base_client().get_or_create_room(room_id, RoomState::Joined);
2696
2697        let event_cache = client.event_cache().unwrap();
2698
2699        let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
2700        let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
2701        let (events, mut stream) = room_event_cache.subscribe().await.unwrap();
2702
2703        assert!(events.is_empty());
2704
2705        // When sending multiple times the same read marker event,…
2706        let read_marker_event = Raw::from_json_string(
2707            json!({
2708                "content": {
2709                    "event_id": "$crepe:saucisse.bzh"
2710                },
2711                "room_id": "!galette:saucisse.bzh",
2712                "type": "m.fully_read"
2713            })
2714            .to_string(),
2715        )
2716        .unwrap();
2717        let account_data = vec![read_marker_event; 100];
2718
2719        room_event_cache
2720            .handle_joined_room_update(
2721                Default::default(),
2722                MaybeReceiptEventContent::none(),
2723                account_data,
2724                Default::default(),
2725                Default::default(),
2726            )
2727            .await
2728            .unwrap();
2729
2730        // … there's only one read marker update.
2731        assert_matches!(
2732            stream.recv().await.unwrap(),
2733            RoomEventCacheUpdate::MoveReadMarkerTo { .. }
2734        );
2735
2736        assert!(stream.recv().now_or_never().is_none());
2737
2738        // None, because an account data doesn't trigger a generic update.
2739        assert!(generic_stream.recv().now_or_never().is_none());
2740    }
2741
2742    async fn event_loaded(room_event_cache: &RoomEventCache, event_id: &EventId) -> bool {
2743        room_event_cache
2744            .rfind_map_event_in_memory_by(|event| {
2745                (event.event_id() == Some(event_id)).then_some(())
2746            })
2747            .await
2748            .unwrap()
2749            .is_some()
2750    }
2751}