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