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::{instrument, trace};
55
56use crate::{
57 Client,
58 client::{ClientInner, WeakClient},
59 event_cache::caches::room::LinkedChunkUpdateSender,
60 paginators::PaginatorError,
61};
62
63pub(crate) mod back_pagination_queue;
64mod caches;
65mod deduplicator;
66mod persistence;
67#[cfg(feature = "e2e-encryption")]
68mod redecryptor;
69mod search_backfill;
70mod states;
71mod tasks;
72
73#[cfg(feature = "e2e-encryption")]
74pub use redecryptor::{DecryptionRetryRequest, RedecryptorReport};
75
76pub(crate) use self::caches::room::RFindMapOutcome;
77pub use self::{
78 back_pagination_queue::BackPaginationQueue,
79 caches::{
80 TimelineVectorDiffs,
81 event_focused::{EventFocusThreadMode, EventFocusedCache, EventFocusedCacheKey},
82 pagination::{BackPaginationOutcome, PaginationStatus},
83 pinned_events::PinnedEventsCache,
84 room::{
85 RoomEventCache, RoomEventCacheGenericUpdate, RoomEventCacheUpdate,
86 pagination::RoomPagination,
87 },
88 subscriber::Subscriber,
89 thread::{ThreadEventCache, ThreadEventCacheUpdate, pagination::ThreadPagination},
90 },
91 search_backfill::SearchBackfillStrategy,
92};
93use self::{
94 caches::{Caches, room::RoomEventCacheLinkedChunkUpdate, subscriber::AutoShrinkMessage},
95 states::StateLock,
96};
97
98#[derive(thiserror::Error, Clone, Debug)]
100pub enum EventCacheError {
101 #[error("Room cache `{room_id}` is not found.")]
103 RoomNotFound {
104 room_id: OwnedRoomId,
106 },
107
108 #[error("Thread cache `{thread_id}` of room `{room_id}` is not found.")]
110 ThreadNotFound {
111 room_id: OwnedRoomId,
113
114 thread_id: OwnedEventId,
116 },
117
118 #[error("Pinned-events cache for room `{room_id}` are not found.")]
120 PinnedEventsNotFound {
121 room_id: OwnedRoomId,
123 },
124
125 #[error("Event-focused cache `{event_focused_id:?}` of room `{room_id}` is not found.")]
127 EventFocusedNotFound {
128 room_id: OwnedRoomId,
130
131 event_focused_id: EventFocusedCacheKey,
133 },
134
135 #[error("The state of a cache is not found")]
138 CacheStateAlreadyExists,
139
140 #[error(transparent)]
142 PaginationError(Arc<crate::Error>),
143
144 #[error(transparent)]
146 InitialPaginationError(#[from] PaginatorError),
147
148 #[error(transparent)]
150 Storage(#[from] EventCacheStoreError),
151
152 #[error(transparent)]
154 LockingStorage(#[from] CrossProcessLockError),
155
156 #[error("The owning client of the event cache has been dropped.")]
160 ClientDropped,
161
162 #[error(transparent)]
167 LinkedChunkLoader(#[from] LazyLoaderError),
168
169 #[error("Unable to load any of the pinned events.")]
173 UnableToLoadPinnedEvents,
174
175 #[error("the linked chunk metadata is invalid: {details}")]
178 InvalidLinkedChunkMetadata {
179 details: String,
181 },
182}
183
184pub type Result<T> = std::result::Result<T, EventCacheError>;
186
187pub struct EventCacheDropHandles {
189 _ignore_user_list_update_task: BackgroundTaskHandle,
191
192 _auto_shrink_linked_chunk_task: BackgroundTaskHandle,
194
195 _thread_subscriber_task: BackgroundTaskHandle,
202
203 #[cfg(feature = "experimental-search")]
209 _search_indexing_task: BackgroundTaskHandle,
210
211 #[cfg(feature = "e2e-encryption")]
213 redecryptor: OnceLock<redecryptor::Redecryptor>,
214}
215
216impl EventCacheDropHandles {
217 fn new(
218 event_cache_inner: &Arc<EventCacheInner>,
219 event_cache_config: RwLockReadGuard<'_, EventCacheConfig>,
220 auto_shrink_receiver: mpsc::Receiver<AutoShrinkMessage>,
221 #[cfg(feature = "experimental-search")] search_index_receiver: mpsc::UnboundedReceiver<(
222 OwnedRoomId,
223 Vec<matrix_sdk_base::event_cache::Event>,
224 )>,
225 client: Client,
226 ) -> Self {
227 let task_monitor = client.task_monitor();
228
229 let ignore_user_list_update_task = task_monitor
230 .spawn_infinite_task(
231 "event_cache::ignore_user_list_update_task",
232 tasks::ignore_user_list_update_task(
233 event_cache_inner.clone(),
234 client.subscribe_to_ignore_user_list_changes(),
235 ),
236 )
237 .abort_on_drop();
238
239 let auto_shrink_linked_chunk_task = task_monitor
240 .spawn_infinite_task(
241 "event_cache::auto_shrink_linked_chunk_task",
242 tasks::auto_shrink_linked_chunk_task(
243 Arc::downgrade(event_cache_inner),
244 auto_shrink_receiver,
245 ),
246 )
247 .abort_on_drop();
248
249 let thread_subscriber_task = client
250 .task_monitor()
251 .spawn_infinite_task(
252 "event_cache::thread_subscriber",
253 tasks::thread_subscriber_task(
254 event_cache_inner.client.clone(),
255 event_cache_inner.linked_chunk_update_sender.clone(),
256 event_cache_inner.thread_subscriber_sender.clone(),
257 ),
258 )
259 .abort_on_drop();
260
261 #[cfg(feature = "experimental-search")]
262 let search_indexing_task = client
263 .task_monitor()
264 .spawn_infinite_task(
265 "event_cache::search_indexing",
266 tasks::search_indexing_task(
267 event_cache_inner.client.clone(),
268 search_index_receiver,
269 ),
270 )
271 .abort_on_drop();
272
273 if event_cache_inner.enable_automatic_back_pagination {
274 trace!("spawning the back-pagination queue");
276 let max_concurrent = event_cache_config.max_concurrent_back_paginations;
277
278 event_cache_inner.back_pagination_queue.get_or_init(|| {
279 BackPaginationQueue::new(
280 Arc::downgrade(event_cache_inner),
281 max_concurrent,
282 task_monitor,
283 )
284 });
285 } else {
286 trace!("back-pagination queue is disabled");
287 }
288
289 Self {
290 _ignore_user_list_update_task: ignore_user_list_update_task,
291 _auto_shrink_linked_chunk_task: auto_shrink_linked_chunk_task,
292 #[cfg(feature = "e2e-encryption")]
295 redecryptor: OnceLock::new(),
296 _thread_subscriber_task: thread_subscriber_task,
297 #[cfg(feature = "experimental-search")]
298 _search_indexing_task: search_indexing_task,
299 }
300 }
301}
302
303impl fmt::Debug for EventCacheDropHandles {
304 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305 f.debug_struct("EventCacheDropHandles").finish_non_exhaustive()
306 }
307}
308
309#[derive(Clone)]
315pub struct EventCache {
316 inner: Arc<EventCacheInner>,
320
321 drop_handles: Arc<EventCacheDropHandles>,
326}
327
328impl fmt::Debug for EventCache {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 f.debug_struct("EventCache").finish_non_exhaustive()
331 }
332}
333
334impl EventCache {
335 pub(crate) fn new(
337 client: &Arc<ClientInner>,
338 event_cache_store: EventCacheStoreLock,
339 enable_automatic_back_pagination: bool,
340 ) -> Self {
341 let (generic_update_sender, _) = channel(128);
342 #[cfg(feature = "experimental-search")]
343 let (search_index_sender, search_index_receiver) = mpsc::unbounded_channel();
344 let linked_chunk_update_sender = LinkedChunkUpdateSender::new(
345 #[cfg(feature = "experimental-search")]
346 search_index_sender,
347 );
348
349 let weak_client = WeakClient::from_inner(client);
350 let client =
351 weak_client.get().expect("Unreachable: `client` is still alive, it can be upgraded");
352
353 let (auto_shrink_sender, auto_shrink_receiver) = mpsc::channel(32);
354
355 let (thread_subscriber_sender, _thread_subscriber_receiver) = channel(128);
356
357 #[cfg(feature = "e2e-encryption")]
358 let redecryption_channels = redecryptor::RedecryptorChannels::new();
359
360 let inner = Arc::new(EventCacheInner {
361 client: weak_client,
362 config: StdRwLock::new(EventCacheConfig::default()),
363 state: StateLock::new(event_cache_store),
364 by_room: Default::default(),
365 auto_shrink_sender,
366 generic_update_sender,
367 linked_chunk_update_sender,
368 #[cfg(feature = "e2e-encryption")]
369 redecryption_channels,
370 enable_automatic_back_pagination,
371 back_pagination_queue: OnceLock::new(),
372 thread_subscriber_sender,
373 });
374
375 let drop_handles = Arc::new(EventCacheDropHandles::new(
376 &inner,
377 inner.config.read().unwrap(),
378 auto_shrink_receiver,
379 #[cfg(feature = "experimental-search")]
380 search_index_receiver,
381 client,
382 ));
383
384 Self { inner, drop_handles }
385 }
386
387 pub fn config(&self) -> RwLockReadGuard<'_, EventCacheConfig> {
390 self.inner.config.read().unwrap()
391 }
392
393 pub fn config_mut(&self) -> RwLockWriteGuard<'_, EventCacheConfig> {
395 self.inner.config.write().unwrap()
396 }
397
398 #[cfg(feature = "e2e-encryption")]
411 pub(crate) fn initialise_redecryptor(&self) -> Result<()> {
412 let client = self.inner.client()?;
413 let receiver =
414 self.inner.redecryption_channels.decryption_request_receiver.lock().take().expect(
415 "We should have initialized the channel an subscribing should happen only once",
416 );
417
418 self.drop_handles.redecryptor.get_or_init(|| {
419 redecryptor::Redecryptor::new(
420 &client,
421 Arc::downgrade(&self.inner),
422 receiver,
423 &self.inner.linked_chunk_update_sender,
424 )
425 });
426
427 Ok(())
428 }
429
430 #[cfg(feature = "testing")]
434 pub fn subscribe_thread_subscriber_updates(&self) -> Receiver<()> {
435 self.inner.thread_subscriber_sender.subscribe()
436 }
437
438 #[cfg(feature = "testing")]
443 pub async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
444 self.inner.handle_room_updates(updates).await
445 }
446
447 #[cfg(not(feature = "testing"))]
452 pub(crate) async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
453 self.inner.handle_room_updates(updates).await
454 }
455
456 pub async fn room(
458 &self,
459 room_id: &RoomId,
460 ) -> Result<(RoomEventCache, 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.room().clone(), drop_handles))
466 }
467
468 pub async fn thread(
474 &self,
475 room_id: &RoomId,
476 thread_id: &EventId,
477 ) -> Result<(ThreadEventCache, Arc<EventCacheDropHandles>)> {
478 let drop_handles = self.drop_handles.clone();
479
480 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
481
482 Ok((caches_for_room.thread(thread_id.to_owned()).await?.deref().clone(), drop_handles))
483 }
484
485 pub async fn pinned_events(
487 &self,
488 room_id: &RoomId,
489 ) -> Result<(PinnedEventsCache, Arc<EventCacheDropHandles>)> {
490 let drop_handles = self.drop_handles.clone();
491
492 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
493
494 Ok((caches_for_room.pinned_events().await?.clone(), drop_handles))
495 }
496
497 pub async fn event_focused(
499 &self,
500 room_id: &RoomId,
501 event_id: &EventId,
502 thread_mode: EventFocusThreadMode,
503 number_of_initial_events: u16,
504 ) -> Result<(EventFocusedCache, Arc<EventCacheDropHandles>)> {
505 let drop_handles = self.drop_handles.clone();
506
507 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
508
509 Ok((
510 caches_for_room
511 .event_focused(event_id.to_owned(), thread_mode, number_of_initial_events)
512 .await?
513 .deref()
514 .clone(),
515 drop_handles,
516 ))
517 }
518
519 pub async fn thread_info(
521 &self,
522 room_id: &RoomId,
523 thread_id: &EventId,
524 ) -> Result<Option<ThreadInfo>> {
525 let Some(client) = self.inner.client.get() else {
526 return Ok(None);
527 };
528
529 let store = match client.event_cache_store().lock().await? {
533 MappedCrossProcessLockState::Clean(store)
534 | MappedCrossProcessLockState::Dirty(store) => store,
535 };
536
537 Ok(store.load_thread_info(room_id, thread_id, false).await?)
538 }
539
540 pub async fn forget_room(&self, room_id: &RoomId) -> Result<()> {
544 self.inner.forget_room(room_id).await
545 }
546
547 pub async fn clear_all_rooms(&self) -> Result<()> {
551 self.inner.clear_all_rooms().await
552 }
553
554 pub fn subscribe_to_room_generic_updates(&self) -> Receiver<RoomEventCacheGenericUpdate> {
564 self.inner.generic_update_sender.subscribe()
565 }
566
567 pub fn back_pagination_queue(&self) -> Option<BackPaginationQueue> {
572 self.inner.back_pagination_queue.get().cloned()
573 }
574}
575
576#[derive(Clone, Copy, Debug)]
578pub struct EventCacheConfig {
579 pub max_pinned_events_concurrent_requests: usize,
581
582 pub max_pinned_events_to_load: usize,
584
585 pub max_concurrent_back_paginations: NonZeroUsize,
591}
592
593impl EventCacheConfig {
594 pub const DEFAULT_MAX_EVENTS_TO_LOAD: usize = 128;
596
597 pub const DEFAULT_MAX_CONCURRENT_REQUESTS: usize = 8;
600
601 pub const DEFAULT_MAX_CONCURRENT_BACK_PAGINATIONS: NonZeroUsize = NonZeroUsize::new(3).unwrap();
604}
605
606impl Default for EventCacheConfig {
607 fn default() -> Self {
608 Self {
609 max_pinned_events_concurrent_requests: Self::DEFAULT_MAX_CONCURRENT_REQUESTS,
610 max_pinned_events_to_load: Self::DEFAULT_MAX_EVENTS_TO_LOAD,
611 max_concurrent_back_paginations: Self::DEFAULT_MAX_CONCURRENT_BACK_PAGINATIONS,
612 }
613 }
614}
615
616type CachesByRoom = HashMap<OwnedRoomId, Caches>;
617
618struct EventCacheInner {
619 client: WeakClient,
622
623 config: StdRwLock<EventCacheConfig>,
625
626 state: StateLock,
629
630 by_room: Arc<RwLock<CachesByRoom>>,
634
635 auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
642
643 generic_update_sender: Sender<RoomEventCacheGenericUpdate>,
648
649 linked_chunk_update_sender: LinkedChunkUpdateSender,
657
658 thread_subscriber_sender: Sender<()>,
664
665 #[cfg(feature = "e2e-encryption")]
666 redecryption_channels: redecryptor::RedecryptorChannels,
667
668 enable_automatic_back_pagination: bool,
674
675 back_pagination_queue: OnceLock<BackPaginationQueue>,
680}
681
682impl EventCacheInner {
683 fn client(&self) -> Result<Client> {
684 self.client.get().ok_or(EventCacheError::ClientDropped)
685 }
686
687 async fn forget_room(&self, room_id: &RoomId) -> Result<()> {
689 let mut caches_for_all_rooms = self.by_room.write().await;
693 self.state.clear_and_reload(&caches_for_all_rooms, Some(room_id)).await?;
694
695 caches_for_all_rooms.remove(room_id);
697
698 Ok(())
699 }
700
701 async fn clear_all_rooms(&self) -> Result<()> {
703 let caches_for_all_rooms = self.by_room.write().await;
731
732 self.state.clear_and_reload(&caches_for_all_rooms, None).await?;
734
735 Ok(())
736 }
737
738 #[instrument(skip(self, updates))]
740 async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
741 for (room_id, left_room_update) in updates.left {
748 trace!(?room_id, "Handling a `LeftRoomUpdate`");
749
750 self.all_caches_for_room(&room_id)
751 .await?
752 .handle_left_room_update(left_room_update)
753 .await?;
754 }
755
756 for (room_id, joined_room_update) in updates.joined {
758 trace!(?room_id, "Handling a `JoinedRoomUpdate`");
759
760 self.all_caches_for_room(&room_id)
761 .await?
762 .handle_joined_room_update(joined_room_update)
763 .await?;
764 }
765
766 Ok(())
772 }
773
774 async fn all_caches_for_room(
776 &self,
777 room_id: &RoomId,
778 ) -> Result<OwnedRwLockReadGuard<CachesByRoom, Caches>> {
779 match OwnedRwLockReadGuard::try_map(self.by_room.clone().read_owned().await, |by_room| {
782 by_room.get(room_id)
783 }) {
784 Ok(caches) => Ok(caches),
785
786 Err(by_room_guard) => {
787 drop(by_room_guard);
790 let by_room_guard = self.by_room.clone().write_owned().await;
791
792 let mut by_room_guard =
795 match OwnedRwLockWriteGuard::try_downgrade_map(by_room_guard, |by_room| {
796 by_room.get(room_id)
797 }) {
798 Ok(caches) => return Ok(caches),
799 Err(by_room_guard) => by_room_guard,
800 };
801
802 let caches = Caches::new(
803 &self.client,
804 room_id,
805 self.generic_update_sender.clone(),
806 self.linked_chunk_update_sender.clone(),
807 self.auto_shrink_sender.clone(),
808 &self.state,
809 self.back_pagination_queue.get().cloned(),
810 )
811 .await?;
812
813 by_room_guard.insert(room_id.to_owned(), caches);
814
815 Ok(OwnedRwLockWriteGuard::try_downgrade_map(by_room_guard, |by_room| {
816 by_room.get(room_id)
817 })
818 .expect("`Caches` has just been inserted"))
819 }
820 }
821 }
822}
823
824#[derive(Debug, Clone)]
826pub enum EventsOrigin {
827 Sync,
829
830 Pagination,
832
833 Cache,
835}
836
837#[cfg(test)]
838mod tests {
839 use std::{ops::Not, sync::Arc, time::Duration};
840
841 use assert_matches::assert_matches;
842 use futures_util::FutureExt as _;
843 use matrix_sdk_base::{
844 RoomState,
845 cross_process_lock::CrossProcessLockConfig,
846 event_cache::store::{EventCacheStore, MemoryStore},
847 linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update},
848 store::StoreConfig,
849 sync::{JoinedRoomUpdate, RoomUpdates, Timeline},
850 };
851 use matrix_sdk_test::{
852 JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
853 };
854 use ruma::{event_id, room_id, user_id};
855 use tokio::time::sleep;
856
857 use super::{EventCacheError, RoomEventCacheGenericUpdate};
858 use crate::test_utils::{
859 assert_event_matches_msg, client::MockClientBuilder, logged_in_client,
860 };
861
862 #[async_test]
863 async fn test_get_event_by_id() {
864 let client = logged_in_client(None).await;
865 let room_id1 = room_id!("!galette:saucisse.bzh");
866 let room_id2 = room_id!("!crepe:saucisse.bzh");
867
868 client.base_client().get_or_create_room(room_id1, RoomState::Joined);
869 client.base_client().get_or_create_room(room_id2, RoomState::Joined);
870
871 let event_cache = client.event_cache().unwrap();
872
873 let f = EventFactory::new().room(room_id1).sender(user_id!("@ben:saucisse.bzh"));
875
876 let eid1 = event_id!("$1");
877 let eid2 = event_id!("$2");
878 let eid3 = event_id!("$3");
879
880 let joined_room_update1 = JoinedRoomUpdate {
881 timeline: Timeline {
882 events: vec![
883 f.text_msg("hey").event_id(eid1).into(),
884 f.text_msg("you").event_id(eid2).into(),
885 ],
886 ..Default::default()
887 },
888 ..Default::default()
889 };
890
891 let joined_room_update2 = JoinedRoomUpdate {
892 timeline: Timeline {
893 events: vec![f.text_msg("bjr").event_id(eid3).into()],
894 ..Default::default()
895 },
896 ..Default::default()
897 };
898
899 let mut updates = RoomUpdates::default();
900 updates.joined.insert(room_id1.to_owned(), joined_room_update1);
901 updates.joined.insert(room_id2.to_owned(), joined_room_update2);
902
903 event_cache.inner.handle_room_updates(updates).await.unwrap();
905
906 let (room_event_cache, _drop_handles) = event_cache.room(room_id1).await.unwrap();
908
909 let found1 = room_event_cache.find_event(eid1).await.unwrap().unwrap();
910 assert_event_matches_msg(&found1, "hey");
911
912 let found2 = room_event_cache.find_event(eid2).await.unwrap().unwrap();
913 assert_event_matches_msg(&found2, "you");
914
915 assert!(room_event_cache.find_event(eid3).await.unwrap().is_none());
918 }
919
920 #[async_test]
921 async fn test_generic_update_when_loading_rooms() {
922 let user = user_id!("@mnt_io:matrix.org");
924
925 let room_id_0 = room_id!("!raclette:patate.ch");
926 let room_id_1 = room_id!("!fondue:patate.ch");
927
928 let event_factory = EventFactory::new().room(room_id_0).sender(user);
929
930 let event_cache_store = MemoryStore::new();
931 event_cache_store
932 .handle_linked_chunk_updates(
933 LinkedChunkId::Room(room_id_0),
934 vec![
935 Update::NewItemsChunk {
937 previous: None,
938 new: ChunkIdentifier::new(0),
939 next: None,
940 },
941 Update::PushItems {
942 at: Position::new(ChunkIdentifier::new(0), 0),
943 items: vec![
944 event_factory
945 .text_msg("hello")
946 .sender(user)
947 .event_id(event_id!("$ev0"))
948 .into_event(),
949 ],
950 },
951 ],
952 )
953 .await
954 .unwrap();
955
956 let client = MockClientBuilder::new(None)
957 .on_builder(move |builder| {
958 builder.store_config(
959 StoreConfig::new(CrossProcessLockConfig::MultiProcess {
960 holder_name: "foo".to_owned(),
961 })
962 .event_cache_store(event_cache_store),
963 )
964 })
965 .build()
966 .await;
967
968 let event_cache = client.event_cache().unwrap();
969
970 client.base_client().get_or_create_room(room_id_0, RoomState::Joined);
971 client.base_client().get_or_create_room(room_id_1, RoomState::Joined);
972
973 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
974
975 {
977 let _room_event_cache = event_cache.room(room_id_0).await.unwrap();
978
979 assert_matches!(
980 generic_stream.recv().await,
981 Ok(RoomEventCacheGenericUpdate { room_id }) => {
982 assert_eq!(room_id, room_id_0);
983 }
984 );
985 }
986
987 {
989 let _room_event_cache = event_cache.room(room_id_1).await.unwrap();
990
991 assert!(generic_stream.recv().now_or_never().is_none());
992 }
993 }
994
995 #[async_test]
996 async fn test_generic_update_when_paginating_room() {
997 let user = user_id!("@mnt_io:matrix.org");
999 let room_id = room_id!("!raclette:patate.ch");
1000
1001 let event_factory = EventFactory::new().room(room_id).sender(user);
1002
1003 let event_cache_store = MemoryStore::new();
1004 event_cache_store
1005 .handle_linked_chunk_updates(
1006 LinkedChunkId::Room(room_id),
1007 vec![
1008 Update::NewItemsChunk {
1010 previous: None,
1011 new: ChunkIdentifier::new(0),
1012 next: None,
1013 },
1014 Update::NewItemsChunk {
1016 previous: Some(ChunkIdentifier::new(0)),
1017 new: ChunkIdentifier::new(1),
1018 next: None,
1019 },
1020 Update::NewItemsChunk {
1022 previous: Some(ChunkIdentifier::new(1)),
1023 new: ChunkIdentifier::new(2),
1024 next: None,
1025 },
1026 Update::PushItems {
1027 at: Position::new(ChunkIdentifier::new(2), 0),
1028 items: vec![
1029 event_factory
1030 .text_msg("hello")
1031 .sender(user)
1032 .event_id(event_id!("$ev0"))
1033 .into_event(),
1034 ],
1035 },
1036 Update::NewItemsChunk {
1038 previous: Some(ChunkIdentifier::new(2)),
1039 new: ChunkIdentifier::new(3),
1040 next: None,
1041 },
1042 Update::PushItems {
1043 at: Position::new(ChunkIdentifier::new(3), 0),
1044 items: vec![
1045 event_factory
1046 .text_msg("world")
1047 .sender(user)
1048 .event_id(event_id!("$ev1"))
1049 .into_event(),
1050 ],
1051 },
1052 ],
1053 )
1054 .await
1055 .unwrap();
1056
1057 let client = MockClientBuilder::new(None)
1058 .on_builder(move |builder| {
1059 builder.store_config(
1060 StoreConfig::new(CrossProcessLockConfig::MultiProcess {
1061 holder_name: "foo".to_owned(),
1062 })
1063 .event_cache_store(event_cache_store),
1064 )
1065 })
1066 .build()
1067 .await;
1068
1069 let event_cache = client.event_cache().unwrap();
1070
1071 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1072
1073 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1074
1075 let (room_event_cache, _) = event_cache.room(room_id).await.unwrap();
1077
1078 assert_matches!(
1079 generic_stream.recv().await,
1080 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1081 assert_eq!(room_id, expected_room_id);
1082 }
1083 );
1084
1085 let pagination = room_event_cache.pagination();
1086
1087 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1089
1090 assert_eq!(pagination_outcome.events.len(), 1);
1091 assert!(pagination_outcome.reached_start.not());
1092 assert_matches!(
1093 generic_stream.recv().await,
1094 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1095 assert_eq!(room_id, expected_room_id);
1096 }
1097 );
1098
1099 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1101
1102 assert!(pagination_outcome.events.is_empty());
1103 assert!(pagination_outcome.reached_start.not());
1104 assert!(generic_stream.recv().now_or_never().is_none());
1105
1106 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1108
1109 assert!(pagination_outcome.reached_start);
1110 assert!(generic_stream.recv().now_or_never().is_none());
1111 }
1112
1113 #[async_test]
1114 async fn test_for_room_when_room_is_not_found() {
1115 let client = logged_in_client(None).await;
1116 let room_id = room_id!("!raclette:patate.ch");
1117
1118 let event_cache = client.event_cache().unwrap();
1119
1120 assert_matches!(
1122 event_cache.room(room_id).await,
1123 Err(EventCacheError::RoomNotFound { room_id: not_found_room_id }) => {
1124 assert_eq!(room_id, not_found_room_id);
1125 }
1126 );
1127
1128 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1130
1131 assert!(event_cache.room(room_id).await.is_ok());
1133 }
1134
1135 #[cfg(not(target_family = "wasm"))]
1138 #[async_test]
1139 async fn test_no_refcycle_event_cache_tasks() {
1140 let client = MockClientBuilder::new(None).build().await;
1141
1142 sleep(Duration::from_secs(1)).await;
1144
1145 let event_cache_weak = Arc::downgrade(&client.event_cache().unwrap().inner);
1146 assert_eq!(event_cache_weak.strong_count(), 2);
1147
1148 {
1149 let room_id = room_id!("!room:example.org");
1150
1151 let response = SyncResponseBuilder::default()
1153 .add_joined_room(JoinedRoomBuilder::new(room_id))
1154 .build_sync_response();
1155 client.inner.base_client.receive_sync_response(response).await.unwrap();
1156
1157 let (_room_event_cache, _drop_handles) =
1158 client.event_cache().unwrap().room(room_id).await.unwrap();
1159 }
1160
1161 drop(client);
1162
1163 sleep(Duration::from_secs(1)).await;
1165
1166 assert_eq!(
1168 event_cache_weak.strong_count(),
1169 0,
1170 "Too many strong references to the event cache {}",
1171 event_cache_weak.strong_count()
1172 );
1173 }
1174
1175 #[async_test]
1178 async fn test_cancelled_caches_creation_can_be_retried() {
1179 let client = logged_in_client(None).await;
1180 let room_id_a = room_id!("!a:saucisse.bzh");
1181 let room_id_b = room_id!("!b:saucisse.bzh");
1182
1183 client.base_client().get_or_create_room(room_id_a, RoomState::Joined);
1184 client.base_client().get_or_create_room(room_id_b, RoomState::Joined);
1185
1186 let event_cache = client.event_cache().unwrap();
1187
1188 let (room_a, _drop_handles) = event_cache.room(room_id_a).await.unwrap();
1189
1190 let guard = room_a.state().write().await.unwrap();
1193
1194 let mut create_b = Box::pin(event_cache.room(room_id_b));
1195 assert!(create_b.as_mut().now_or_never().is_none());
1196
1197 let mut other_writer = Box::pin(room_a.state().write());
1199 assert!(other_writer.as_mut().now_or_never().is_none());
1200
1201 drop(guard);
1205 let _ = create_b.as_mut().now_or_never();
1206 drop(create_b);
1207 drop(other_writer);
1208
1209 event_cache.room(room_id_b).await.unwrap();
1211 }
1212}