Skip to main content

matrix_sdk/event_cache/caches/room/
mod.rs

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