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,
40 event_cache::store::{EventCacheStoreError, EventCacheStoreLock},
41 linked_chunk::lazy_loader::LazyLoaderError,
42 sync::RoomUpdates,
43 task_monitor::BackgroundTaskHandle,
44};
45use ruma::{EventId, OwnedEventId, OwnedRoomId, RoomId};
46use tokio::sync::{
47 OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock,
48 broadcast::{Receiver, Sender, channel},
49 mpsc,
50};
51use tracing::{error, instrument, trace};
52
53use crate::{
54 Client,
55 client::{ClientInner, WeakClient},
56 paginators::PaginatorError,
57};
58
59pub(crate) mod back_pagination_queue;
60mod caches;
61mod deduplicator;
62mod persistence;
63#[cfg(feature = "e2e-encryption")]
64mod redecryptor;
65mod search_backfill;
66mod states;
67mod tasks;
68
69#[cfg(feature = "e2e-encryption")]
70pub use redecryptor::{DecryptionRetryRequest, RedecryptorReport};
71
72pub use self::{
73 back_pagination_queue::BackPaginationQueue,
74 caches::{
75 TimelineVectorDiffs,
76 event_focused::{EventFocusThreadMode, EventFocusedCache, EventFocusedCacheKey},
77 pagination::{BackPaginationOutcome, PaginationStatus},
78 pinned_events::PinnedEventsCache,
79 room::{
80 RoomEventCache, RoomEventCacheGenericUpdate, RoomEventCacheUpdate,
81 pagination::RoomPagination,
82 },
83 subscriber::Subscriber,
84 thread::{ThreadEventCache, ThreadEventCacheUpdate, pagination::ThreadPagination},
85 },
86 search_backfill::SearchBackfillStrategy,
87};
88use self::{
89 caches::{Caches, room::RoomEventCacheLinkedChunkUpdate, subscriber::AutoShrinkMessage},
90 states::StateLock,
91};
92
93#[derive(thiserror::Error, Clone, Debug)]
95pub enum EventCacheError {
96 #[error(
99 "The EventCache hasn't subscribed to sync responses yet, call `EventCache::subscribe()`"
100 )]
101 NotSubscribedYet,
102
103 #[error("Room cache `{room_id}` is not found.")]
105 RoomNotFound {
106 room_id: OwnedRoomId,
108 },
109
110 #[error("Thread cache `{thread_id}` of room `{room_id}` is not found.")]
112 ThreadNotFound {
113 room_id: OwnedRoomId,
115
116 thread_id: OwnedEventId,
118 },
119
120 #[error("Pinned-events cache for room `{room_id}` are not found.")]
122 PinnedEventsNotFound {
123 room_id: OwnedRoomId,
125 },
126
127 #[error("Event-focused cache `{event_focused_id:?}` of room `{room_id}` is not found.")]
129 EventFocusedNotFound {
130 room_id: OwnedRoomId,
132
133 event_focused_id: EventFocusedCacheKey,
135 },
136
137 #[error("The state of a cache is not found")]
140 CacheStateAlreadyExists,
141
142 #[error(transparent)]
144 PaginationError(Arc<crate::Error>),
145
146 #[error(transparent)]
148 InitialPaginationError(#[from] PaginatorError),
149
150 #[error(transparent)]
152 Storage(#[from] EventCacheStoreError),
153
154 #[error(transparent)]
156 LockingStorage(#[from] CrossProcessLockError),
157
158 #[error("The owning client of the event cache has been dropped.")]
162 ClientDropped,
163
164 #[error(transparent)]
169 LinkedChunkLoader(#[from] LazyLoaderError),
170
171 #[error("Unable to load any of the pinned events.")]
175 UnableToLoadPinnedEvents,
176
177 #[error("the linked chunk metadata is invalid: {details}")]
180 InvalidLinkedChunkMetadata {
181 details: String,
183 },
184}
185
186pub type Result<T> = std::result::Result<T, EventCacheError>;
188
189pub struct EventCacheDropHandles {
191 _listen_updates_task: BackgroundTaskHandle,
193
194 _ignore_user_list_update_task: BackgroundTaskHandle,
196
197 _auto_shrink_linked_chunk_task: BackgroundTaskHandle,
199
200 _thread_subscriber_task: BackgroundTaskHandle,
207
208 #[cfg(feature = "experimental-search")]
214 _search_indexing_task: BackgroundTaskHandle,
215
216 #[cfg(feature = "e2e-encryption")]
218 _redecryptor: redecryptor::Redecryptor,
219}
220
221impl fmt::Debug for EventCacheDropHandles {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 f.debug_struct("EventCacheDropHandles").finish_non_exhaustive()
224 }
225}
226
227#[derive(Clone)]
233pub struct EventCache {
234 inner: Arc<EventCacheInner>,
236}
237
238impl fmt::Debug for EventCache {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 f.debug_struct("EventCache").finish_non_exhaustive()
241 }
242}
243
244impl EventCache {
245 pub(crate) fn new(
247 client: &Arc<ClientInner>,
248 event_cache_store: EventCacheStoreLock,
249 enable_automatic_back_pagination: bool,
250 ) -> Self {
251 let (generic_update_sender, _) = channel(128);
252 let (linked_chunk_update_sender, _) = channel(128);
253
254 let weak_client = WeakClient::from_inner(client);
255
256 let (thread_subscriber_sender, _thread_subscriber_receiver) = channel(128);
257
258 #[cfg(feature = "e2e-encryption")]
259 let redecryption_channels = redecryptor::RedecryptorChannels::new();
260
261 Self {
262 inner: Arc::new(EventCacheInner {
263 client: weak_client,
264 config: StdRwLock::new(EventCacheConfig::default()),
265 state: StateLock::new(event_cache_store),
266 by_room: Default::default(),
267 drop_handles: Default::default(),
268 auto_shrink_sender: Default::default(),
269 generic_update_sender,
270 linked_chunk_update_sender,
271 #[cfg(feature = "e2e-encryption")]
272 redecryption_channels,
273 enable_automatic_back_pagination,
274 back_pagination_queue: OnceLock::new(),
275 thread_subscriber_sender,
276 }),
277 }
278 }
279
280 pub fn config(&self) -> RwLockReadGuard<'_, EventCacheConfig> {
283 self.inner.config.read().unwrap()
284 }
285
286 pub fn config_mut(&self) -> RwLockWriteGuard<'_, EventCacheConfig> {
288 self.inner.config.write().unwrap()
289 }
290
291 #[cfg(feature = "testing")]
295 pub fn subscribe_thread_subscriber_updates(&self) -> Receiver<()> {
296 self.inner.thread_subscriber_sender.subscribe()
297 }
298
299 pub fn subscribe(&self) -> Result<()> {
305 let client = self.inner.client()?;
306
307 let _ = self.inner.drop_handles.get_or_init(|| {
309 let task_monitor = client.task_monitor();
310
311 let listen_updates_task = task_monitor.spawn_infinite_task("event_cache::room_updates_task", tasks::room_updates_task(
313 self.inner.clone(),
314 client.subscribe_to_all_room_updates(),
315 )).abort_on_drop();
316
317 let ignore_user_list_update_task = task_monitor.spawn_infinite_task("event_cache::ignore_user_list_update_task", tasks::ignore_user_list_update_task(
318 self.inner.clone(),
319 client.subscribe_to_ignore_user_list_changes(),
320 )).abort_on_drop();
321
322 let (auto_shrink_sender, auto_shrink_receiver) = mpsc::channel(32);
323
324 self.inner.auto_shrink_sender.get_or_init(|| auto_shrink_sender);
326
327 let auto_shrink_linked_chunk_task = task_monitor.spawn_infinite_task("event_cache::auto_shrink_linked_chunk_task", tasks::auto_shrink_linked_chunk_task(
328 Arc::downgrade(&self.inner),
329 auto_shrink_receiver,
330 )).abort_on_drop();
331
332 #[cfg(feature = "e2e-encryption")]
333 let redecryptor = {
334 let receiver = self
335 .inner
336 .redecryption_channels
337 .decryption_request_receiver
338 .lock()
339 .take()
340 .expect("We should have initialized the channel an subscribing should happen only once");
341
342 redecryptor::Redecryptor::new(&client, Arc::downgrade(&self.inner), receiver, &self.inner.linked_chunk_update_sender)
343 };
344
345 let thread_subscriber_task = client
346 .task_monitor()
347 .spawn_infinite_task(
348 "event_cache::thread_subscriber",
349 tasks::thread_subscriber_task(
350 self.inner.client.clone(),
351 self.inner.linked_chunk_update_sender.clone(),
352 self.inner.thread_subscriber_sender.clone(),
353 ),
354 )
355 .abort_on_drop();
356
357 #[cfg(feature = "experimental-search")]
358 let search_indexing_task = client
359 .task_monitor()
360 .spawn_infinite_task(
361 "event_cache::search_indexing",
362 tasks::search_indexing_task(
363 self.inner.client.clone(),
364 self.inner.linked_chunk_update_sender.clone(),
365 ),
366 )
367 .abort_on_drop();
368
369 if self.inner.enable_automatic_back_pagination {
370 trace!("spawning the back-pagination queue");
372 let max_concurrent = self.config().max_concurrent_back_paginations;
373 self.inner.back_pagination_queue.get_or_init(|| {
374 BackPaginationQueue::new(
375 Arc::downgrade(&self.inner),
376 max_concurrent,
377 task_monitor,
378 )
379 });
380 } else {
381 trace!("back-pagination queue is disabled");
382 }
383
384 Arc::new(EventCacheDropHandles {
385 _listen_updates_task: listen_updates_task,
386 _ignore_user_list_update_task: ignore_user_list_update_task,
387 _auto_shrink_linked_chunk_task: auto_shrink_linked_chunk_task,
388 #[cfg(feature = "e2e-encryption")]
389 _redecryptor: redecryptor,
390 _thread_subscriber_task: thread_subscriber_task,
391 #[cfg(feature = "experimental-search")]
392 _search_indexing_task: search_indexing_task,
393 })
394 });
395
396 Ok(())
397 }
398
399 #[doc(hidden)]
401 pub async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
402 self.inner.handle_room_updates(updates).await
403 }
404
405 pub fn has_subscribed(&self) -> bool {
407 self.inner.drop_handles.get().is_some()
408 }
409
410 pub async fn room(
412 &self,
413 room_id: &RoomId,
414 ) -> Result<(RoomEventCache, Arc<EventCacheDropHandles>)> {
415 let Some(drop_handles) = self.inner.drop_handles.get().cloned() else {
416 return Err(EventCacheError::NotSubscribedYet);
417 };
418
419 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
420
421 Ok((caches_for_room.room().clone(), drop_handles))
422 }
423
424 pub async fn thread(
426 &self,
427 room_id: &RoomId,
428 thread_id: &EventId,
429 ) -> Result<(ThreadEventCache, Arc<EventCacheDropHandles>)> {
430 let Some(drop_handles) = self.inner.drop_handles.get().cloned() else {
431 return Err(EventCacheError::NotSubscribedYet);
432 };
433
434 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
435
436 Ok((caches_for_room.thread(thread_id.to_owned()).await?.deref().clone(), drop_handles))
437 }
438
439 pub async fn pinned_events(
441 &self,
442 room_id: &RoomId,
443 ) -> Result<(PinnedEventsCache, Arc<EventCacheDropHandles>)> {
444 let Some(drop_handles) = self.inner.drop_handles.get().cloned() else {
445 return Err(EventCacheError::NotSubscribedYet);
446 };
447
448 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
449
450 Ok((caches_for_room.pinned_events().await?.clone(), drop_handles))
451 }
452
453 pub async fn event_focused(
455 &self,
456 room_id: &RoomId,
457 event_id: &EventId,
458 thread_mode: EventFocusThreadMode,
459 number_of_initial_events: u16,
460 ) -> Result<(EventFocusedCache, Arc<EventCacheDropHandles>)> {
461 let Some(drop_handles) = self.inner.drop_handles.get().cloned() else {
462 return Err(EventCacheError::NotSubscribedYet);
463 };
464
465 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
466
467 Ok((
468 caches_for_room
469 .event_focused(event_id.to_owned(), thread_mode, number_of_initial_events)
470 .await?
471 .deref()
472 .clone(),
473 drop_handles,
474 ))
475 }
476
477 pub async fn forget_room(&self, room_id: &RoomId) -> Result<()> {
481 self.inner.forget_room(room_id).await
482 }
483
484 pub async fn clear_all_rooms(&self) -> Result<()> {
488 self.inner.clear_all_rooms().await
489 }
490
491 pub fn subscribe_to_room_generic_updates(&self) -> Receiver<RoomEventCacheGenericUpdate> {
501 self.inner.generic_update_sender.subscribe()
502 }
503
504 pub fn back_pagination_queue(&self) -> Option<BackPaginationQueue> {
509 self.inner.back_pagination_queue.get().cloned()
510 }
511}
512
513#[derive(Clone, Copy, Debug)]
515pub struct EventCacheConfig {
516 pub max_pinned_events_concurrent_requests: usize,
518
519 pub max_pinned_events_to_load: usize,
521
522 pub max_concurrent_back_paginations: NonZeroUsize,
528}
529
530impl EventCacheConfig {
531 pub const DEFAULT_MAX_EVENTS_TO_LOAD: usize = 128;
533
534 pub const DEFAULT_MAX_CONCURRENT_REQUESTS: usize = 8;
537
538 pub const DEFAULT_MAX_CONCURRENT_BACK_PAGINATIONS: NonZeroUsize = NonZeroUsize::new(3).unwrap();
541}
542
543impl Default for EventCacheConfig {
544 fn default() -> Self {
545 Self {
546 max_pinned_events_concurrent_requests: Self::DEFAULT_MAX_CONCURRENT_REQUESTS,
547 max_pinned_events_to_load: Self::DEFAULT_MAX_EVENTS_TO_LOAD,
548 max_concurrent_back_paginations: Self::DEFAULT_MAX_CONCURRENT_BACK_PAGINATIONS,
549 }
550 }
551}
552
553type CachesByRoom = HashMap<OwnedRoomId, Caches>;
554
555struct EventCacheInner {
556 client: WeakClient,
559
560 config: StdRwLock<EventCacheConfig>,
562
563 state: StateLock,
566
567 by_room: Arc<RwLock<CachesByRoom>>,
571
572 drop_handles: OnceLock<Arc<EventCacheDropHandles>>,
574
575 auto_shrink_sender: OnceLock<mpsc::Sender<AutoShrinkMessage>>,
585
586 generic_update_sender: Sender<RoomEventCacheGenericUpdate>,
591
592 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
600
601 thread_subscriber_sender: Sender<()>,
607
608 #[cfg(feature = "e2e-encryption")]
609 redecryption_channels: redecryptor::RedecryptorChannels,
610
611 enable_automatic_back_pagination: bool,
617
618 back_pagination_queue: OnceLock<BackPaginationQueue>,
623}
624
625impl EventCacheInner {
626 fn client(&self) -> Result<Client> {
627 self.client.get().ok_or(EventCacheError::ClientDropped)
628 }
629
630 async fn forget_room(&self, room_id: &RoomId) -> Result<()> {
632 let mut caches_for_all_rooms = self.by_room.write().await;
636 self.state.clear_and_reload(&caches_for_all_rooms, Some(room_id)).await?;
637
638 caches_for_all_rooms.remove(room_id);
640
641 Ok(())
642 }
643
644 async fn clear_all_rooms(&self) -> Result<()> {
646 let caches_for_all_rooms = self.by_room.write().await;
674
675 self.state.clear_and_reload(&caches_for_all_rooms, None).await?;
677
678 Ok(())
679 }
680
681 #[instrument(skip(self, updates))]
683 async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
684 for (room_id, left_room_update) in updates.left {
691 let Ok(caches) = self.all_caches_for_room(&room_id).await else {
692 error!(?room_id, "Room must exist");
693 continue;
694 };
695
696 if let Err(err) = caches.handle_left_room_update(left_room_update).await {
697 error!("handling left room update: {err}");
699 }
700 }
701
702 for (room_id, joined_room_update) in updates.joined {
704 trace!(?room_id, "Handling a `JoinedRoomUpdate`");
705
706 let Ok(caches) = self.all_caches_for_room(&room_id).await else {
707 error!(?room_id, "Room must exist");
708 continue;
709 };
710
711 if let Err(err) = caches.handle_joined_room_update(joined_room_update).await {
712 error!(%room_id, "handling joined room update: {err}");
714 }
715 }
716
717 Ok(())
723 }
724
725 async fn all_caches_for_room(
727 &self,
728 room_id: &RoomId,
729 ) -> Result<OwnedRwLockReadGuard<CachesByRoom, Caches>> {
730 match OwnedRwLockReadGuard::try_map(self.by_room.clone().read_owned().await, |by_room| {
733 by_room.get(room_id)
734 }) {
735 Ok(caches) => Ok(caches),
736
737 Err(by_room_guard) => {
738 drop(by_room_guard);
741 let by_room_guard = self.by_room.clone().write_owned().await;
742
743 let mut by_room_guard =
746 match OwnedRwLockWriteGuard::try_downgrade_map(by_room_guard, |by_room| {
747 by_room.get(room_id)
748 }) {
749 Ok(caches) => return Ok(caches),
750 Err(by_room_guard) => by_room_guard,
751 };
752
753 let caches = Caches::new(
754 &self.client,
755 room_id,
756 self.generic_update_sender.clone(),
757 self.linked_chunk_update_sender.clone(),
758 self.auto_shrink_sender.get().cloned().expect(
761 "we must have called `EventCache::subscribe()` before calling here.",
762 ),
763 &self.state,
764 self.back_pagination_queue.get().cloned(),
765 )
766 .await?;
767
768 by_room_guard.insert(room_id.to_owned(), caches);
769
770 Ok(OwnedRwLockWriteGuard::try_downgrade_map(by_room_guard, |by_room| {
771 by_room.get(room_id)
772 })
773 .expect("`Caches` has just been inserted"))
774 }
775 }
776 }
777}
778
779#[derive(Debug, Clone)]
781pub enum EventsOrigin {
782 Sync,
784
785 Pagination,
787
788 Cache,
790}
791
792#[cfg(test)]
793mod tests {
794 use std::{ops::Not, sync::Arc, time::Duration};
795
796 use assert_matches::assert_matches;
797 use futures_util::FutureExt as _;
798 use matrix_sdk_base::{
799 RoomState,
800 linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update},
801 sync::{JoinedRoomUpdate, RoomUpdates, Timeline},
802 };
803 use matrix_sdk_test::{
804 JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
805 };
806 use ruma::{event_id, room_id, user_id};
807 use tokio::time::sleep;
808
809 use super::{EventCacheError, RoomEventCacheGenericUpdate};
810 use crate::test_utils::{
811 assert_event_matches_msg, client::MockClientBuilder, logged_in_client,
812 };
813
814 #[async_test]
815 async fn test_must_explicitly_subscribe() {
816 let client = logged_in_client(None).await;
817
818 let event_cache = client.event_cache();
819
820 let room_id = room_id!("!omelette:fromage.fr");
823 let result = event_cache.room(room_id).await;
824
825 assert_matches!(result, Err(EventCacheError::NotSubscribedYet));
828 }
829
830 #[async_test]
831 async fn test_get_event_by_id() {
832 let client = logged_in_client(None).await;
833 let room_id1 = room_id!("!galette:saucisse.bzh");
834 let room_id2 = room_id!("!crepe:saucisse.bzh");
835
836 client.base_client().get_or_create_room(room_id1, RoomState::Joined);
837 client.base_client().get_or_create_room(room_id2, RoomState::Joined);
838
839 let event_cache = client.event_cache();
840 event_cache.subscribe().unwrap();
841
842 let f = EventFactory::new().room(room_id1).sender(user_id!("@ben:saucisse.bzh"));
844
845 let eid1 = event_id!("$1");
846 let eid2 = event_id!("$2");
847 let eid3 = event_id!("$3");
848
849 let joined_room_update1 = JoinedRoomUpdate {
850 timeline: Timeline {
851 events: vec![
852 f.text_msg("hey").event_id(eid1).into(),
853 f.text_msg("you").event_id(eid2).into(),
854 ],
855 ..Default::default()
856 },
857 ..Default::default()
858 };
859
860 let joined_room_update2 = JoinedRoomUpdate {
861 timeline: Timeline {
862 events: vec![f.text_msg("bjr").event_id(eid3).into()],
863 ..Default::default()
864 },
865 ..Default::default()
866 };
867
868 let mut updates = RoomUpdates::default();
869 updates.joined.insert(room_id1.to_owned(), joined_room_update1);
870 updates.joined.insert(room_id2.to_owned(), joined_room_update2);
871
872 event_cache.inner.handle_room_updates(updates).await.unwrap();
874
875 let room1 = client.get_room(room_id1).unwrap();
877
878 let (room_event_cache, _drop_handles) = room1.event_cache().await.unwrap();
879
880 let found1 = room_event_cache.find_event(eid1).await.unwrap().unwrap();
881 assert_event_matches_msg(&found1, "hey");
882
883 let found2 = room_event_cache.find_event(eid2).await.unwrap().unwrap();
884 assert_event_matches_msg(&found2, "you");
885
886 assert!(room_event_cache.find_event(eid3).await.unwrap().is_none());
889 }
890
891 #[async_test]
892 async fn test_generic_update_when_loading_rooms() {
893 let user = user_id!("@mnt_io:matrix.org");
895 let client = logged_in_client(None).await;
896 let room_id_0 = room_id!("!raclette:patate.ch");
897 let room_id_1 = room_id!("!fondue:patate.ch");
898
899 let event_factory = EventFactory::new().room(room_id_0).sender(user);
900
901 let event_cache = client.event_cache();
902 event_cache.subscribe().unwrap();
903
904 client.base_client().get_or_create_room(room_id_0, RoomState::Joined);
905 client.base_client().get_or_create_room(room_id_1, RoomState::Joined);
906
907 client
908 .event_cache_store()
909 .lock()
910 .await
911 .expect("Could not acquire the event cache lock")
912 .as_clean()
913 .expect("Could not acquire a clean event cache lock")
914 .handle_linked_chunk_updates(
915 LinkedChunkId::Room(room_id_0),
916 vec![
917 Update::NewItemsChunk {
919 previous: None,
920 new: ChunkIdentifier::new(0),
921 next: None,
922 },
923 Update::PushItems {
924 at: Position::new(ChunkIdentifier::new(0), 0),
925 items: vec![
926 event_factory
927 .text_msg("hello")
928 .sender(user)
929 .event_id(event_id!("$ev0"))
930 .into_event(),
931 ],
932 },
933 ],
934 )
935 .await
936 .unwrap();
937
938 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
939
940 {
942 let _room_event_cache = event_cache.room(room_id_0).await.unwrap();
943
944 assert_matches!(
945 generic_stream.recv().await,
946 Ok(RoomEventCacheGenericUpdate { room_id }) => {
947 assert_eq!(room_id, room_id_0);
948 }
949 );
950 }
951
952 {
954 let _room_event_cache = event_cache.room(room_id_1).await.unwrap();
955
956 assert!(generic_stream.recv().now_or_never().is_none());
957 }
958 }
959
960 #[async_test]
961 async fn test_generic_update_when_paginating_room() {
962 let user = user_id!("@mnt_io:matrix.org");
964 let client = logged_in_client(None).await;
965 let room_id = room_id!("!raclette:patate.ch");
966
967 let event_factory = EventFactory::new().room(room_id).sender(user);
968
969 let event_cache = client.event_cache();
970 event_cache.subscribe().unwrap();
971
972 client.base_client().get_or_create_room(room_id, RoomState::Joined);
973
974 client
975 .event_cache_store()
976 .lock()
977 .await
978 .expect("Could not acquire the event cache lock")
979 .as_clean()
980 .expect("Could not acquire a clean event cache lock")
981 .handle_linked_chunk_updates(
982 LinkedChunkId::Room(room_id),
983 vec![
984 Update::NewItemsChunk {
986 previous: None,
987 new: ChunkIdentifier::new(0),
988 next: None,
989 },
990 Update::NewItemsChunk {
992 previous: Some(ChunkIdentifier::new(0)),
993 new: ChunkIdentifier::new(1),
994 next: None,
995 },
996 Update::NewItemsChunk {
998 previous: Some(ChunkIdentifier::new(1)),
999 new: ChunkIdentifier::new(2),
1000 next: None,
1001 },
1002 Update::PushItems {
1003 at: Position::new(ChunkIdentifier::new(2), 0),
1004 items: vec![
1005 event_factory
1006 .text_msg("hello")
1007 .sender(user)
1008 .event_id(event_id!("$ev0"))
1009 .into_event(),
1010 ],
1011 },
1012 Update::NewItemsChunk {
1014 previous: Some(ChunkIdentifier::new(2)),
1015 new: ChunkIdentifier::new(3),
1016 next: None,
1017 },
1018 Update::PushItems {
1019 at: Position::new(ChunkIdentifier::new(3), 0),
1020 items: vec![
1021 event_factory
1022 .text_msg("world")
1023 .sender(user)
1024 .event_id(event_id!("$ev1"))
1025 .into_event(),
1026 ],
1027 },
1028 ],
1029 )
1030 .await
1031 .unwrap();
1032
1033 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1034
1035 let (room_event_cache, _) = event_cache.room(room_id).await.unwrap();
1037
1038 assert_matches!(
1039 generic_stream.recv().await,
1040 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1041 assert_eq!(room_id, expected_room_id);
1042 }
1043 );
1044
1045 let pagination = room_event_cache.pagination();
1046
1047 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1049
1050 assert_eq!(pagination_outcome.events.len(), 1);
1051 assert!(pagination_outcome.reached_start.not());
1052 assert_matches!(
1053 generic_stream.recv().await,
1054 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1055 assert_eq!(room_id, expected_room_id);
1056 }
1057 );
1058
1059 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1061
1062 assert!(pagination_outcome.events.is_empty());
1063 assert!(pagination_outcome.reached_start.not());
1064 assert!(generic_stream.recv().now_or_never().is_none());
1065
1066 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1068
1069 assert!(pagination_outcome.reached_start);
1070 assert!(generic_stream.recv().now_or_never().is_none());
1071 }
1072
1073 #[async_test]
1074 async fn test_for_room_when_room_is_not_found() {
1075 let client = logged_in_client(None).await;
1076 let room_id = room_id!("!raclette:patate.ch");
1077
1078 let event_cache = client.event_cache();
1079 event_cache.subscribe().unwrap();
1080
1081 assert_matches!(
1083 event_cache.room(room_id).await,
1084 Err(EventCacheError::RoomNotFound { room_id: not_found_room_id }) => {
1085 assert_eq!(room_id, not_found_room_id);
1086 }
1087 );
1088
1089 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1091
1092 assert!(event_cache.room(room_id).await.is_ok());
1094 }
1095
1096 #[cfg(not(target_family = "wasm"))]
1099 #[async_test]
1100 async fn test_no_refcycle_event_cache_tasks() {
1101 let client = MockClientBuilder::new(None).build().await;
1102
1103 sleep(Duration::from_secs(1)).await;
1105
1106 let event_cache_weak = Arc::downgrade(&client.event_cache().inner);
1107 assert_eq!(event_cache_weak.strong_count(), 1);
1108
1109 {
1110 let room_id = room_id!("!room:example.org");
1111
1112 let response = SyncResponseBuilder::default()
1114 .add_joined_room(JoinedRoomBuilder::new(room_id))
1115 .build_sync_response();
1116 client.inner.base_client.receive_sync_response(response).await.unwrap();
1117
1118 client.event_cache().subscribe().unwrap();
1119
1120 let (_room_event_cache, _drop_handles) =
1121 client.get_room(room_id).unwrap().event_cache().await.unwrap();
1122 }
1123
1124 drop(client);
1125
1126 sleep(Duration::from_secs(1)).await;
1128
1129 assert_eq!(
1131 event_cache_weak.strong_count(),
1132 0,
1133 "Too many strong references to the event cache {}",
1134 event_cache_weak.strong_count()
1135 );
1136 }
1137}