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