1pub mod pagination;
16mod state;
17mod updates;
18
19use std::{collections::BTreeMap, fmt, sync::Arc};
20
21use eyeball::SharedObservable;
22use matrix_sdk_base::{
23 deserialized_responses::{AmbiguityChange, ThreadSummary},
24 event_cache::Event,
25 sync::Timeline,
26};
27use ruma::{
28 EventId, OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId,
29 events::{AnyRoomAccountDataEvent, relation::RelationType},
30 serde::Raw,
31};
32use tokio::sync::{Notify, mpsc};
33use tracing::{instrument, trace, warn};
34
35use self::pagination::RoomPagination;
36pub use self::{
37 state::RoomEventCacheState,
38 updates::{
39 LinkedChunkUpdateSender, RoomEventCacheGenericUpdate, RoomEventCacheLinkedChunkUpdate,
40 RoomEventCacheUpdate, RoomEventCacheUpdateSender,
41 },
42};
43use super::{
44 super::{
45 EventsOrigin, Result,
46 states::{CacheStateLock, StateLockWriteGuard, selectors::RoomStateSelector},
47 },
48 TimelineVectorDiffs,
49 event_linked_chunk::sort_positions_descending,
50 pagination::SharedPaginationStatus,
51 read_receipts::MaybeReceiptEventContent,
52 subscriber::{AutoShrinkMessage, Subscriber},
53};
54use crate::room::WeakRoom;
55
56#[derive(Debug)]
58pub(crate) enum RFindMapOutcome<O> {
59 Found(O),
61
62 ReachedGap,
64
65 ReachedStart,
67
68 ReachedLimit,
70}
71
72#[derive(Clone)]
76pub struct RoomEventCache {
77 inner: Arc<RoomEventCacheInner>,
78}
79
80impl fmt::Debug for RoomEventCache {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 f.debug_struct("RoomEventCache").finish_non_exhaustive()
83 }
84}
85
86impl RoomEventCache {
87 pub(super) fn new(
89 room_id: OwnedRoomId,
90 weak_room: WeakRoom,
91 own_user_id: OwnedUserId,
92 state: CacheStateLock<RoomStateSelector>,
93 shared_pagination_status: SharedObservable<SharedPaginationStatus>,
94 auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
95 update_sender: RoomEventCacheUpdateSender,
96 ) -> Self {
97 Self {
98 inner: Arc::new(RoomEventCacheInner {
99 room_id,
100 weak_room,
101 own_user_id,
102 state,
103 update_sender,
104 pagination_batch_token_notifier: Notify::new(),
105 auto_shrink_sender,
106 shared_pagination_status,
107 }),
108 }
109 }
110
111 pub fn room_id(&self) -> &RoomId {
113 &self.inner.room_id
114 }
115
116 pub(super) fn own_user_id(&self) -> &OwnedUserId {
118 &self.inner.own_user_id
119 }
120
121 pub(super) fn weak_room(&self) -> &WeakRoom {
123 &self.inner.weak_room
124 }
125
126 pub async fn events(&self) -> Result<Vec<Event>> {
131 let state = self.inner.state.read().await?;
132
133 Ok(state.room_linked_chunk().events().map(|(_position, item)| item.clone()).collect())
134 }
135
136 pub async fn subscribe(&self) -> Result<(Vec<Event>, Subscriber<RoomEventCacheUpdate>)> {
143 let state = self.inner.state.read().await?;
144 let events =
145 state.room_linked_chunk().events().map(|(_position, item)| item.clone()).collect();
146
147 let subscribers_handle = state.subscribers_handle();
148
149 let subscriber = Subscriber::new(
150 self.inner.update_sender.new_room_receiver(),
151 AutoShrinkMessage::Room { room_id: self.inner.room_id.clone() },
152 self.inner.auto_shrink_sender.clone(),
153 subscribers_handle,
154 );
155
156 trace!("added a room event cache subscriber; new count: {}", subscribers_handle.count());
157
158 Ok((events, subscriber))
159 }
160
161 pub fn pagination(&self) -> RoomPagination {
164 RoomPagination::new(self.inner.clone())
165 }
166
167 pub async fn rfind_map_event_in_memory_by<O, P>(&self, predicate: P) -> Result<Option<O>>
175 where
176 P: FnMut(&Event) -> Option<O>,
177 {
178 Ok(self.inner.state.read().await?.rfind_map_event_in_memory_by(predicate))
179 }
180
181 pub(crate) async fn rfind_map_event_until_gap_by<O, P>(
188 &self,
189 max_stored_chunks: usize,
190 predicate: P,
191 ) -> Result<RFindMapOutcome<O>>
192 where
193 P: FnMut(&Event) -> Option<O>,
194 {
195 self.inner
196 .state
197 .read()
198 .await?
199 .rfind_map_event_until_gap_by(max_stored_chunks, predicate)
200 .await
201 }
202
203 pub async fn find_event(&self, event_id: &EventId) -> Result<Option<Event>> {
208 Ok(self
209 .inner
210 .state
211 .read()
212 .await?
213 .find_event(event_id)
214 .await
215 .ok()
216 .flatten()
217 .map(|(_loc, event)| event))
218 }
219
220 pub async fn find_event_with_relations(
232 &self,
233 event_id: &EventId,
234 filter: Option<Vec<RelationType>>,
235 ) -> Result<Option<(Event, Vec<Event>)>> {
236 Ok(self
238 .inner
239 .state
240 .read()
241 .await?
242 .find_event_with_relations(event_id, filter)
243 .await
244 .ok()
245 .flatten())
246 }
247
248 pub async fn find_event_relations(
260 &self,
261 event_id: &EventId,
262 filter: Option<Vec<RelationType>>,
263 ) -> Result<Vec<Event>> {
264 self.inner.state.read().await?.find_event_relations(event_id, filter.clone()).await
266 }
267
268 pub(in super::super) fn state(&self) -> &CacheStateLock<RoomStateSelector> {
270 &self.inner.state
271 }
272
273 #[instrument(skip_all, fields(room_id = %self.room_id()))]
275 pub(super) async fn handle_joined_room_update(
276 &self,
277 timeline: Timeline,
278 read_receipts: MaybeReceiptEventContent,
279 account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
280 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
281 avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
282 ) -> Result<()> {
283 self.inner
284 .handle_timeline(timeline, read_receipts, ambiguity_changes, avatar_changes)
285 .await?;
286 self.inner.handle_account_data(account_data);
287
288 Ok(())
289 }
290
291 #[instrument(skip_all, fields(room_id = %self.room_id()))]
293 pub(super) async fn handle_left_room_update(
294 &self,
295 timeline: Timeline,
296 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
297 ) -> Result<()> {
298 self.inner
299 .handle_timeline(timeline, MaybeReceiptEventContent::none(), ambiguity_changes, None)
300 .await?;
301
302 Ok(())
303 }
304
305 pub(in super::super) fn update_thread_summary(
306 &self,
307 thread_id: &EventId,
308 new_thread_summary: ThreadSummary,
309 ) -> Result<()> {
310 self.inner.update_sender.send(
314 RoomEventCacheUpdate::UpdateThreadSummary {
315 thread_root: thread_id.to_owned(),
316 thread_summary: new_thread_summary,
317 },
318 Some(RoomEventCacheGenericUpdate { room_id: self.inner.room_id.clone() }),
319 );
320
321 Ok(())
322 }
323
324 pub(in super::super) fn update_sender(&self) -> &RoomEventCacheUpdateSender {
326 &self.inner.update_sender
327 }
328
329 pub(crate) async fn insert_sent_event_from_send_queue(&self, event: Event) -> Result<()> {
331 self.inner.insert_sent_event_from_send_queue(event).await
332 }
333
334 pub async fn debug_string(&self) -> Vec<String> {
337 match self.inner.state.read().await {
338 Ok(read_guard) => read_guard.room_linked_chunk().debug_string(),
339 Err(err) => {
340 warn!(?err, "Failed to obtain the read guard for the `RoomEventCache`");
341
342 vec![]
343 }
344 }
345 }
346}
347
348pub(super) struct RoomEventCacheInner {
350 room_id: OwnedRoomId,
352
353 weak_room: WeakRoom,
354
355 own_user_id: OwnedUserId,
357
358 state: CacheStateLock<RoomStateSelector>,
360
361 pagination_batch_token_notifier: Notify,
363
364 shared_pagination_status: SharedObservable<SharedPaginationStatus>,
365
366 auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
371
372 update_sender: RoomEventCacheUpdateSender,
374}
375
376impl RoomEventCacheInner {
377 fn handle_account_data(&self, account_data: Vec<Raw<AnyRoomAccountDataEvent>>) {
378 if account_data.is_empty() {
379 return;
380 }
381
382 let mut handled_read_marker = false;
383
384 trace!("Handling account data");
385
386 for raw_event in account_data {
387 match raw_event.deserialize() {
388 Ok(AnyRoomAccountDataEvent::FullyRead(ev)) => {
389 if handled_read_marker {
392 continue;
393 }
394
395 handled_read_marker = true;
396
397 self.update_sender.send(
400 RoomEventCacheUpdate::MoveReadMarkerTo { event_id: ev.content.event_id },
401 None,
402 );
403 }
404
405 Ok(_) => {
406 }
409
410 Err(e) => {
411 let event_type = raw_event.get_field::<String>("type").ok().flatten();
412 warn!(event_type, "Failed to deserialize account data: {e}");
413 }
414 }
415 }
416 }
417
418 async fn handle_timeline(
420 &self,
421 timeline: Timeline,
422 read_receipts: MaybeReceiptEventContent,
423 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
424 avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
425 ) -> Result<()> {
426 self.handle_timeline_inner(
427 self.state.write().await?,
428 timeline,
429 read_receipts,
430 ambiguity_changes,
431 avatar_changes,
432 )
433 .await
434 }
435
436 async fn insert_sent_event_from_send_queue(&self, event: Event) -> Result<()> {
440 let state = self.state.write().await?;
441
442 if state.room_linked_chunk().events().next().is_some() {
447 return self
448 .handle_timeline_inner(
449 state,
450 Timeline { limited: false, prev_batch: None, events: vec![event] },
451 MaybeReceiptEventContent::none(),
452 BTreeMap::new(),
453 None,
454 )
455 .await;
456 }
457
458 Ok(())
459 }
460
461 async fn handle_timeline_inner(
462 &self,
463 mut state: StateLockWriteGuard<'_, RoomEventCacheState>,
464 timeline: Timeline,
465 read_receipts: MaybeReceiptEventContent,
466 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
467 avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
468 ) -> Result<()> {
469 if timeline.events.is_empty()
470 && timeline.prev_batch.is_none()
471 && read_receipts.is_none()
472 && ambiguity_changes.is_empty()
473 && avatar_changes.as_ref().is_none_or(|avatars| avatars.is_empty())
474 {
475 return Ok(());
476 }
477
478 trace!("adding new events");
479
480 let (stored_prev_batch_token, timeline_event_diffs) =
481 state.handle_sync(timeline, &read_receipts).await?;
482
483 if stored_prev_batch_token {
486 self.pagination_batch_token_notifier.notify_one();
487 }
488
489 if !timeline_event_diffs.is_empty() {
496 state.update_sender.send(
497 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
498 diffs: timeline_event_diffs,
499 origin: EventsOrigin::Sync,
500 }),
501 Some(RoomEventCacheGenericUpdate { room_id: self.room_id.clone() }),
502 );
503 }
504
505 if let Some(read_receipts) = read_receipts.into_inner() {
506 self.update_sender
507 .send(RoomEventCacheUpdate::AddReadReceiptEvent { event: read_receipts }, None);
508 }
509
510 if !ambiguity_changes.is_empty() || avatar_changes.as_ref().is_some_and(|c| !c.is_empty()) {
511 self.update_sender.send(
512 RoomEventCacheUpdate::UpdateMembers { ambiguity_changes, avatar_changes },
513 None,
514 );
515 }
516
517 Ok(())
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use matrix_sdk_base::{RoomState, event_cache::Event};
524 use matrix_sdk_test::{async_test, event_factory::EventFactory};
525 use ruma::{
526 RoomId, event_id,
527 events::{relation::RelationType, room::message::RoomMessageEventContentWithoutRelation},
528 room_id, user_id,
529 };
530
531 use crate::test_utils::logged_in_client;
532
533 #[async_test]
534 async fn test_find_event_by_id_with_edit_relation() {
535 let original_id = event_id!("$original");
536 let related_id = event_id!("$related");
537 let room_id = room_id!("!galette:saucisse.bzh");
538 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
539
540 assert_relations(
541 room_id,
542 f.text_msg("Original event").event_id(original_id).into(),
543 f.text_msg("* An edited event")
544 .edit(
545 original_id,
546 RoomMessageEventContentWithoutRelation::text_plain("And edited event"),
547 )
548 .event_id(related_id)
549 .into(),
550 f,
551 )
552 .await;
553 }
554
555 #[async_test]
556 async fn test_find_event_by_id_with_thread_reply_relation() {
557 let original_id = event_id!("$original");
558 let related_id = event_id!("$related");
559 let room_id = room_id!("!galette:saucisse.bzh");
560 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
561
562 assert_relations(
563 room_id,
564 f.text_msg("Original event").event_id(original_id).into(),
565 f.text_msg("A reply").in_thread(original_id, related_id).event_id(related_id).into(),
566 f,
567 )
568 .await;
569 }
570
571 #[async_test]
572 async fn test_find_event_by_id_with_reaction_relation() {
573 let original_id = event_id!("$original");
574 let related_id = event_id!("$related");
575 let room_id = room_id!("!galette:saucisse.bzh");
576 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
577
578 assert_relations(
579 room_id,
580 f.text_msg("Original event").event_id(original_id).into(),
581 f.reaction(original_id, ":D").event_id(related_id).into(),
582 f,
583 )
584 .await;
585 }
586
587 #[async_test]
588 async fn test_find_event_by_id_with_poll_response_relation() {
589 let original_id = event_id!("$original");
590 let related_id = event_id!("$related");
591 let room_id = room_id!("!galette:saucisse.bzh");
592 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
593
594 assert_relations(
595 room_id,
596 f.poll_start("Poll start event", "A poll question", vec!["An answer"])
597 .event_id(original_id)
598 .into(),
599 f.poll_response(vec!["1"], original_id).event_id(related_id).into(),
600 f,
601 )
602 .await;
603 }
604
605 #[async_test]
606 async fn test_find_event_by_id_with_poll_end_relation() {
607 let original_id = event_id!("$original");
608 let related_id = event_id!("$related");
609 let room_id = room_id!("!galette:saucisse.bzh");
610 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
611
612 assert_relations(
613 room_id,
614 f.poll_start("Poll start event", "A poll question", vec!["An answer"])
615 .event_id(original_id)
616 .into(),
617 f.poll_end("Poll ended", original_id).event_id(related_id).into(),
618 f,
619 )
620 .await;
621 }
622
623 #[async_test]
624 async fn test_find_event_by_id_with_filtered_relationships() {
625 let original_id = event_id!("$original");
626 let related_id = event_id!("$related");
627 let associated_related_id = event_id!("$recursive_related");
628 let room_id = room_id!("!galette:saucisse.bzh");
629 let event_factory = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
630
631 let original_event = event_factory.text_msg("Original event").event_id(original_id).into();
632 let related_event = event_factory
633 .text_msg("* Edited event")
634 .edit(original_id, RoomMessageEventContentWithoutRelation::text_plain("Edited event"))
635 .event_id(related_id)
636 .into();
637 let associated_related_event =
638 event_factory.reaction(related_id, "🤡").event_id(associated_related_id).into();
639
640 let client = logged_in_client(None).await;
641
642 client.base_client().get_or_create_room(room_id, RoomState::Joined);
643
644 let (room_event_cache, _drop_handles) =
645 client.event_cache().unwrap().room(room_id).await.unwrap();
646
647 {
648 let mut state = room_event_cache.inner.state.write().await.unwrap();
649
650 state.save_events([original_event]).await.unwrap();
652
653 state.save_events([related_event]).await.unwrap();
655
656 state.save_events([associated_related_event]).await.unwrap();
659 }
660
661 let filter = Some(vec![RelationType::Replacement]);
662 let (event, related_events) = room_event_cache
663 .find_event_with_relations(original_id, filter)
664 .await
665 .expect("Failed to find the event with relations")
666 .expect("Event has no relation");
667 let cached_event_id = event.event_id().unwrap();
669 assert_eq!(cached_event_id, original_id);
670
671 assert_eq!(related_events.len(), 1);
674
675 let related_event_id = related_events[0].event_id().unwrap();
676 assert_eq!(related_event_id, related_id);
677
678 let filter = Some(vec![RelationType::Thread]);
680 let (event, related_events) = room_event_cache
681 .find_event_with_relations(original_id, filter)
682 .await
683 .expect("Failed to find the event with relations")
684 .expect("Event has no relation");
685
686 let cached_event_id = event.event_id().unwrap();
688 assert_eq!(cached_event_id, original_id);
689 assert!(related_events.is_empty());
691 }
692
693 #[async_test]
694 async fn test_find_event_by_id_with_recursive_relation() {
695 let original_id = event_id!("$original");
696 let related_id = event_id!("$related");
697 let associated_related_id = event_id!("$recursive_related");
698 let room_id = room_id!("!galette:saucisse.bzh");
699 let event_factory = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
700
701 let original_event = event_factory.text_msg("Original event").event_id(original_id).into();
702 let related_event = event_factory
703 .text_msg("* Edited event")
704 .edit(original_id, RoomMessageEventContentWithoutRelation::text_plain("Edited event"))
705 .event_id(related_id)
706 .into();
707 let associated_related_event =
708 event_factory.reaction(related_id, "👍").event_id(associated_related_id).into();
709
710 let client = logged_in_client(None).await;
711
712 client.base_client().get_or_create_room(room_id, RoomState::Joined);
713
714 let (room_event_cache, _drop_handles) =
715 client.event_cache().unwrap().room(room_id).await.unwrap();
716
717 {
718 let mut state = room_event_cache.inner.state.write().await.unwrap();
719
720 state.save_events([original_event]).await.unwrap();
722
723 state.save_events([related_event]).await.unwrap();
725
726 state.save_events([associated_related_event]).await.unwrap();
729 }
730
731 let (event, related_events) = room_event_cache
732 .find_event_with_relations(original_id, None)
733 .await
734 .expect("Failed to find the event with relations")
735 .expect("Event has no relation");
736 let cached_event_id = event.event_id().unwrap();
738 assert_eq!(cached_event_id, original_id);
739
740 assert_eq!(related_events.len(), 2);
742
743 let related_event_id = related_events[0].event_id().unwrap();
744 assert_eq!(related_event_id, related_id);
745 let related_event_id = related_events[1].event_id().unwrap();
746 assert_eq!(related_event_id, associated_related_id);
747 }
748
749 async fn assert_relations(
750 room_id: &RoomId,
751 original_event: Event,
752 related_event: Event,
753 event_factory: EventFactory,
754 ) {
755 let client = logged_in_client(None).await;
756
757 client.base_client().get_or_create_room(room_id, RoomState::Joined);
758
759 let (room_event_cache, _drop_handles) =
760 client.event_cache().unwrap().room(room_id).await.unwrap();
761
762 let original_event_id = original_event.event_id().unwrap().to_owned();
763 let related_id = related_event.event_id().unwrap().to_owned();
764
765 {
766 let mut state = room_event_cache.inner.state.write().await.unwrap();
767
768 state.save_events([original_event]).await.unwrap();
770
771 let unrelated_id = event_id!("$2");
774 state
775 .save_events([event_factory
776 .text_msg("An unrelated event")
777 .event_id(unrelated_id)
778 .into()])
779 .await
780 .unwrap();
781
782 state.save_events([related_event]).await.unwrap();
784 }
785
786 let (event, related_events) = room_event_cache
787 .find_event_with_relations(&original_event_id, None)
788 .await
789 .expect("Failed to find the event with relations")
790 .expect("Event has no relation");
791 let cached_event_id = event.event_id().unwrap();
793 assert_eq!(cached_event_id, original_event_id);
794
795 let related_event_id = related_events[0].event_id().unwrap();
797 assert_eq!(related_event_id, related_id);
798 }
799}
800
801#[cfg(all(test, not(target_family = "wasm")))] mod timed_tests {
803 use std::{ops::Not, sync::Arc};
804
805 use assert_matches::assert_matches;
806 use eyeball_im::VectorDiff;
807 use futures_util::FutureExt;
808 use matrix_sdk_base::{
809 RoomState,
810 event_cache::{
811 Gap,
812 store::{ControlledMemoryStore, EventCacheStore as _, MemoryStore},
813 },
814 linked_chunk::{
815 ChunkContent, ChunkIdentifier, LinkedChunkId, Position, Update,
816 lazy_loader::from_all_chunks,
817 },
818 store::StoreConfig,
819 sync::Timeline,
820 };
821 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
822 use matrix_sdk_test::{User, async_test, event_factory::EventFactory};
823 use ruma::{
824 EventId, event_id,
825 events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent},
826 room_id,
827 serde::Raw,
828 user_id,
829 };
830 use serde_json::json;
831 use strass::assert_let;
832 use tokio::task::yield_now;
833
834 use super::{
835 super::{super::TimelineVectorDiffs, pagination::LoadMoreEventsBackwardsOutcome},
836 MaybeReceiptEventContent, RoomEventCache, RoomEventCacheGenericUpdate,
837 RoomEventCacheUpdate,
838 };
839 use crate::{assert_let_timeout, test_utils::client::MockClientBuilder};
840
841 #[async_test]
842 async fn test_write_to_storage() {
843 let room_id = room_id!("!galette:saucisse.bzh");
844 let event_id_0 = event_id!("$ev0");
845 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
846
847 let event_cache_store = Arc::new(MemoryStore::new());
848
849 let client = MockClientBuilder::new(None)
850 .on_builder(|builder| {
851 builder.store_config(
852 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
853 .event_cache_store(event_cache_store.clone()),
854 )
855 })
856 .build()
857 .await;
858
859 let event_cache = client.event_cache().unwrap();
860
861 client.base_client().get_or_create_room(room_id, RoomState::Joined);
862
863 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
864 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
865
866 let timeline = Timeline {
868 limited: true,
869 prev_batch: Some("raclette".to_owned()),
870 events: vec![f.text_msg("hey yo").event_id(event_id_0).into_event()],
871 };
872
873 room_event_cache
874 .handle_joined_room_update(
875 timeline,
876 MaybeReceiptEventContent::none(),
877 Default::default(),
878 Default::default(),
879 Default::default(),
880 )
881 .await
882 .unwrap();
883
884 assert_matches!(
886 generic_stream.recv().await,
887 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
888 assert_eq!(expected_room_id, room_id);
889 }
890 );
891 assert!(generic_stream.is_empty());
892
893 let linked_chunk = from_all_chunks::<3, _, _>(
895 event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(),
896 )
897 .unwrap()
898 .unwrap();
899
900 assert_eq!(linked_chunk.chunks().count(), 2);
901
902 let mut chunks = linked_chunk.chunks();
903
904 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Gap(gap) => {
906 assert_eq!(gap.token, "raclette");
907 });
908
909 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
911 assert_eq!(events.len(), 1);
912 assert_eq!(events[0].event_id(), Some(event_id_0));
913 });
914
915 assert!(chunks.next().is_none());
917 }
918
919 #[async_test]
920 async fn test_write_to_storage_strips_bundled_relations() {
921 let room_id = room_id!("!galette:saucisse.bzh");
922 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
923
924 let event_cache_store = Arc::new(MemoryStore::new());
925
926 let client = MockClientBuilder::new(None)
927 .on_builder(|builder| {
928 builder.store_config(
929 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
930 .event_cache_store(event_cache_store.clone()),
931 )
932 })
933 .build()
934 .await;
935
936 let event_cache = client.event_cache().unwrap();
937
938 client.base_client().get_or_create_room(room_id, RoomState::Joined);
939
940 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
941 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
942
943 let ev = f
945 .text_msg("hey yo")
946 .sender(User::Alice)
947 .with_bundled_edit(f.text_msg("Hello, Kind Sir").sender(User::Alice))
948 .into_event();
949
950 let timeline = Timeline { limited: false, prev_batch: None, events: vec![ev] };
951
952 room_event_cache
953 .handle_joined_room_update(
954 timeline,
955 MaybeReceiptEventContent::none(),
956 Default::default(),
957 Default::default(),
958 Default::default(),
959 )
960 .await
961 .unwrap();
962
963 assert_matches!(
965 generic_stream.recv().await,
966 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
967 assert_eq!(expected_room_id, room_id);
968 }
969 );
970 assert!(generic_stream.is_empty());
971
972 {
974 let events = room_event_cache.events().await.unwrap();
975
976 assert_eq!(events.len(), 1);
977
978 let ev = events[0].raw().deserialize().unwrap();
979 assert_let!(
980 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = ev
981 );
982
983 let original = msg.as_original().unwrap();
984 assert_eq!(original.content.body(), "hey yo");
985 assert!(original.unsigned.relations.replace.is_some());
986 }
987
988 let linked_chunk = from_all_chunks::<3, _, _>(
990 event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap(),
991 )
992 .unwrap()
993 .unwrap();
994
995 assert_eq!(linked_chunk.chunks().count(), 1);
996
997 let mut chunks = linked_chunk.chunks();
998 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
999 assert_eq!(events.len(), 1);
1000
1001 let ev = events[0].raw().deserialize().unwrap();
1002 assert_let!(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = ev);
1003
1004 let original = msg.as_original().unwrap();
1005 assert_eq!(original.content.body(), "hey yo");
1006 assert!(original.unsigned.relations.replace.is_none());
1007 });
1008
1009 assert!(chunks.next().is_none());
1011 }
1012
1013 #[async_test]
1014 async fn test_clear() {
1015 let room_id = room_id!("!galette:saucisse.bzh");
1016 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
1017
1018 let event_id1 = event_id!("$1");
1019 let event_id2 = event_id!("$2");
1020
1021 let ev1 = f.text_msg("hello world").event_id(event_id1).into_event();
1022 let ev2 = f.text_msg("how's it going").event_id(event_id2).into_event();
1023
1024 let event_cache_store = Arc::new(MemoryStore::new());
1026 event_cache_store
1027 .handle_linked_chunk_updates(
1028 LinkedChunkId::Room(room_id),
1029 vec![
1030 Update::NewItemsChunk {
1032 previous: None,
1033 new: ChunkIdentifier::new(0),
1034 next: None,
1035 },
1036 Update::NewGapChunk {
1038 previous: Some(ChunkIdentifier::new(0)),
1039 new: ChunkIdentifier::new(42),
1041 next: None,
1042 gap: Gap { token: "comté".to_owned() },
1043 },
1044 Update::NewItemsChunk {
1046 previous: Some(ChunkIdentifier::new(42)),
1047 new: ChunkIdentifier::new(1),
1048 next: None,
1049 },
1050 Update::PushItems {
1051 at: Position::new(ChunkIdentifier::new(1), 0),
1052 items: vec![ev1.clone()],
1053 },
1054 Update::NewItemsChunk {
1056 previous: Some(ChunkIdentifier::new(1)),
1057 new: ChunkIdentifier::new(2),
1058 next: None,
1059 },
1060 Update::PushItems {
1061 at: Position::new(ChunkIdentifier::new(2), 0),
1062 items: vec![ev2.clone()],
1063 },
1064 ],
1065 )
1066 .await
1067 .unwrap();
1068
1069 let client = MockClientBuilder::new(None)
1070 .on_builder(|builder| {
1071 builder.store_config(
1072 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
1073 .event_cache_store(event_cache_store.clone()),
1074 )
1075 })
1076 .build()
1077 .await;
1078
1079 let event_cache = client.event_cache().unwrap();
1080
1081 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1082
1083 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1084
1085 let (items, mut stream) = room_event_cache.subscribe().await.unwrap();
1086 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1087
1088 {
1090 assert!(room_event_cache.find_event(event_id1).await.unwrap().is_some());
1091 assert!(room_event_cache.find_event(event_id2).await.unwrap().is_some());
1092 }
1093
1094 {
1096 assert_eq!(items.len(), 1);
1099 assert_eq!(items[0].event_id().unwrap(), event_id2);
1100
1101 assert!(stream.is_empty());
1102 }
1103
1104 {
1106 room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1107
1108 assert_let_timeout!(
1109 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1110 stream.recv()
1111 );
1112 assert_eq!(diffs.len(), 1);
1113 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
1114 assert_eq!(event.event_id().unwrap(), event_id1);
1116 });
1117
1118 assert!(stream.is_empty());
1119
1120 assert_let_timeout!(
1121 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) =
1122 generic_stream.recv()
1123 );
1124 assert_eq!(room_id, expected_room_id);
1125 assert!(generic_stream.is_empty());
1126 }
1127
1128 event_cache.clear_all_rooms().await.unwrap();
1130
1131 assert_let_timeout!(
1133 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1134 stream.recv()
1135 );
1136 assert_eq!(diffs.len(), 1);
1137 assert_let!(VectorDiff::Clear = &diffs[0]);
1138
1139 assert_let_timeout!(
1141 Ok(RoomEventCacheGenericUpdate { room_id: received_room_id }) = generic_stream.recv()
1142 );
1143 assert_eq!(received_room_id, room_id);
1144 assert!(generic_stream.is_empty());
1145
1146 assert!(room_event_cache.find_event(event_id1).await.unwrap().is_none());
1148
1149 let items = room_event_cache.events().await.unwrap();
1151 assert!(items.is_empty());
1152
1153 assert!(
1155 event_cache_store
1156 .load_all_chunks(LinkedChunkId::Room(room_id))
1157 .await
1158 .unwrap()
1159 .is_empty()
1160 );
1161 }
1162
1163 #[async_test]
1164 async fn test_load_from_storage() {
1165 let room_id = room_id!("!galette:saucisse.bzh");
1166 let f = EventFactory::new().room(room_id).sender(user_id!("@ben:saucisse.bzh"));
1167
1168 let event_id1 = event_id!("$1");
1169 let event_id2 = event_id!("$2");
1170
1171 let ev1 = f.text_msg("hello world").sender(User::Alice).event_id(event_id1).into_event();
1172 let ev2 = f.text_msg("how's it going").sender(User::Bob).event_id(event_id2).into_event();
1173
1174 let event_cache_store = Arc::new(MemoryStore::new());
1176 event_cache_store
1177 .handle_linked_chunk_updates(
1178 LinkedChunkId::Room(room_id),
1179 vec![
1180 Update::NewItemsChunk {
1182 previous: None,
1183 new: ChunkIdentifier::new(0),
1184 next: None,
1185 },
1186 Update::NewGapChunk {
1188 previous: Some(ChunkIdentifier::new(0)),
1189 new: ChunkIdentifier::new(42),
1191 next: None,
1192 gap: Gap { token: "cheddar".to_owned() },
1193 },
1194 Update::NewItemsChunk {
1196 previous: Some(ChunkIdentifier::new(42)),
1197 new: ChunkIdentifier::new(1),
1198 next: None,
1199 },
1200 Update::PushItems {
1201 at: Position::new(ChunkIdentifier::new(1), 0),
1202 items: vec![ev1.clone()],
1203 },
1204 Update::NewItemsChunk {
1206 previous: Some(ChunkIdentifier::new(1)),
1207 new: ChunkIdentifier::new(2),
1208 next: None,
1209 },
1210 Update::PushItems {
1211 at: Position::new(ChunkIdentifier::new(2), 0),
1212 items: vec![ev2.clone()],
1213 },
1214 ],
1215 )
1216 .await
1217 .unwrap();
1218
1219 let client = MockClientBuilder::new(None)
1220 .on_builder(|builder| {
1221 builder.store_config(
1222 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
1223 .event_cache_store(event_cache_store.clone()),
1224 )
1225 })
1226 .build()
1227 .await;
1228
1229 let event_cache = client.event_cache().unwrap();
1230
1231 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1234
1235 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1236
1237 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1238
1239 assert_matches!(
1242 generic_stream.recv().await,
1243 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1244 assert_eq!(room_id, expected_room_id);
1245 }
1246 );
1247 assert!(generic_stream.is_empty());
1248
1249 let (items, mut stream) = room_event_cache.subscribe().await.unwrap();
1250
1251 assert_eq!(items.len(), 1);
1254 assert_eq!(items[0].event_id().unwrap(), event_id2);
1255 assert!(stream.is_empty());
1256
1257 assert!(room_event_cache.find_event(event_id1).await.unwrap().is_some());
1260 assert!(room_event_cache.find_event(event_id2).await.unwrap().is_some());
1261
1262 room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1264
1265 assert_let_timeout!(
1266 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1267 stream.recv()
1268 );
1269 assert_eq!(diffs.len(), 1);
1270 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
1271 assert_eq!(event.event_id().unwrap(), event_id1);
1272 });
1273
1274 assert!(stream.is_empty());
1275
1276 assert_matches!(
1278 generic_stream.recv().await,
1279 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1280 assert_eq!(expected_room_id, room_id);
1281 }
1282 );
1283 assert!(generic_stream.is_empty());
1284
1285 let timeline = Timeline { limited: false, prev_batch: None, events: vec![ev2] };
1287
1288 room_event_cache
1289 .handle_joined_room_update(
1290 timeline,
1291 MaybeReceiptEventContent::none(),
1292 Default::default(),
1293 Default::default(),
1294 Default::default(),
1295 )
1296 .await
1297 .unwrap();
1298
1299 assert!(generic_stream.recv().now_or_never().is_none());
1302
1303 let items = room_event_cache.events().await.unwrap();
1308 assert_eq!(items.len(), 2);
1309 assert_eq!(items[0].event_id().unwrap(), event_id1);
1310 assert_eq!(items[1].event_id().unwrap(), event_id2);
1311 }
1312
1313 #[async_test]
1314 async fn test_load_from_storage_resilient_to_failure() {
1315 let room_id = room_id!("!fondue:patate.ch");
1316
1317 let event = EventFactory::new()
1318 .room(room_id)
1319 .sender(user_id!("@ben:saucisse.bzh"))
1320 .text_msg("foo")
1321 .event_id(event_id!("$42"))
1322 .into_event();
1323
1324 let event_cache_store = Arc::new(MemoryStore::new());
1326 event_cache_store
1327 .handle_linked_chunk_updates(
1328 LinkedChunkId::Room(room_id),
1329 vec![
1330 Update::NewItemsChunk {
1331 previous: None,
1332 new: ChunkIdentifier::new(0),
1333 next: None,
1334 },
1335 Update::PushItems {
1336 at: Position::new(ChunkIdentifier::new(0), 0),
1337 items: vec![event],
1338 },
1339 Update::NewItemsChunk {
1340 previous: Some(ChunkIdentifier::new(0)),
1341 new: ChunkIdentifier::new(1),
1342 next: Some(ChunkIdentifier::new(0)),
1343 },
1344 ],
1345 )
1346 .await
1347 .unwrap();
1348
1349 let client = MockClientBuilder::new(None)
1350 .on_builder(|builder| {
1351 builder.store_config(
1352 StoreConfig::new(CrossProcessLockConfig::multi_process("holder"))
1353 .event_cache_store(event_cache_store.clone()),
1354 )
1355 })
1356 .build()
1357 .await;
1358
1359 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1360 let event_cache = client.event_cache().unwrap();
1361
1362 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1363
1364 let items = room_event_cache.events().await.unwrap();
1365
1366 assert!(items.is_empty());
1369
1370 let raw_chunks =
1373 event_cache_store.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap();
1374 assert!(raw_chunks.is_empty());
1375 }
1376
1377 #[async_test]
1378 async fn test_no_useless_gaps() {
1379 let room_id = room_id!("!galette:saucisse.bzh");
1380
1381 let client = MockClientBuilder::new(None).build().await;
1382
1383 let event_cache = client.event_cache().unwrap();
1384
1385 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1386 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1387 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1388
1389 let f = EventFactory::new().room(room_id).sender(User::Alice);
1390
1391 room_event_cache
1394 .handle_joined_room_update(
1395 Timeline {
1396 limited: true,
1397 prev_batch: Some("raclette".to_owned()),
1398 events: vec![f.text_msg("hey yo").into_event()],
1399 },
1400 MaybeReceiptEventContent::none(),
1401 Default::default(),
1402 Default::default(),
1403 Default::default(),
1404 )
1405 .await
1406 .unwrap();
1407
1408 assert_matches!(
1410 generic_stream.recv().await,
1411 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1412 assert_eq!(expected_room_id, room_id);
1413 }
1414 );
1415 assert!(generic_stream.is_empty());
1416
1417 {
1418 let state = room_event_cache.inner.state.read().await.unwrap();
1419
1420 let mut num_gaps = 0;
1421 let mut num_events = 0;
1422
1423 for c in state.room_linked_chunk().chunks() {
1424 match c.content() {
1425 ChunkContent::Items(items) => num_events += items.len(),
1426 ChunkContent::Gap(_) => num_gaps += 1,
1427 }
1428 }
1429
1430 assert_eq!(num_gaps, 0);
1433 assert_eq!(num_events, 1);
1434 }
1435
1436 assert_matches!(
1438 room_event_cache.pagination().load_more_events_backwards().await.unwrap(),
1439 LoadMoreEventsBackwardsOutcome::Gap { .. }
1440 );
1441
1442 {
1443 let state = room_event_cache.inner.state.read().await.unwrap();
1444
1445 let mut num_gaps = 0;
1446 let mut num_events = 0;
1447
1448 for c in state.room_linked_chunk().chunks() {
1449 match c.content() {
1450 ChunkContent::Items(items) => num_events += items.len(),
1451 ChunkContent::Gap(_) => num_gaps += 1,
1452 }
1453 }
1454
1455 assert_eq!(num_gaps, 1);
1457 assert_eq!(num_events, 1);
1458 }
1459
1460 room_event_cache
1463 .handle_joined_room_update(
1464 Timeline {
1465 limited: false,
1466 prev_batch: Some("fondue".to_owned()),
1467 events: vec![f.text_msg("sup").into_event()],
1468 },
1469 MaybeReceiptEventContent::none(),
1470 Default::default(),
1471 Default::default(),
1472 Default::default(),
1473 )
1474 .await
1475 .unwrap();
1476
1477 assert_matches!(
1479 generic_stream.recv().await,
1480 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1481 assert_eq!(expected_room_id, room_id);
1482 }
1483 );
1484 assert!(generic_stream.is_empty());
1485
1486 {
1487 let state = room_event_cache.inner.state.read().await.unwrap();
1488
1489 let mut num_gaps = 0;
1490 let mut num_events = 0;
1491
1492 for c in state.room_linked_chunk().chunks() {
1493 match c.content() {
1494 ChunkContent::Items(items) => num_events += items.len(),
1495 ChunkContent::Gap(gap) => {
1496 assert_eq!(gap.token, "raclette");
1497 num_gaps += 1;
1498 }
1499 }
1500 }
1501
1502 assert_eq!(num_gaps, 1);
1504 assert_eq!(num_events, 2);
1505 }
1506 }
1507
1508 #[async_test]
1509 async fn test_shrink_to_last_chunk() {
1510 let room_id = room_id!("!galette:saucisse.bzh");
1511
1512 let f = EventFactory::new().room(room_id);
1513
1514 let evid1 = event_id!("$1");
1515 let evid2 = event_id!("$2");
1516
1517 let ev1 = f.text_msg("hello world").sender(User::Alice).event_id(evid1).into_event();
1518 let ev2 = f.text_msg("howdy").sender(User::Bob).event_id(evid2).into_event();
1519
1520 let event_cache_store = MemoryStore::new();
1523 event_cache_store
1524 .handle_linked_chunk_updates(
1525 LinkedChunkId::Room(room_id),
1526 vec![
1527 Update::NewItemsChunk {
1528 previous: None,
1529 new: ChunkIdentifier::new(0),
1530 next: None,
1531 },
1532 Update::PushItems {
1533 at: Position::new(ChunkIdentifier::new(0), 0),
1534 items: vec![ev1],
1535 },
1536 Update::NewItemsChunk {
1537 previous: Some(ChunkIdentifier::new(0)),
1538 new: ChunkIdentifier::new(1),
1539 next: None,
1540 },
1541 Update::PushItems {
1542 at: Position::new(ChunkIdentifier::new(1), 0),
1543 items: vec![ev2],
1544 },
1545 ],
1546 )
1547 .await
1548 .unwrap();
1549
1550 let client = MockClientBuilder::new(None)
1551 .on_builder(move |builder| {
1552 builder.store_config(
1553 StoreConfig::new(CrossProcessLockConfig::MultiProcess {
1554 holder_name: "foo".to_owned(),
1555 })
1556 .event_cache_store(event_cache_store),
1557 )
1558 })
1559 .build()
1560 .await;
1561
1562 let event_cache = client.event_cache().unwrap();
1563
1564 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1565 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1566
1567 let (events, mut stream) = room_event_cache.subscribe().await.unwrap();
1569 assert_eq!(events.len(), 1);
1570 assert_eq!(events[0].event_id(), Some(evid2));
1571 assert!(stream.is_empty());
1572
1573 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1574
1575 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1577 assert_eq!(outcome.events.len(), 1);
1578 assert_eq!(outcome.events[0].event_id(), Some(evid1));
1579 assert!(outcome.reached_start);
1580
1581 assert_let_timeout!(
1583 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1584 stream.recv()
1585 );
1586 assert_eq!(diffs.len(), 1);
1587 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1588 assert_eq!(value.event_id(), Some(evid1));
1589 });
1590
1591 assert!(stream.is_empty());
1592
1593 assert_let_timeout!(
1595 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1596 );
1597 assert_eq!(expected_room_id, room_id);
1598 assert!(generic_stream.is_empty());
1599
1600 room_event_cache
1602 .inner
1603 .state
1604 .reload_no_preprocessing()
1605 .await
1606 .expect("shrinking should succeed");
1607
1608 assert_let_timeout!(
1610 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1611 stream.recv()
1612 );
1613 assert_eq!(diffs.len(), 2);
1614 assert_matches!(&diffs[0], VectorDiff::Clear);
1615 assert_matches!(&diffs[1], VectorDiff::Append { values} => {
1616 assert_eq!(values.len(), 1);
1617 assert_eq!(values[0].event_id(), Some(evid2));
1618 });
1619
1620 assert!(stream.is_empty());
1621
1622 assert_let_timeout!(Ok(RoomEventCacheGenericUpdate { .. }) = generic_stream.recv());
1624 assert!(generic_stream.is_empty());
1625
1626 let events = room_event_cache.events().await.unwrap();
1628 assert_eq!(events.len(), 1);
1629 assert_eq!(events[0].event_id(), Some(evid2));
1630
1631 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1634 assert_eq!(outcome.events.len(), 1);
1635 assert_eq!(outcome.events[0].event_id(), Some(evid1));
1636 assert!(outcome.reached_start);
1637 }
1638
1639 #[async_test]
1640 async fn test_shrink_when_loading_the_last_chunk_fails() {
1641 let room_id = room_id!("!galette:saucisse.bzh");
1642
1643 let f = EventFactory::new().room(room_id);
1644
1645 let ev1 =
1646 f.text_msg("hello world").sender(User::Alice).event_id(event_id!("$1")).into_event();
1647 let ev2 = f.text_msg("howdy").sender(User::Bob).event_id(event_id!("$2")).into_event();
1648
1649 let event_cache_store = ControlledMemoryStore::new();
1652 event_cache_store
1653 .handle_linked_chunk_updates(
1654 LinkedChunkId::Room(room_id),
1655 vec![
1656 Update::NewItemsChunk {
1657 previous: None,
1658 new: ChunkIdentifier::new(0),
1659 next: None,
1660 },
1661 Update::PushItems {
1662 at: Position::new(ChunkIdentifier::new(0), 0),
1663 items: vec![ev1],
1664 },
1665 Update::NewItemsChunk {
1666 previous: Some(ChunkIdentifier::new(0)),
1667 new: ChunkIdentifier::new(1),
1668 next: None,
1669 },
1670 Update::PushItems {
1671 at: Position::new(ChunkIdentifier::new(1), 0),
1672 items: vec![ev2],
1673 },
1674 ],
1675 )
1676 .await
1677 .unwrap();
1678
1679 let client = MockClientBuilder::new(None)
1680 .on_builder({
1681 let event_cache_store = event_cache_store.clone();
1682
1683 move |builder| {
1684 builder.store_config(
1685 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
1686 .event_cache_store(event_cache_store),
1687 )
1688 }
1689 })
1690 .build()
1691 .await;
1692
1693 let event_cache = client.event_cache().unwrap();
1694
1695 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1696 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1697
1698 let events = room_event_cache.events().await.unwrap();
1699 assert_eq!(events.len(), 1);
1700
1701 event_cache_store.fail_next_last_chunk_load();
1704 room_event_cache
1705 .inner
1706 .state
1707 .reload_no_preprocessing()
1708 .await
1709 .expect("shrinking should succeed");
1710
1711 assert!(room_event_cache.events().await.unwrap().is_empty());
1713
1714 event_cache_store.stop_failing_next_last_chunk_load();
1715
1716 room_event_cache
1719 .inner
1720 .state
1721 .reload_no_preprocessing()
1722 .await
1723 .expect("shrinking should succeed");
1724
1725 assert!(room_event_cache.events().await.unwrap().is_empty());
1726 }
1727
1728 #[async_test]
1729 async fn test_room_ordering() {
1730 let room_id = room_id!("!galette:saucisse.bzh");
1731
1732 let f = EventFactory::new().room(room_id).sender(User::Alice);
1733
1734 let evid1 = event_id!("$1");
1735 let evid2 = event_id!("$2");
1736 let evid3 = event_id!("$3");
1737
1738 let ev1 = f.text_msg("hello world").event_id(evid1).into_event();
1739 let ev2 = f.text_msg("howdy").sender(User::Bob).event_id(evid2).into_event();
1740 let ev3 = f.text_msg("yo").event_id(evid3).into_event();
1741
1742 let event_cache_store = MemoryStore::new();
1745 event_cache_store
1746 .handle_linked_chunk_updates(
1747 LinkedChunkId::Room(room_id),
1748 vec![
1749 Update::NewItemsChunk {
1750 previous: None,
1751 new: ChunkIdentifier::new(0),
1752 next: None,
1753 },
1754 Update::PushItems {
1755 at: Position::new(ChunkIdentifier::new(0), 0),
1756 items: vec![ev1, ev2],
1757 },
1758 Update::NewItemsChunk {
1759 previous: Some(ChunkIdentifier::new(0)),
1760 new: ChunkIdentifier::new(1),
1761 next: None,
1762 },
1763 Update::PushItems {
1764 at: Position::new(ChunkIdentifier::new(1), 0),
1765 items: vec![ev3.clone()],
1766 },
1767 ],
1768 )
1769 .await
1770 .unwrap();
1771
1772 let client = MockClientBuilder::new(None)
1773 .on_builder(move |builder| {
1774 builder.store_config(
1775 StoreConfig::new(CrossProcessLockConfig::MultiProcess {
1776 holder_name: "foo".to_owned(),
1777 })
1778 .event_cache_store(event_cache_store),
1779 )
1780 })
1781 .build()
1782 .await;
1783
1784 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1785 let event_cache = client.event_cache().unwrap();
1786
1787 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1788
1789 {
1792 let state = room_event_cache.inner.state.read().await.unwrap();
1793 let room_linked_chunk = state.room_linked_chunk();
1794
1795 assert_eq!(
1797 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 0)),
1798 Some(0)
1799 );
1800
1801 assert_eq!(
1803 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 1)),
1804 Some(1)
1805 );
1806
1807 let mut events = room_linked_chunk.events();
1809 let (pos, ev) = events.next().unwrap();
1810 assert_eq!(pos, Position::new(ChunkIdentifier::new(1), 0));
1811 assert_eq!(ev.event_id(), Some(evid3));
1812 assert_eq!(room_linked_chunk.event_order(pos), Some(2));
1813
1814 assert!(events.next().is_none());
1816 }
1817
1818 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1820 assert!(outcome.reached_start);
1821
1822 {
1825 let state = room_event_cache.inner.state.read().await.unwrap();
1826 let room_linked_chunk = state.room_linked_chunk();
1827
1828 for (i, (pos, _)) in room_linked_chunk.events().enumerate() {
1829 assert_eq!(room_linked_chunk.event_order(pos), Some(i));
1830 }
1831 }
1832
1833 let evid4 = event_id!("$4");
1838 room_event_cache
1839 .handle_joined_room_update(
1840 Timeline {
1841 limited: true,
1842 prev_batch: Some("fondue".to_owned()),
1843 events: vec![ev3, f.text_msg("sup").event_id(evid4).into_event()],
1844 },
1845 MaybeReceiptEventContent::none(),
1846 Default::default(),
1847 Default::default(),
1848 Default::default(),
1849 )
1850 .await
1851 .unwrap();
1852
1853 {
1854 let state = room_event_cache.inner.state.read().await.unwrap();
1855 let room_linked_chunk = state.room_linked_chunk();
1856
1857 let mut events = room_linked_chunk.events();
1859
1860 let (pos, ev) = events.next().unwrap();
1861 assert_eq!(ev.event_id(), Some(evid3));
1862 assert_eq!(room_linked_chunk.event_order(pos), Some(2));
1863
1864 let (pos, ev) = events.next().unwrap();
1865 assert_eq!(ev.event_id(), Some(evid4));
1866 assert_eq!(room_linked_chunk.event_order(pos), Some(3));
1867
1868 assert!(events.next().is_none());
1870
1871 assert_eq!(
1873 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 0)),
1874 Some(0)
1875 );
1876 assert_eq!(
1877 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(0), 1)),
1878 Some(1)
1879 );
1880
1881 assert_eq!(
1884 room_linked_chunk.event_order(Position::new(ChunkIdentifier::new(1), 0)),
1885 None
1886 );
1887 }
1888 }
1889
1890 #[async_test]
1891 async fn test_auto_shrink_after_all_subscribers_are_gone() {
1892 let room_id = room_id!("!galette:saucisse.bzh");
1893
1894 let f = EventFactory::new().room(room_id);
1895
1896 let evid1 = event_id!("$1");
1897 let evid2 = event_id!("$2");
1898
1899 let ev1 = f.text_msg("hello world").sender(User::Alice).event_id(evid1).into_event();
1900 let ev2 = f.text_msg("howdy").sender(User::Bob).event_id(evid2).into_event();
1901
1902 let event_cache_store = MemoryStore::new();
1905 event_cache_store
1906 .handle_linked_chunk_updates(
1907 LinkedChunkId::Room(room_id),
1908 vec![
1909 Update::NewItemsChunk {
1910 previous: None,
1911 new: ChunkIdentifier::new(0),
1912 next: None,
1913 },
1914 Update::PushItems {
1915 at: Position::new(ChunkIdentifier::new(0), 0),
1916 items: vec![ev1],
1917 },
1918 Update::NewItemsChunk {
1919 previous: Some(ChunkIdentifier::new(0)),
1920 new: ChunkIdentifier::new(1),
1921 next: None,
1922 },
1923 Update::PushItems {
1924 at: Position::new(ChunkIdentifier::new(1), 0),
1925 items: vec![ev2],
1926 },
1927 ],
1928 )
1929 .await
1930 .unwrap();
1931
1932 let client = MockClientBuilder::new(None)
1933 .on_builder(move |builder| {
1934 builder.store_config(
1935 StoreConfig::new(CrossProcessLockConfig::MultiProcess {
1936 holder_name: "foo".to_owned(),
1937 })
1938 .event_cache_store(event_cache_store),
1939 )
1940 })
1941 .build()
1942 .await;
1943
1944 let event_cache = client.event_cache().unwrap();
1945
1946 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1947 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
1948
1949 let (events1, mut stream1) = room_event_cache.subscribe().await.unwrap();
1951 assert_eq!(events1.len(), 1);
1952 assert_eq!(events1[0].event_id(), Some(evid2));
1953 assert!(stream1.is_empty());
1954
1955 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1956
1957 let outcome = room_event_cache.pagination().run_backwards_once(20).await.unwrap();
1959 assert_eq!(outcome.events.len(), 1);
1960 assert_eq!(outcome.events[0].event_id(), Some(evid1));
1961 assert!(outcome.reached_start);
1962
1963 assert_let_timeout!(
1966 Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1967 stream1.recv()
1968 );
1969 assert_eq!(diffs.len(), 1);
1970 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1971 assert_eq!(value.event_id(), Some(evid1));
1972 });
1973
1974 assert!(stream1.is_empty());
1975
1976 assert_let_timeout!(
1977 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1978 );
1979 assert_eq!(expected_room_id, room_id);
1980 assert!(generic_stream.is_empty());
1981
1982 let (events2, stream2) = room_event_cache.subscribe().await.unwrap();
1986 assert_eq!(events2.len(), 2);
1987 assert_eq!(events2[0].event_id(), Some(evid1));
1988 assert_eq!(events2[1].event_id(), Some(evid2));
1989 assert!(stream2.is_empty());
1990
1991 let subscriber = {
1993 let state = room_event_cache.inner.state.read().await.unwrap();
1994 state.update_sender.new_room_receiver()
1995 };
1996
1997 drop(stream1);
1999 yield_now().await;
2000
2001 assert!(stream2.is_empty());
2003
2004 drop(stream2);
2006 yield_now().await;
2007
2008 {
2011 let state = room_event_cache.inner.state.read().await.unwrap();
2013 assert_eq!(state.subscribers_handle().count(), 0);
2014
2015 assert!(subscriber.is_empty());
2017 assert!(generic_stream.is_empty());
2018 }
2019
2020 let events3 = room_event_cache.events().await.unwrap();
2022 assert_eq!(events3.len(), 1);
2023 assert_eq!(events3[0].event_id(), Some(evid2));
2024 }
2025
2026 #[async_test]
2027 async fn test_rfind_map_event_in_memory_by() {
2028 let user_id = user_id!("@mnt_io:matrix.org");
2029 let room_id = room_id!("!raclette:patate.ch");
2030
2031 let event_factory = EventFactory::new().room(room_id);
2032
2033 let event_id_0 = event_id!("$ev0");
2034 let event_id_1 = event_id!("$ev1");
2035 let event_id_2 = event_id!("$ev2");
2036 let event_id_3 = event_id!("$ev3");
2037
2038 let event_0 =
2039 event_factory.text_msg("hello").sender(User::Bob).event_id(event_id_0).into_event();
2040 let event_1 =
2041 event_factory.text_msg("world").sender(User::Alice).event_id(event_id_1).into_event();
2042 let event_2 =
2043 event_factory.text_msg("!").sender(User::Alice).event_id(event_id_2).into_event();
2044 let event_3 =
2045 event_factory.text_msg("eh!").sender(user_id).event_id(event_id_3).into_event();
2046
2047 let event_cache_store = MemoryStore::new();
2050 event_cache_store
2051 .handle_linked_chunk_updates(
2052 LinkedChunkId::Room(room_id),
2053 vec![
2054 Update::NewItemsChunk {
2055 previous: None,
2056 new: ChunkIdentifier::new(0),
2057 next: None,
2058 },
2059 Update::PushItems {
2060 at: Position::new(ChunkIdentifier::new(0), 0),
2061 items: vec![event_3],
2062 },
2063 Update::NewItemsChunk {
2064 previous: Some(ChunkIdentifier::new(0)),
2065 new: ChunkIdentifier::new(1),
2066 next: None,
2067 },
2068 Update::PushItems {
2069 at: Position::new(ChunkIdentifier::new(1), 0),
2070 items: vec![event_0, event_1, event_2],
2071 },
2072 ],
2073 )
2074 .await
2075 .unwrap();
2076
2077 let client = MockClientBuilder::new(None)
2078 .on_builder(move |builder| {
2079 builder.store_config(
2080 StoreConfig::new(CrossProcessLockConfig::MultiProcess {
2081 holder_name: "foo".to_owned(),
2082 })
2083 .event_cache_store(event_cache_store),
2084 )
2085 })
2086 .build()
2087 .await;
2088
2089 client.base_client().get_or_create_room(room_id, RoomState::Joined);
2090 let event_cache = client.event_cache().unwrap();
2091 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
2092
2093 assert_matches!(
2095 room_event_cache
2096 .rfind_map_event_in_memory_by(|event| {
2097 (event.sender().is_some_and(|sender| sender == User::Bob)).then(|| event.event_id().map(ToOwned::to_owned))
2098 })
2099 .await,
2100 Ok(Some(event_id)) => {
2101 assert_eq!(event_id.as_deref(), Some(event_id_0));
2102 }
2103 );
2104
2105 assert_matches!(
2108 room_event_cache
2109 .rfind_map_event_in_memory_by(|event| {
2110 (event.sender().is_some_and(|sender| sender == User::Alice)).then(|| event.event_id().map(ToOwned::to_owned))
2111 })
2112 .await,
2113 Ok(Some(event_id)) => {
2114 assert_eq!(event_id.as_deref(), Some(event_id_2));
2115 }
2116 );
2117
2118 assert!(
2120 room_event_cache
2121 .rfind_map_event_in_memory_by(|event| {
2122 (event.sender().as_deref() == Some(user_id))
2123 .then(|| event.event_id().map(ToOwned::to_owned))
2124 })
2125 .await
2126 .unwrap()
2127 .is_none()
2128 );
2129
2130 assert!(
2132 room_event_cache.rfind_map_event_in_memory_by(|_| None::<()>).await.unwrap().is_none()
2133 );
2134 }
2135
2136 #[async_test]
2137 async fn test_reload_when_dirty() {
2138 let user_id = user_id!("@mnt_io:matrix.org");
2139 let room_id = room_id!("!raclette:patate.ch");
2140
2141 let event_factory = EventFactory::new().room(room_id).sender(user_id);
2142
2143 let ev_id_0 = event_id!("$ev_0");
2144 let ev_id_1 = event_id!("$ev_1");
2145
2146 let ev_0 = event_factory.text_msg("comté").event_id(ev_id_0).into_event();
2147 let ev_1 = event_factory.text_msg("morbier").event_id(ev_id_1).into_event();
2148
2149 let event_cache_store = MemoryStore::new();
2151 event_cache_store
2152 .handle_linked_chunk_updates(
2153 LinkedChunkId::Room(room_id),
2154 vec![
2155 Update::NewItemsChunk {
2156 previous: None,
2157 new: ChunkIdentifier::new(0),
2158 next: None,
2159 },
2160 Update::PushItems {
2161 at: Position::new(ChunkIdentifier::new(0), 0),
2162 items: vec![ev_0],
2163 },
2164 Update::NewItemsChunk {
2165 previous: Some(ChunkIdentifier::new(0)),
2166 new: ChunkIdentifier::new(1),
2167 next: None,
2168 },
2169 Update::PushItems {
2170 at: Position::new(ChunkIdentifier::new(1), 0),
2171 items: vec![ev_1],
2172 },
2173 ],
2174 )
2175 .await
2176 .unwrap();
2177
2178 let client_p0 = MockClientBuilder::new(None)
2180 .on_builder(|builder| {
2181 builder.store_config(
2182 StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
2183 .event_cache_store(event_cache_store.clone()),
2184 )
2185 })
2186 .build()
2187 .await;
2188
2189 let client_p1 = MockClientBuilder::new(None)
2191 .on_builder(|builder| {
2192 builder.store_config(
2193 StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
2194 .event_cache_store(event_cache_store),
2195 )
2196 })
2197 .build()
2198 .await;
2199
2200 let (room_event_cache_p0, room_event_cache_p1) = {
2202 client_p0.base_client().get_or_create_room(room_id, RoomState::Joined);
2203 client_p1.base_client().get_or_create_room(room_id, RoomState::Joined);
2204
2205 let (room_event_cache_p0, _drop_handles) =
2206 client_p0.event_cache().unwrap().room(room_id).await.unwrap();
2207 let (room_event_cache_p1, _drop_handles) =
2208 client_p1.event_cache().unwrap().room(room_id).await.unwrap();
2209
2210 (room_event_cache_p0, room_event_cache_p1)
2211 };
2212
2213 let mut updates_stream_p0 = {
2218 let room_event_cache = &room_event_cache_p0;
2219
2220 let (initial_updates, mut updates_stream) =
2221 room_event_cache_p0.subscribe().await.unwrap();
2222
2223 assert_eq!(initial_updates.len(), 1);
2225 assert_eq!(initial_updates[0].event_id(), Some(ev_id_1));
2226 assert!(updates_stream.is_empty());
2227
2228 assert!(event_loaded(room_event_cache, ev_id_1).await);
2230
2231 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2233
2234 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2236
2237 assert_matches!(
2239 updates_stream.recv().await.unwrap(),
2240 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2241 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2242 assert_matches!(
2243 &diffs[0],
2244 VectorDiff::Insert { index: 0, value: event } => {
2245 assert_eq!(event.event_id(), Some(ev_id_0));
2246 }
2247 );
2248 }
2249 );
2250
2251 assert!(event_loaded(room_event_cache, ev_id_0).await);
2253
2254 updates_stream
2255 };
2256
2257 let mut updates_stream_p1 = {
2259 let room_event_cache = &room_event_cache_p1;
2260 let (initial_updates, mut updates_stream) =
2261 room_event_cache_p1.subscribe().await.unwrap();
2262
2263 assert_eq!(initial_updates.len(), 1);
2265 assert_eq!(initial_updates[0].event_id(), Some(ev_id_1));
2266 assert!(updates_stream.is_empty());
2267
2268 assert!(event_loaded(room_event_cache, ev_id_1).await);
2270
2271 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2273
2274 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2276
2277 assert_matches!(
2279 updates_stream.recv().await.unwrap(),
2280 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2281 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2282 assert_matches!(
2283 &diffs[0],
2284 VectorDiff::Insert { index: 0, value: event } => {
2285 assert_eq!(event.event_id(), Some(ev_id_0));
2286 }
2287 );
2288 }
2289 );
2290
2291 assert!(event_loaded(room_event_cache, ev_id_0).await);
2293
2294 updates_stream
2295 };
2296
2297 for _ in 0..3 {
2299 {
2303 let room_event_cache = &room_event_cache_p0;
2304 let updates_stream = &mut updates_stream_p0;
2305
2306 assert!(event_loaded(room_event_cache, ev_id_1).await);
2308
2309 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2312
2313 assert_matches!(
2315 updates_stream.recv().await.unwrap(),
2316 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2317 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2318 assert_matches!(&diffs[0], VectorDiff::Clear);
2319 assert_matches!(
2320 &diffs[1],
2321 VectorDiff::Append { values: events } => {
2322 assert_eq!(events.len(), 1);
2323 assert_eq!(events[0].event_id(), Some(ev_id_1));
2324 }
2325 );
2326 }
2327 );
2328
2329 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2331
2332 assert!(event_loaded(room_event_cache, ev_id_0).await);
2334
2335 assert_matches!(
2337 updates_stream.recv().await.unwrap(),
2338 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2339 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2340 assert_matches!(
2341 &diffs[0],
2342 VectorDiff::Insert { index: 0, value: event } => {
2343 assert_eq!(event.event_id(), Some(ev_id_0));
2344 }
2345 );
2346 }
2347 );
2348 }
2349
2350 {
2354 let room_event_cache = &room_event_cache_p1;
2355 let updates_stream = &mut updates_stream_p1;
2356
2357 assert!(event_loaded(room_event_cache, ev_id_1).await);
2359
2360 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2363
2364 assert_matches!(
2366 updates_stream.recv().await.unwrap(),
2367 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2368 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2369 assert_matches!(&diffs[0], VectorDiff::Clear);
2370 assert_matches!(
2371 &diffs[1],
2372 VectorDiff::Append { values: events } => {
2373 assert_eq!(events.len(), 1);
2374 assert_eq!(events[0].event_id(), Some(ev_id_1));
2375 }
2376 );
2377 }
2378 );
2379
2380 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2382
2383 assert!(event_loaded(room_event_cache, ev_id_0).await);
2385
2386 assert_matches!(
2388 updates_stream.recv().await.unwrap(),
2389 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2390 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2391 assert_matches!(
2392 &diffs[0],
2393 VectorDiff::Insert { index: 0, value: event } => {
2394 assert_eq!(event.event_id(), Some(ev_id_0));
2395 }
2396 );
2397 }
2398 );
2399 }
2400 }
2401
2402 for _ in 0..3 {
2405 {
2406 let room_event_cache = &room_event_cache_p0;
2407 let updates_stream = &mut updates_stream_p0;
2408
2409 let guard = room_event_cache.inner.state.read().await.unwrap();
2410
2411 assert!(guard.is_dirty().not());
2417
2418 assert_matches!(
2420 updates_stream.recv().await.unwrap(),
2421 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2422 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2423 assert_matches!(&diffs[0], VectorDiff::Clear);
2424 assert_matches!(
2425 &diffs[1],
2426 VectorDiff::Append { values: events } => {
2427 assert_eq!(events.len(), 1);
2428 assert_eq!(events[0].event_id(), Some(ev_id_1));
2429 }
2430 );
2431 }
2432 );
2433
2434 assert!(event_loaded(room_event_cache, ev_id_1).await);
2435 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2436
2437 drop(guard);
2443
2444 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2445 assert!(event_loaded(room_event_cache, ev_id_0).await);
2446
2447 assert_matches!(
2449 updates_stream.recv().await.unwrap(),
2450 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2451 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2452 assert_matches!(
2453 &diffs[0],
2454 VectorDiff::Insert { index: 0, value: event } => {
2455 assert_eq!(event.event_id(), Some(ev_id_0));
2456 }
2457 );
2458 }
2459 );
2460 }
2461
2462 {
2463 let room_event_cache = &room_event_cache_p1;
2464 let updates_stream = &mut updates_stream_p1;
2465
2466 let guard = room_event_cache.inner.state.read().await.unwrap();
2467
2468 assert!(guard.is_dirty().not());
2473
2474 assert_matches!(
2476 updates_stream.recv().await.unwrap(),
2477 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2478 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2479 assert_matches!(&diffs[0], VectorDiff::Clear);
2480 assert_matches!(
2481 &diffs[1],
2482 VectorDiff::Append { values: events } => {
2483 assert_eq!(events.len(), 1);
2484 assert_eq!(events[0].event_id(), Some(ev_id_1));
2485 }
2486 );
2487 }
2488 );
2489
2490 assert!(event_loaded(room_event_cache, ev_id_1).await);
2491 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2492
2493 drop(guard);
2499
2500 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2501 assert!(event_loaded(room_event_cache, ev_id_0).await);
2502
2503 assert_matches!(
2505 updates_stream.recv().await.unwrap(),
2506 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2507 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2508 assert_matches!(
2509 &diffs[0],
2510 VectorDiff::Insert { index: 0, value: event } => {
2511 assert_eq!(event.event_id(), Some(ev_id_0));
2512 }
2513 );
2514 }
2515 );
2516 }
2517 }
2518
2519 for _ in 0..3 {
2521 {
2522 let room_event_cache = &room_event_cache_p0;
2523 let updates_stream = &mut updates_stream_p0;
2524
2525 let guard = room_event_cache.inner.state.write().await.unwrap();
2526
2527 assert!(guard.is_dirty().not());
2529
2530 assert_matches!(
2532 updates_stream.recv().await.unwrap(),
2533 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2534 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2535 assert_matches!(&diffs[0], VectorDiff::Clear);
2536 assert_matches!(
2537 &diffs[1],
2538 VectorDiff::Append { values: events } => {
2539 assert_eq!(events.len(), 1);
2540 assert_eq!(events[0].event_id(), Some(ev_id_1));
2541 }
2542 );
2543 }
2544 );
2545
2546 drop(guard);
2549
2550 assert!(event_loaded(room_event_cache, ev_id_1).await);
2551 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2552
2553 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2554 assert!(event_loaded(room_event_cache, ev_id_0).await);
2555
2556 assert_matches!(
2558 updates_stream.recv().await.unwrap(),
2559 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2560 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2561 assert_matches!(
2562 &diffs[0],
2563 VectorDiff::Insert { index: 0, value: event } => {
2564 assert_eq!(event.event_id(), Some(ev_id_0));
2565 }
2566 );
2567 }
2568 );
2569 }
2570
2571 {
2572 let room_event_cache = &room_event_cache_p1;
2573 let updates_stream = &mut updates_stream_p1;
2574
2575 let guard = room_event_cache.inner.state.write().await.unwrap();
2576
2577 assert!(guard.is_dirty().not());
2579
2580 assert_matches!(
2582 updates_stream.recv().await.unwrap(),
2583 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2584 assert_eq!(diffs.len(), 2, "{diffs:#?}");
2585 assert_matches!(&diffs[0], VectorDiff::Clear);
2586 assert_matches!(
2587 &diffs[1],
2588 VectorDiff::Append { values: events } => {
2589 assert_eq!(events.len(), 1);
2590 assert_eq!(events[0].event_id(), Some(ev_id_1));
2591 }
2592 );
2593 }
2594 );
2595
2596 drop(guard);
2599
2600 assert!(event_loaded(room_event_cache, ev_id_1).await);
2601 assert!(event_loaded(room_event_cache, ev_id_0).await.not());
2602
2603 room_event_cache.pagination().run_backwards_once(1).await.unwrap();
2604 assert!(event_loaded(room_event_cache, ev_id_0).await);
2605
2606 assert_matches!(
2608 updates_stream.recv().await.unwrap(),
2609 RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
2610 assert_eq!(diffs.len(), 1, "{diffs:#?}");
2611 assert_matches!(
2612 &diffs[0],
2613 VectorDiff::Insert { index: 0, value: event } => {
2614 assert_eq!(event.event_id(), Some(ev_id_0));
2615 }
2616 );
2617 }
2618 );
2619 }
2620 }
2621 }
2622
2623 #[async_test]
2624 async fn test_load_when_dirty() {
2625 let room_id_0 = room_id!("!raclette:patate.ch");
2626 let room_id_1 = room_id!("!morbiflette:patate.ch");
2627
2628 let event_cache_store = MemoryStore::new();
2630
2631 let client_p0 = MockClientBuilder::new(None)
2633 .on_builder(|builder| {
2634 builder.store_config(
2635 StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
2636 .event_cache_store(event_cache_store.clone()),
2637 )
2638 })
2639 .build()
2640 .await;
2641
2642 let client_p1 = MockClientBuilder::new(None)
2644 .on_builder(|builder| {
2645 builder.store_config(
2646 StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
2647 .event_cache_store(event_cache_store),
2648 )
2649 })
2650 .build()
2651 .await;
2652
2653 let (room_event_cache_0_p0, room_event_cache_0_p1) = {
2655 client_p0.base_client().get_or_create_room(room_id_0, RoomState::Joined);
2656 client_p0.base_client().get_or_create_room(room_id_1, RoomState::Joined);
2657
2658 client_p1.base_client().get_or_create_room(room_id_0, RoomState::Joined);
2659 client_p1.base_client().get_or_create_room(room_id_1, RoomState::Joined);
2660
2661 let (room_event_cache_0_p0, _drop_handles) =
2662 client_p0.event_cache().unwrap().room(room_id_0).await.unwrap();
2663 let (room_event_cache_0_p1, _drop_handles) =
2664 client_p1.event_cache().unwrap().room(room_id_0).await.unwrap();
2665
2666 (room_event_cache_0_p0, room_event_cache_0_p1)
2667 };
2668
2669 {
2671 drop(room_event_cache_0_p0.inner.state.read().await.unwrap());
2672 drop(room_event_cache_0_p1.inner.state.read().await.unwrap());
2673 }
2674
2675 let (room_event_cache_1_p0, _) =
2679 client_p0.event_cache().unwrap().room(room_id_1).await.unwrap();
2680
2681 {
2683 let guard = room_event_cache_1_p0.inner.state.read().await.unwrap();
2684 assert!(guard.is_dirty().not());
2685 }
2686
2687 }
2690
2691 #[async_test]
2692 async fn test_uniq_read_marker() {
2693 let client = MockClientBuilder::new(None).build().await;
2694 let room_id = room_id!("!galette:saucisse.bzh");
2695 client.base_client().get_or_create_room(room_id, RoomState::Joined);
2696
2697 let event_cache = client.event_cache().unwrap();
2698
2699 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
2700 let (room_event_cache, _drop_handles) = event_cache.room(room_id).await.unwrap();
2701 let (events, mut stream) = room_event_cache.subscribe().await.unwrap();
2702
2703 assert!(events.is_empty());
2704
2705 let read_marker_event = Raw::from_json_string(
2707 json!({
2708 "content": {
2709 "event_id": "$crepe:saucisse.bzh"
2710 },
2711 "room_id": "!galette:saucisse.bzh",
2712 "type": "m.fully_read"
2713 })
2714 .to_string(),
2715 )
2716 .unwrap();
2717 let account_data = vec![read_marker_event; 100];
2718
2719 room_event_cache
2720 .handle_joined_room_update(
2721 Default::default(),
2722 MaybeReceiptEventContent::none(),
2723 account_data,
2724 Default::default(),
2725 Default::default(),
2726 )
2727 .await
2728 .unwrap();
2729
2730 assert_matches!(
2732 stream.recv().await.unwrap(),
2733 RoomEventCacheUpdate::MoveReadMarkerTo { .. }
2734 );
2735
2736 assert!(stream.recv().now_or_never().is_none());
2737
2738 assert!(generic_stream.recv().now_or_never().is_none());
2740 }
2741
2742 async fn event_loaded(room_event_cache: &RoomEventCache, event_id: &EventId) -> bool {
2743 room_event_cache
2744 .rfind_map_event_in_memory_by(|event| {
2745 (event.event_id() == Some(event_id)).then_some(())
2746 })
2747 .await
2748 .unwrap()
2749 .is_some()
2750 }
2751}