1#![forbid(missing_docs)]
29
30use std::{
31 collections::HashMap,
32 fmt,
33 num::NonZeroUsize,
34 ops::Deref,
35 sync::{Arc, OnceLock, RwLock as StdRwLock, RwLockReadGuard, RwLockWriteGuard},
36};
37
38use matrix_sdk_base::{
39 cross_process_lock::{CrossProcessLockError, MappedCrossProcessLockState},
40 event_cache::{
41 store::{EventCacheStoreError, EventCacheStoreLock},
42 thread::ThreadInfo,
43 },
44 linked_chunk::lazy_loader::LazyLoaderError,
45 sync::RoomUpdates,
46 task_monitor::BackgroundTaskHandle,
47};
48use ruma::{EventId, OwnedEventId, OwnedRoomId, RoomId};
49use tokio::sync::{
50 OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock,
51 broadcast::{Receiver, Sender, channel},
52 mpsc,
53};
54use tracing::{error, instrument, trace};
55
56use crate::{
57 Client,
58 client::{ClientInner, WeakClient},
59 paginators::PaginatorError,
60};
61
62pub(crate) mod back_pagination_queue;
63mod caches;
64mod deduplicator;
65mod persistence;
66#[cfg(feature = "e2e-encryption")]
67mod redecryptor;
68mod search_backfill;
69mod states;
70mod tasks;
71
72#[cfg(feature = "e2e-encryption")]
73pub use redecryptor::{DecryptionRetryRequest, RedecryptorReport};
74
75pub use self::{
76 back_pagination_queue::BackPaginationQueue,
77 caches::{
78 TimelineVectorDiffs,
79 event_focused::{EventFocusThreadMode, EventFocusedCache, EventFocusedCacheKey},
80 pagination::{BackPaginationOutcome, PaginationStatus},
81 pinned_events::PinnedEventsCache,
82 room::{
83 RoomEventCache, RoomEventCacheGenericUpdate, RoomEventCacheUpdate,
84 pagination::RoomPagination,
85 },
86 subscriber::Subscriber,
87 thread::{ThreadEventCache, ThreadEventCacheUpdate, pagination::ThreadPagination},
88 },
89 search_backfill::SearchBackfillStrategy,
90};
91use self::{
92 caches::{Caches, room::RoomEventCacheLinkedChunkUpdate, subscriber::AutoShrinkMessage},
93 states::StateLock,
94};
95
96#[derive(thiserror::Error, Clone, Debug)]
98pub enum EventCacheError {
99 #[error("Room cache `{room_id}` is not found.")]
101 RoomNotFound {
102 room_id: OwnedRoomId,
104 },
105
106 #[error("Thread cache `{thread_id}` of room `{room_id}` is not found.")]
108 ThreadNotFound {
109 room_id: OwnedRoomId,
111
112 thread_id: OwnedEventId,
114 },
115
116 #[error("Pinned-events cache for room `{room_id}` are not found.")]
118 PinnedEventsNotFound {
119 room_id: OwnedRoomId,
121 },
122
123 #[error("Event-focused cache `{event_focused_id:?}` of room `{room_id}` is not found.")]
125 EventFocusedNotFound {
126 room_id: OwnedRoomId,
128
129 event_focused_id: EventFocusedCacheKey,
131 },
132
133 #[error("The state of a cache is not found")]
136 CacheStateAlreadyExists,
137
138 #[error(transparent)]
140 PaginationError(Arc<crate::Error>),
141
142 #[error(transparent)]
144 InitialPaginationError(#[from] PaginatorError),
145
146 #[error(transparent)]
148 Storage(#[from] EventCacheStoreError),
149
150 #[error(transparent)]
152 LockingStorage(#[from] CrossProcessLockError),
153
154 #[error("The owning client of the event cache has been dropped.")]
158 ClientDropped,
159
160 #[error(transparent)]
165 LinkedChunkLoader(#[from] LazyLoaderError),
166
167 #[error("Unable to load any of the pinned events.")]
171 UnableToLoadPinnedEvents,
172
173 #[error("the linked chunk metadata is invalid: {details}")]
176 InvalidLinkedChunkMetadata {
177 details: String,
179 },
180}
181
182pub type Result<T> = std::result::Result<T, EventCacheError>;
184
185pub struct EventCacheDropHandles {
187 _ignore_user_list_update_task: BackgroundTaskHandle,
189
190 _auto_shrink_linked_chunk_task: BackgroundTaskHandle,
192
193 _thread_subscriber_task: BackgroundTaskHandle,
200
201 #[cfg(feature = "experimental-search")]
207 _search_indexing_task: BackgroundTaskHandle,
208
209 #[cfg(feature = "e2e-encryption")]
211 redecryptor: OnceLock<redecryptor::Redecryptor>,
212}
213
214impl EventCacheDropHandles {
215 fn new(
216 event_cache_inner: &Arc<EventCacheInner>,
217 event_cache_config: RwLockReadGuard<'_, EventCacheConfig>,
218 auto_shrink_receiver: mpsc::Receiver<AutoShrinkMessage>,
219 client: Client,
220 ) -> Self {
221 let task_monitor = client.task_monitor();
222
223 let ignore_user_list_update_task = task_monitor
224 .spawn_infinite_task(
225 "event_cache::ignore_user_list_update_task",
226 tasks::ignore_user_list_update_task(
227 event_cache_inner.clone(),
228 client.subscribe_to_ignore_user_list_changes(),
229 ),
230 )
231 .abort_on_drop();
232
233 let auto_shrink_linked_chunk_task = task_monitor
234 .spawn_infinite_task(
235 "event_cache::auto_shrink_linked_chunk_task",
236 tasks::auto_shrink_linked_chunk_task(
237 Arc::downgrade(event_cache_inner),
238 auto_shrink_receiver,
239 ),
240 )
241 .abort_on_drop();
242
243 let thread_subscriber_task = client
244 .task_monitor()
245 .spawn_infinite_task(
246 "event_cache::thread_subscriber",
247 tasks::thread_subscriber_task(
248 event_cache_inner.client.clone(),
249 event_cache_inner.linked_chunk_update_sender.clone(),
250 event_cache_inner.thread_subscriber_sender.clone(),
251 ),
252 )
253 .abort_on_drop();
254
255 #[cfg(feature = "experimental-search")]
256 let search_indexing_task = client
257 .task_monitor()
258 .spawn_infinite_task(
259 "event_cache::search_indexing",
260 tasks::search_indexing_task(
261 event_cache_inner.client.clone(),
262 event_cache_inner.linked_chunk_update_sender.clone(),
263 ),
264 )
265 .abort_on_drop();
266
267 if event_cache_inner.enable_automatic_back_pagination {
268 trace!("spawning the back-pagination queue");
270 let max_concurrent = event_cache_config.max_concurrent_back_paginations;
271
272 event_cache_inner.back_pagination_queue.get_or_init(|| {
273 BackPaginationQueue::new(
274 Arc::downgrade(event_cache_inner),
275 max_concurrent,
276 task_monitor,
277 )
278 });
279 } else {
280 trace!("back-pagination queue is disabled");
281 }
282
283 Self {
284 _ignore_user_list_update_task: ignore_user_list_update_task,
285 _auto_shrink_linked_chunk_task: auto_shrink_linked_chunk_task,
286 #[cfg(feature = "e2e-encryption")]
289 redecryptor: OnceLock::new(),
290 _thread_subscriber_task: thread_subscriber_task,
291 #[cfg(feature = "experimental-search")]
292 _search_indexing_task: search_indexing_task,
293 }
294 }
295}
296
297impl fmt::Debug for EventCacheDropHandles {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 f.debug_struct("EventCacheDropHandles").finish_non_exhaustive()
300 }
301}
302
303#[derive(Clone)]
309pub struct EventCache {
310 inner: Arc<EventCacheInner>,
314
315 drop_handles: Arc<EventCacheDropHandles>,
320}
321
322impl fmt::Debug for EventCache {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 f.debug_struct("EventCache").finish_non_exhaustive()
325 }
326}
327
328impl EventCache {
329 pub(crate) fn new(
331 client: &Arc<ClientInner>,
332 event_cache_store: EventCacheStoreLock,
333 enable_automatic_back_pagination: bool,
334 ) -> Self {
335 let (generic_update_sender, _) = channel(128);
336 let (linked_chunk_update_sender, _) = channel(128);
337
338 let weak_client = WeakClient::from_inner(client);
339 let client =
340 weak_client.get().expect("Unreachable: `client` is still alive, it can be upgraded");
341
342 let (auto_shrink_sender, auto_shrink_receiver) = mpsc::channel(32);
343
344 let (thread_subscriber_sender, _thread_subscriber_receiver) = channel(128);
345
346 #[cfg(feature = "e2e-encryption")]
347 let redecryption_channels = redecryptor::RedecryptorChannels::new();
348
349 let inner = Arc::new(EventCacheInner {
350 client: weak_client,
351 config: StdRwLock::new(EventCacheConfig::default()),
352 state: StateLock::new(event_cache_store),
353 by_room: Default::default(),
354 auto_shrink_sender,
355 generic_update_sender,
356 linked_chunk_update_sender,
357 #[cfg(feature = "e2e-encryption")]
358 redecryption_channels,
359 enable_automatic_back_pagination,
360 back_pagination_queue: OnceLock::new(),
361 thread_subscriber_sender,
362 });
363
364 let drop_handles = Arc::new(EventCacheDropHandles::new(
365 &inner,
366 inner.config.read().unwrap(),
367 auto_shrink_receiver,
368 client,
369 ));
370
371 Self { inner, drop_handles }
372 }
373
374 pub fn config(&self) -> RwLockReadGuard<'_, EventCacheConfig> {
377 self.inner.config.read().unwrap()
378 }
379
380 pub fn config_mut(&self) -> RwLockWriteGuard<'_, EventCacheConfig> {
382 self.inner.config.write().unwrap()
383 }
384
385 #[cfg(feature = "e2e-encryption")]
398 pub(crate) fn initialise_redecryptor(&self) -> Result<()> {
399 let client = self.inner.client()?;
400 let receiver =
401 self.inner.redecryption_channels.decryption_request_receiver.lock().take().expect(
402 "We should have initialized the channel an subscribing should happen only once",
403 );
404
405 self.drop_handles.redecryptor.get_or_init(|| {
406 redecryptor::Redecryptor::new(
407 &client,
408 Arc::downgrade(&self.inner),
409 receiver,
410 &self.inner.linked_chunk_update_sender,
411 )
412 });
413
414 Ok(())
415 }
416
417 #[cfg(feature = "testing")]
421 pub fn subscribe_thread_subscriber_updates(&self) -> Receiver<()> {
422 self.inner.thread_subscriber_sender.subscribe()
423 }
424
425 #[cfg(feature = "testing")]
430 pub async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
431 self.inner.handle_room_updates(updates).await
432 }
433
434 #[cfg(not(feature = "testing"))]
439 pub(crate) async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
440 self.inner.handle_room_updates(updates).await
441 }
442
443 pub async fn room(
445 &self,
446 room_id: &RoomId,
447 ) -> Result<(RoomEventCache, Arc<EventCacheDropHandles>)> {
448 let drop_handles = self.drop_handles.clone();
449
450 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
451
452 Ok((caches_for_room.room().clone(), drop_handles))
453 }
454
455 pub async fn thread(
457 &self,
458 room_id: &RoomId,
459 thread_id: &EventId,
460 ) -> Result<(ThreadEventCache, Arc<EventCacheDropHandles>)> {
461 let drop_handles = self.drop_handles.clone();
462
463 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
464
465 Ok((caches_for_room.thread(thread_id.to_owned()).await?.deref().clone(), drop_handles))
466 }
467
468 pub async fn pinned_events(
470 &self,
471 room_id: &RoomId,
472 ) -> Result<(PinnedEventsCache, Arc<EventCacheDropHandles>)> {
473 let drop_handles = self.drop_handles.clone();
474
475 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
476
477 Ok((caches_for_room.pinned_events().await?.clone(), drop_handles))
478 }
479
480 pub async fn event_focused(
482 &self,
483 room_id: &RoomId,
484 event_id: &EventId,
485 thread_mode: EventFocusThreadMode,
486 number_of_initial_events: u16,
487 ) -> Result<(EventFocusedCache, Arc<EventCacheDropHandles>)> {
488 let drop_handles = self.drop_handles.clone();
489
490 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
491
492 Ok((
493 caches_for_room
494 .event_focused(event_id.to_owned(), thread_mode, number_of_initial_events)
495 .await?
496 .deref()
497 .clone(),
498 drop_handles,
499 ))
500 }
501
502 pub async fn thread_info(
504 &self,
505 room_id: &RoomId,
506 thread_id: &EventId,
507 ) -> Result<Option<ThreadInfo>> {
508 let Some(client) = self.inner.client.get() else {
509 return Ok(None);
510 };
511
512 let store = match client.event_cache_store().lock().await? {
516 MappedCrossProcessLockState::Clean(store)
517 | MappedCrossProcessLockState::Dirty(store) => store,
518 };
519
520 Ok(store.load_thread_info(room_id, thread_id, false).await?)
521 }
522
523 pub async fn forget_room(&self, room_id: &RoomId) -> Result<()> {
527 self.inner.forget_room(room_id).await
528 }
529
530 pub async fn clear_all_rooms(&self) -> Result<()> {
534 self.inner.clear_all_rooms().await
535 }
536
537 pub fn subscribe_to_room_generic_updates(&self) -> Receiver<RoomEventCacheGenericUpdate> {
547 self.inner.generic_update_sender.subscribe()
548 }
549
550 pub fn back_pagination_queue(&self) -> Option<BackPaginationQueue> {
555 self.inner.back_pagination_queue.get().cloned()
556 }
557}
558
559#[derive(Clone, Copy, Debug)]
561pub struct EventCacheConfig {
562 pub max_pinned_events_concurrent_requests: usize,
564
565 pub max_pinned_events_to_load: usize,
567
568 pub max_concurrent_back_paginations: NonZeroUsize,
574}
575
576impl EventCacheConfig {
577 pub const DEFAULT_MAX_EVENTS_TO_LOAD: usize = 128;
579
580 pub const DEFAULT_MAX_CONCURRENT_REQUESTS: usize = 8;
583
584 pub const DEFAULT_MAX_CONCURRENT_BACK_PAGINATIONS: NonZeroUsize = NonZeroUsize::new(3).unwrap();
587}
588
589impl Default for EventCacheConfig {
590 fn default() -> Self {
591 Self {
592 max_pinned_events_concurrent_requests: Self::DEFAULT_MAX_CONCURRENT_REQUESTS,
593 max_pinned_events_to_load: Self::DEFAULT_MAX_EVENTS_TO_LOAD,
594 max_concurrent_back_paginations: Self::DEFAULT_MAX_CONCURRENT_BACK_PAGINATIONS,
595 }
596 }
597}
598
599type CachesByRoom = HashMap<OwnedRoomId, Caches>;
600
601struct EventCacheInner {
602 client: WeakClient,
605
606 config: StdRwLock<EventCacheConfig>,
608
609 state: StateLock,
612
613 by_room: Arc<RwLock<CachesByRoom>>,
617
618 auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
625
626 generic_update_sender: Sender<RoomEventCacheGenericUpdate>,
631
632 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
640
641 thread_subscriber_sender: Sender<()>,
647
648 #[cfg(feature = "e2e-encryption")]
649 redecryption_channels: redecryptor::RedecryptorChannels,
650
651 enable_automatic_back_pagination: bool,
657
658 back_pagination_queue: OnceLock<BackPaginationQueue>,
663}
664
665impl EventCacheInner {
666 fn client(&self) -> Result<Client> {
667 self.client.get().ok_or(EventCacheError::ClientDropped)
668 }
669
670 async fn forget_room(&self, room_id: &RoomId) -> Result<()> {
672 let mut caches_for_all_rooms = self.by_room.write().await;
676 self.state.clear_and_reload(&caches_for_all_rooms, Some(room_id)).await?;
677
678 caches_for_all_rooms.remove(room_id);
680
681 Ok(())
682 }
683
684 async fn clear_all_rooms(&self) -> Result<()> {
686 let caches_for_all_rooms = self.by_room.write().await;
714
715 self.state.clear_and_reload(&caches_for_all_rooms, None).await?;
717
718 Ok(())
719 }
720
721 #[instrument(skip(self, updates))]
723 async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
724 for (room_id, left_room_update) in updates.left {
731 let Ok(caches) = self.all_caches_for_room(&room_id).await else {
732 error!(?room_id, "Room must exist");
733 continue;
734 };
735
736 if let Err(err) = caches.handle_left_room_update(left_room_update).await {
737 error!("handling left room update: {err}");
739 }
740 }
741
742 for (room_id, joined_room_update) in updates.joined {
744 trace!(?room_id, "Handling a `JoinedRoomUpdate`");
745
746 let Ok(caches) = self.all_caches_for_room(&room_id).await else {
747 error!(?room_id, "Room must exist");
748 continue;
749 };
750
751 if let Err(err) = caches.handle_joined_room_update(joined_room_update).await {
752 error!(%room_id, "handling joined room update: {err}");
754 }
755 }
756
757 Ok(())
763 }
764
765 async fn all_caches_for_room(
767 &self,
768 room_id: &RoomId,
769 ) -> Result<OwnedRwLockReadGuard<CachesByRoom, Caches>> {
770 match OwnedRwLockReadGuard::try_map(self.by_room.clone().read_owned().await, |by_room| {
773 by_room.get(room_id)
774 }) {
775 Ok(caches) => Ok(caches),
776
777 Err(by_room_guard) => {
778 drop(by_room_guard);
781 let by_room_guard = self.by_room.clone().write_owned().await;
782
783 let mut by_room_guard =
786 match OwnedRwLockWriteGuard::try_downgrade_map(by_room_guard, |by_room| {
787 by_room.get(room_id)
788 }) {
789 Ok(caches) => return Ok(caches),
790 Err(by_room_guard) => by_room_guard,
791 };
792
793 let caches = Caches::new(
794 &self.client,
795 room_id,
796 self.generic_update_sender.clone(),
797 self.linked_chunk_update_sender.clone(),
798 self.auto_shrink_sender.clone(),
799 &self.state,
800 self.back_pagination_queue.get().cloned(),
801 )
802 .await?;
803
804 by_room_guard.insert(room_id.to_owned(), caches);
805
806 Ok(OwnedRwLockWriteGuard::try_downgrade_map(by_room_guard, |by_room| {
807 by_room.get(room_id)
808 })
809 .expect("`Caches` has just been inserted"))
810 }
811 }
812 }
813}
814
815#[derive(Debug, Clone)]
817pub enum EventsOrigin {
818 Sync,
820
821 Pagination,
823
824 Cache,
826}
827
828#[cfg(test)]
829mod tests {
830 use std::{ops::Not, sync::Arc, time::Duration};
831
832 use assert_matches::assert_matches;
833 use futures_util::FutureExt as _;
834 use matrix_sdk_base::{
835 RoomState,
836 cross_process_lock::CrossProcessLockConfig,
837 event_cache::store::{EventCacheStore, MemoryStore},
838 linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update},
839 store::StoreConfig,
840 sync::{JoinedRoomUpdate, RoomUpdates, Timeline},
841 };
842 use matrix_sdk_test::{
843 JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
844 };
845 use ruma::{event_id, room_id, user_id};
846 use tokio::time::sleep;
847
848 use super::{EventCacheError, RoomEventCacheGenericUpdate};
849 use crate::test_utils::{
850 assert_event_matches_msg, client::MockClientBuilder, logged_in_client,
851 };
852
853 #[async_test]
854 async fn test_get_event_by_id() {
855 let client = logged_in_client(None).await;
856 let room_id1 = room_id!("!galette:saucisse.bzh");
857 let room_id2 = room_id!("!crepe:saucisse.bzh");
858
859 client.base_client().get_or_create_room(room_id1, RoomState::Joined);
860 client.base_client().get_or_create_room(room_id2, RoomState::Joined);
861
862 let event_cache = client.event_cache().unwrap();
863
864 let f = EventFactory::new().room(room_id1).sender(user_id!("@ben:saucisse.bzh"));
866
867 let eid1 = event_id!("$1");
868 let eid2 = event_id!("$2");
869 let eid3 = event_id!("$3");
870
871 let joined_room_update1 = JoinedRoomUpdate {
872 timeline: Timeline {
873 events: vec![
874 f.text_msg("hey").event_id(eid1).into(),
875 f.text_msg("you").event_id(eid2).into(),
876 ],
877 ..Default::default()
878 },
879 ..Default::default()
880 };
881
882 let joined_room_update2 = JoinedRoomUpdate {
883 timeline: Timeline {
884 events: vec![f.text_msg("bjr").event_id(eid3).into()],
885 ..Default::default()
886 },
887 ..Default::default()
888 };
889
890 let mut updates = RoomUpdates::default();
891 updates.joined.insert(room_id1.to_owned(), joined_room_update1);
892 updates.joined.insert(room_id2.to_owned(), joined_room_update2);
893
894 event_cache.inner.handle_room_updates(updates).await.unwrap();
896
897 let (room_event_cache, _drop_handles) = event_cache.room(room_id1).await.unwrap();
899
900 let found1 = room_event_cache.find_event(eid1).await.unwrap().unwrap();
901 assert_event_matches_msg(&found1, "hey");
902
903 let found2 = room_event_cache.find_event(eid2).await.unwrap().unwrap();
904 assert_event_matches_msg(&found2, "you");
905
906 assert!(room_event_cache.find_event(eid3).await.unwrap().is_none());
909 }
910
911 #[async_test]
912 async fn test_generic_update_when_loading_rooms() {
913 let user = user_id!("@mnt_io:matrix.org");
915
916 let room_id_0 = room_id!("!raclette:patate.ch");
917 let room_id_1 = room_id!("!fondue:patate.ch");
918
919 let event_factory = EventFactory::new().room(room_id_0).sender(user);
920
921 let event_cache_store = MemoryStore::new();
922 event_cache_store
923 .handle_linked_chunk_updates(
924 LinkedChunkId::Room(room_id_0),
925 vec![
926 Update::NewItemsChunk {
928 previous: None,
929 new: ChunkIdentifier::new(0),
930 next: None,
931 },
932 Update::PushItems {
933 at: Position::new(ChunkIdentifier::new(0), 0),
934 items: vec![
935 event_factory
936 .text_msg("hello")
937 .sender(user)
938 .event_id(event_id!("$ev0"))
939 .into_event(),
940 ],
941 },
942 ],
943 )
944 .await
945 .unwrap();
946
947 let client = MockClientBuilder::new(None)
948 .on_builder(move |builder| {
949 builder.store_config(
950 StoreConfig::new(CrossProcessLockConfig::MultiProcess {
951 holder_name: "foo".to_owned(),
952 })
953 .event_cache_store(event_cache_store),
954 )
955 })
956 .build()
957 .await;
958
959 let event_cache = client.event_cache().unwrap();
960
961 client.base_client().get_or_create_room(room_id_0, RoomState::Joined);
962 client.base_client().get_or_create_room(room_id_1, RoomState::Joined);
963
964 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
965
966 {
968 let _room_event_cache = event_cache.room(room_id_0).await.unwrap();
969
970 assert_matches!(
971 generic_stream.recv().await,
972 Ok(RoomEventCacheGenericUpdate { room_id }) => {
973 assert_eq!(room_id, room_id_0);
974 }
975 );
976 }
977
978 {
980 let _room_event_cache = event_cache.room(room_id_1).await.unwrap();
981
982 assert!(generic_stream.recv().now_or_never().is_none());
983 }
984 }
985
986 #[async_test]
987 async fn test_generic_update_when_paginating_room() {
988 let user = user_id!("@mnt_io:matrix.org");
990 let room_id = room_id!("!raclette:patate.ch");
991
992 let event_factory = EventFactory::new().room(room_id).sender(user);
993
994 let event_cache_store = MemoryStore::new();
995 event_cache_store
996 .handle_linked_chunk_updates(
997 LinkedChunkId::Room(room_id),
998 vec![
999 Update::NewItemsChunk {
1001 previous: None,
1002 new: ChunkIdentifier::new(0),
1003 next: None,
1004 },
1005 Update::NewItemsChunk {
1007 previous: Some(ChunkIdentifier::new(0)),
1008 new: ChunkIdentifier::new(1),
1009 next: None,
1010 },
1011 Update::NewItemsChunk {
1013 previous: Some(ChunkIdentifier::new(1)),
1014 new: ChunkIdentifier::new(2),
1015 next: None,
1016 },
1017 Update::PushItems {
1018 at: Position::new(ChunkIdentifier::new(2), 0),
1019 items: vec![
1020 event_factory
1021 .text_msg("hello")
1022 .sender(user)
1023 .event_id(event_id!("$ev0"))
1024 .into_event(),
1025 ],
1026 },
1027 Update::NewItemsChunk {
1029 previous: Some(ChunkIdentifier::new(2)),
1030 new: ChunkIdentifier::new(3),
1031 next: None,
1032 },
1033 Update::PushItems {
1034 at: Position::new(ChunkIdentifier::new(3), 0),
1035 items: vec![
1036 event_factory
1037 .text_msg("world")
1038 .sender(user)
1039 .event_id(event_id!("$ev1"))
1040 .into_event(),
1041 ],
1042 },
1043 ],
1044 )
1045 .await
1046 .unwrap();
1047
1048 let client = MockClientBuilder::new(None)
1049 .on_builder(move |builder| {
1050 builder.store_config(
1051 StoreConfig::new(CrossProcessLockConfig::MultiProcess {
1052 holder_name: "foo".to_owned(),
1053 })
1054 .event_cache_store(event_cache_store),
1055 )
1056 })
1057 .build()
1058 .await;
1059
1060 let event_cache = client.event_cache().unwrap();
1061
1062 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1063
1064 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1065
1066 let (room_event_cache, _) = event_cache.room(room_id).await.unwrap();
1068
1069 assert_matches!(
1070 generic_stream.recv().await,
1071 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1072 assert_eq!(room_id, expected_room_id);
1073 }
1074 );
1075
1076 let pagination = room_event_cache.pagination();
1077
1078 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1080
1081 assert_eq!(pagination_outcome.events.len(), 1);
1082 assert!(pagination_outcome.reached_start.not());
1083 assert_matches!(
1084 generic_stream.recv().await,
1085 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1086 assert_eq!(room_id, expected_room_id);
1087 }
1088 );
1089
1090 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1092
1093 assert!(pagination_outcome.events.is_empty());
1094 assert!(pagination_outcome.reached_start.not());
1095 assert!(generic_stream.recv().now_or_never().is_none());
1096
1097 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1099
1100 assert!(pagination_outcome.reached_start);
1101 assert!(generic_stream.recv().now_or_never().is_none());
1102 }
1103
1104 #[async_test]
1105 async fn test_for_room_when_room_is_not_found() {
1106 let client = logged_in_client(None).await;
1107 let room_id = room_id!("!raclette:patate.ch");
1108
1109 let event_cache = client.event_cache().unwrap();
1110
1111 assert_matches!(
1113 event_cache.room(room_id).await,
1114 Err(EventCacheError::RoomNotFound { room_id: not_found_room_id }) => {
1115 assert_eq!(room_id, not_found_room_id);
1116 }
1117 );
1118
1119 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1121
1122 assert!(event_cache.room(room_id).await.is_ok());
1124 }
1125
1126 #[cfg(not(target_family = "wasm"))]
1129 #[async_test]
1130 async fn test_no_refcycle_event_cache_tasks() {
1131 let client = MockClientBuilder::new(None).build().await;
1132
1133 sleep(Duration::from_secs(1)).await;
1135
1136 let event_cache_weak = Arc::downgrade(&client.event_cache().unwrap().inner);
1137 assert_eq!(event_cache_weak.strong_count(), 2);
1138
1139 {
1140 let room_id = room_id!("!room:example.org");
1141
1142 let response = SyncResponseBuilder::default()
1144 .add_joined_room(JoinedRoomBuilder::new(room_id))
1145 .build_sync_response();
1146 client.inner.base_client.receive_sync_response(response).await.unwrap();
1147
1148 let (_room_event_cache, _drop_handles) =
1149 client.event_cache().unwrap().room(room_id).await.unwrap();
1150 }
1151
1152 drop(client);
1153
1154 sleep(Duration::from_secs(1)).await;
1156
1157 assert_eq!(
1159 event_cache_weak.strong_count(),
1160 0,
1161 "Too many strong references to the event cache {}",
1162 event_cache_weak.strong_count()
1163 );
1164 }
1165
1166 #[async_test]
1169 async fn test_cancelled_caches_creation_can_be_retried() {
1170 let client = logged_in_client(None).await;
1171 let room_id_a = room_id!("!a:saucisse.bzh");
1172 let room_id_b = room_id!("!b:saucisse.bzh");
1173
1174 client.base_client().get_or_create_room(room_id_a, RoomState::Joined);
1175 client.base_client().get_or_create_room(room_id_b, RoomState::Joined);
1176
1177 let event_cache = client.event_cache().unwrap();
1178
1179 let (room_a, _drop_handles) = event_cache.room(room_id_a).await.unwrap();
1180
1181 let guard = room_a.state().write().await.unwrap();
1184
1185 let mut create_b = Box::pin(event_cache.room(room_id_b));
1186 assert!(create_b.as_mut().now_or_never().is_none());
1187
1188 let mut other_writer = Box::pin(room_a.state().write());
1190 assert!(other_writer.as_mut().now_or_never().is_none());
1191
1192 drop(guard);
1196 let _ = create_b.as_mut().now_or_never();
1197 drop(create_b);
1198 drop(other_writer);
1199
1200 event_cache.room(room_id_b).await.unwrap();
1202 }
1203}