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