1#![forbid(missing_docs)]
29
30use std::{
31 collections::HashMap,
32 fmt,
33 ops::Deref,
34 sync::{Arc, OnceLock, RwLock as StdRwLock, RwLockReadGuard, RwLockWriteGuard},
35};
36
37use matrix_sdk_base::{
38 cross_process_lock::CrossProcessLockError,
39 event_cache::store::{EventCacheStoreError, EventCacheStoreLock},
40 linked_chunk::lazy_loader::LazyLoaderError,
41 sync::RoomUpdates,
42 task_monitor::BackgroundTaskHandle,
43};
44use ruma::{EventId, OwnedEventId, OwnedRoomId, RoomId};
45use tokio::sync::{
46 OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock,
47 broadcast::{Receiver, Sender, channel},
48 mpsc,
49};
50use tracing::{error, instrument, trace};
51
52use crate::{
53 Client,
54 client::{ClientInner, WeakClient},
55 paginators::PaginatorError,
56};
57
58mod automatic_pagination;
59mod caches;
60mod deduplicator;
61mod persistence;
62#[cfg(feature = "e2e-encryption")]
63mod redecryptor;
64mod states;
65mod tasks;
66
67#[cfg(feature = "e2e-encryption")]
68pub use redecryptor::{DecryptionRetryRequest, RedecryptorReport};
69
70pub use self::{
71 automatic_pagination::AutomaticPagination,
72 caches::{
73 TimelineVectorDiffs,
74 event_focused::{EventFocusThreadMode, EventFocusedCache, EventFocusedCacheKey},
75 pagination::{BackPaginationOutcome, PaginationStatus},
76 pinned_events::PinnedEventsCache,
77 room::{
78 RoomEventCache, RoomEventCacheGenericUpdate, RoomEventCacheUpdate,
79 pagination::RoomPagination,
80 },
81 subscriber::Subscriber,
82 thread::{ThreadEventCache, pagination::ThreadPagination},
83 },
84};
85use self::{
86 caches::{Caches, room::RoomEventCacheLinkedChunkUpdate, subscriber::AutoShrinkMessage},
87 states::StateLock,
88};
89
90#[derive(thiserror::Error, Clone, Debug)]
92pub enum EventCacheError {
93 #[error(
96 "The EventCache hasn't subscribed to sync responses yet, call `EventCache::subscribe()`"
97 )]
98 NotSubscribedYet,
99
100 #[error("Room cache `{room_id}` is not found.")]
102 RoomNotFound {
103 room_id: OwnedRoomId,
105 },
106
107 #[error("Thread cache `{thread_id}` of room `{room_id}` is not found.")]
109 ThreadNotFound {
110 room_id: OwnedRoomId,
112
113 thread_id: OwnedEventId,
115 },
116
117 #[error("Pinned-events cache for room `{room_id}` are not found.")]
119 PinnedEventsNotFound {
120 room_id: OwnedRoomId,
122 },
123
124 #[error("Event-focused cache `{event_focused_id:?}` of room `{room_id}` is not found.")]
126 EventFocusedNotFound {
127 room_id: OwnedRoomId,
129
130 event_focused_id: EventFocusedCacheKey,
132 },
133
134 #[error("The state of a cache is not found")]
137 CacheStateAlreadyExists,
138
139 #[error(transparent)]
141 PaginationError(Arc<crate::Error>),
142
143 #[error(transparent)]
145 InitialPaginationError(#[from] PaginatorError),
146
147 #[error(transparent)]
149 Storage(#[from] EventCacheStoreError),
150
151 #[error(transparent)]
153 LockingStorage(#[from] CrossProcessLockError),
154
155 #[error("The owning client of the event cache has been dropped.")]
159 ClientDropped,
160
161 #[error(transparent)]
166 LinkedChunkLoader(#[from] LazyLoaderError),
167
168 #[error("Unable to load any of the pinned events.")]
172 UnableToLoadPinnedEvents,
173
174 #[error("the linked chunk metadata is invalid: {details}")]
177 InvalidLinkedChunkMetadata {
178 details: String,
180 },
181}
182
183pub type Result<T> = std::result::Result<T, EventCacheError>;
185
186pub struct EventCacheDropHandles {
188 _listen_updates_task: BackgroundTaskHandle,
190
191 _ignore_user_list_update_task: BackgroundTaskHandle,
193
194 _auto_shrink_linked_chunk_task: BackgroundTaskHandle,
196
197 _thread_subscriber_task: BackgroundTaskHandle,
204
205 #[cfg(feature = "experimental-search")]
212 _search_indexing_task: BackgroundTaskHandle,
213
214 #[cfg(feature = "e2e-encryption")]
216 _redecryptor: redecryptor::Redecryptor,
217}
218
219impl fmt::Debug for EventCacheDropHandles {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 f.debug_struct("EventCacheDropHandles").finish_non_exhaustive()
222 }
223}
224
225#[derive(Clone)]
231pub struct EventCache {
232 inner: Arc<EventCacheInner>,
234}
235
236impl fmt::Debug for EventCache {
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 f.debug_struct("EventCache").finish_non_exhaustive()
239 }
240}
241
242impl EventCache {
243 pub(crate) fn new(client: &Arc<ClientInner>, event_cache_store: EventCacheStoreLock) -> Self {
245 let (generic_update_sender, _) = channel(128);
246 let (linked_chunk_update_sender, _) = channel(128);
247
248 let weak_client = WeakClient::from_inner(client);
249
250 let (thread_subscriber_sender, _thread_subscriber_receiver) = channel(128);
251
252 #[cfg(feature = "e2e-encryption")]
253 let redecryption_channels = redecryptor::RedecryptorChannels::new();
254
255 Self {
256 inner: Arc::new(EventCacheInner {
257 client: weak_client,
258 config: StdRwLock::new(EventCacheConfig::default()),
259 state: StateLock::new(event_cache_store),
260 by_room: Default::default(),
261 drop_handles: Default::default(),
262 auto_shrink_sender: Default::default(),
263 generic_update_sender,
264 linked_chunk_update_sender,
265 #[cfg(feature = "e2e-encryption")]
266 redecryption_channels,
267 automatic_pagination: OnceLock::new(),
268 thread_subscriber_sender,
269 }),
270 }
271 }
272
273 pub fn config(&self) -> RwLockReadGuard<'_, EventCacheConfig> {
276 self.inner.config.read().unwrap()
277 }
278
279 pub fn config_mut(&self) -> RwLockWriteGuard<'_, EventCacheConfig> {
281 self.inner.config.write().unwrap()
282 }
283
284 #[cfg(feature = "testing")]
288 pub fn subscribe_thread_subscriber_updates(&self) -> Receiver<()> {
289 self.inner.thread_subscriber_sender.subscribe()
290 }
291
292 pub fn subscribe(&self) -> Result<()> {
298 let client = self.inner.client()?;
299
300 let _ = self.inner.drop_handles.get_or_init(|| {
302 let task_monitor = client.task_monitor();
303
304 let listen_updates_task = task_monitor.spawn_infinite_task("event_cache::room_updates_task", tasks::room_updates_task(
306 self.inner.clone(),
307 client.subscribe_to_all_room_updates(),
308 )).abort_on_drop();
309
310 let ignore_user_list_update_task = task_monitor.spawn_infinite_task("event_cache::ignore_user_list_update_task", tasks::ignore_user_list_update_task(
311 self.inner.clone(),
312 client.subscribe_to_ignore_user_list_changes(),
313 )).abort_on_drop();
314
315 let (auto_shrink_sender, auto_shrink_receiver) = mpsc::channel(32);
316
317 self.inner.auto_shrink_sender.get_or_init(|| auto_shrink_sender);
319
320 let auto_shrink_linked_chunk_task = task_monitor.spawn_infinite_task("event_cache::auto_shrink_linked_chunk_task", tasks::auto_shrink_linked_chunk_task(
321 Arc::downgrade(&self.inner),
322 auto_shrink_receiver,
323 )).abort_on_drop();
324
325 #[cfg(feature = "e2e-encryption")]
326 let redecryptor = {
327 let receiver = self
328 .inner
329 .redecryption_channels
330 .decryption_request_receiver
331 .lock()
332 .take()
333 .expect("We should have initialized the channel an subscribing should happen only once");
334
335 redecryptor::Redecryptor::new(&client, Arc::downgrade(&self.inner), receiver, &self.inner.linked_chunk_update_sender)
336 };
337
338 let thread_subscriber_task = client
339 .task_monitor()
340 .spawn_infinite_task(
341 "event_cache::thread_subscriber",
342 tasks::thread_subscriber_task(
343 self.inner.client.clone(),
344 self.inner.linked_chunk_update_sender.clone(),
345 self.inner.thread_subscriber_sender.clone(),
346 ),
347 )
348 .abort_on_drop();
349
350 #[cfg(feature = "experimental-search")]
351 let search_indexing_task = client
352 .task_monitor()
353 .spawn_infinite_task(
354 "event_cache::search_indexing",
355 tasks::search_indexing_task(
356 self.inner.client.clone(),
357 self.inner.linked_chunk_update_sender.clone(),
358 ),
359 )
360 .abort_on_drop();
361
362 if self.config().experimental_auto_backpagination {
363 trace!("spawning the automatic paginations API");
366 self.inner.automatic_pagination.get_or_init(|| AutomaticPagination::new(Arc::downgrade(&self.inner), task_monitor));
367 } else {
368 trace!("automatic paginations API is disabled");
369 }
370
371 Arc::new(EventCacheDropHandles {
372 _listen_updates_task: listen_updates_task,
373 _ignore_user_list_update_task: ignore_user_list_update_task,
374 _auto_shrink_linked_chunk_task: auto_shrink_linked_chunk_task,
375 #[cfg(feature = "e2e-encryption")]
376 _redecryptor: redecryptor,
377 _thread_subscriber_task: thread_subscriber_task,
378 #[cfg(feature = "experimental-search")]
379 _search_indexing_task: search_indexing_task,
380 })
381 });
382
383 Ok(())
384 }
385
386 #[doc(hidden)]
388 pub async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
389 self.inner.handle_room_updates(updates).await
390 }
391
392 pub fn has_subscribed(&self) -> bool {
394 self.inner.drop_handles.get().is_some()
395 }
396
397 pub async fn room(
399 &self,
400 room_id: &RoomId,
401 ) -> Result<(RoomEventCache, Arc<EventCacheDropHandles>)> {
402 let Some(drop_handles) = self.inner.drop_handles.get().cloned() else {
403 return Err(EventCacheError::NotSubscribedYet);
404 };
405
406 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
407
408 Ok((caches_for_room.room().clone(), drop_handles))
409 }
410
411 pub async fn thread(
413 &self,
414 room_id: &RoomId,
415 thread_id: &EventId,
416 ) -> Result<(ThreadEventCache, Arc<EventCacheDropHandles>)> {
417 let Some(drop_handles) = self.inner.drop_handles.get().cloned() else {
418 return Err(EventCacheError::NotSubscribedYet);
419 };
420
421 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
422
423 Ok((caches_for_room.thread(thread_id.to_owned()).await?.deref().clone(), drop_handles))
424 }
425
426 pub async fn pinned_events(
428 &self,
429 room_id: &RoomId,
430 ) -> Result<(PinnedEventsCache, Arc<EventCacheDropHandles>)> {
431 let Some(drop_handles) = self.inner.drop_handles.get().cloned() else {
432 return Err(EventCacheError::NotSubscribedYet);
433 };
434
435 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
436
437 Ok((caches_for_room.pinned_events().await?.clone(), drop_handles))
438 }
439
440 pub async fn event_focused(
442 &self,
443 room_id: &RoomId,
444 event_id: &EventId,
445 thread_mode: EventFocusThreadMode,
446 number_of_initial_events: u16,
447 ) -> Result<(EventFocusedCache, Arc<EventCacheDropHandles>)> {
448 let Some(drop_handles) = self.inner.drop_handles.get().cloned() else {
449 return Err(EventCacheError::NotSubscribedYet);
450 };
451
452 let caches_for_room = self.inner.all_caches_for_room(room_id).await?;
453
454 Ok((
455 caches_for_room
456 .event_focused(event_id.to_owned(), thread_mode, number_of_initial_events)
457 .await?
458 .deref()
459 .clone(),
460 drop_handles,
461 ))
462 }
463
464 pub async fn clear_all_rooms(&self) -> Result<()> {
468 self.inner.clear_all_rooms().await
469 }
470
471 pub fn subscribe_to_room_generic_updates(&self) -> Receiver<RoomEventCacheGenericUpdate> {
481 self.inner.generic_update_sender.subscribe()
482 }
483
484 pub fn automatic_pagination(&self) -> Option<AutomaticPagination> {
488 self.inner.automatic_pagination.get().cloned()
489 }
490}
491
492#[derive(Clone, Copy, Debug)]
494pub struct EventCacheConfig {
495 pub max_pinned_events_concurrent_requests: usize,
497
498 pub max_pinned_events_to_load: usize,
500
501 pub experimental_auto_backpagination: bool,
505
506 pub room_pagination_per_room_credit: usize,
515
516 pub room_pagination_batch_size: u16,
521}
522
523impl EventCacheConfig {
524 pub const DEFAULT_MAX_EVENTS_TO_LOAD: usize = 128;
526
527 pub const DEFAULT_MAX_CONCURRENT_REQUESTS: usize = 8;
530
531 pub const DEFAULT_ROOM_PAGINATION_CREDITS: usize = 20;
535
536 pub const DEFAULT_ROOM_PAGINATION_BATCH_SIZE: u16 = 30;
540}
541
542impl Default for EventCacheConfig {
543 fn default() -> Self {
544 Self {
545 max_pinned_events_concurrent_requests: Self::DEFAULT_MAX_CONCURRENT_REQUESTS,
546 max_pinned_events_to_load: Self::DEFAULT_MAX_EVENTS_TO_LOAD,
547 room_pagination_per_room_credit: Self::DEFAULT_ROOM_PAGINATION_CREDITS,
548 room_pagination_batch_size: Self::DEFAULT_ROOM_PAGINATION_BATCH_SIZE,
549 experimental_auto_backpagination: false,
550 }
551 }
552}
553
554type CachesByRoom = HashMap<OwnedRoomId, Caches>;
555
556struct EventCacheInner {
557 client: WeakClient,
560
561 config: StdRwLock<EventCacheConfig>,
563
564 state: StateLock,
567
568 by_room: Arc<RwLock<CachesByRoom>>,
572
573 drop_handles: OnceLock<Arc<EventCacheDropHandles>>,
575
576 auto_shrink_sender: OnceLock<mpsc::Sender<AutoShrinkMessage>>,
586
587 generic_update_sender: Sender<RoomEventCacheGenericUpdate>,
592
593 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
601
602 thread_subscriber_sender: Sender<()>,
608
609 #[cfg(feature = "e2e-encryption")]
610 redecryption_channels: redecryptor::RedecryptorChannels,
611
612 automatic_pagination: OnceLock<AutomaticPagination>,
617}
618
619impl EventCacheInner {
620 fn client(&self) -> Result<Client> {
621 self.client.get().ok_or(EventCacheError::ClientDropped)
622 }
623
624 async fn clear_all_rooms(&self) -> Result<()> {
626 self.state.clear_and_reload(self.by_room.write().await).await?;
653
654 Ok(())
655 }
656
657 #[instrument(skip(self, updates))]
659 async fn handle_room_updates(&self, updates: RoomUpdates) -> Result<()> {
660 for (room_id, left_room_update) in updates.left {
667 let Ok(caches) = self.all_caches_for_room(&room_id).await else {
668 error!(?room_id, "Room must exist");
669 continue;
670 };
671
672 if let Err(err) = caches.handle_left_room_update(left_room_update).await {
673 error!("handling left room update: {err}");
675 }
676 }
677
678 for (room_id, joined_room_update) in updates.joined {
680 trace!(?room_id, "Handling a `JoinedRoomUpdate`");
681
682 let Ok(caches) = self.all_caches_for_room(&room_id).await else {
683 error!(?room_id, "Room must exist");
684 continue;
685 };
686
687 if let Err(err) = caches.handle_joined_room_update(joined_room_update).await {
688 error!(%room_id, "handling joined room update: {err}");
690 }
691 }
692
693 Ok(())
697 }
698
699 async fn all_caches_for_room(
701 &self,
702 room_id: &RoomId,
703 ) -> Result<OwnedRwLockReadGuard<CachesByRoom, Caches>> {
704 match OwnedRwLockReadGuard::try_map(self.by_room.clone().read_owned().await, |by_room| {
707 by_room.get(room_id)
708 }) {
709 Ok(caches) => Ok(caches),
710
711 Err(by_room_guard) => {
712 drop(by_room_guard);
714 let by_room_guard = self.by_room.clone().write_owned().await;
715
716 let mut by_room_guard =
719 match OwnedRwLockWriteGuard::try_downgrade_map(by_room_guard, |by_room| {
720 by_room.get(room_id)
721 }) {
722 Ok(caches) => return Ok(caches),
723 Err(by_room_guard) => by_room_guard,
724 };
725
726 let caches = Caches::new(
727 &self.client,
728 room_id,
729 self.generic_update_sender.clone(),
730 self.linked_chunk_update_sender.clone(),
731 self.auto_shrink_sender.get().cloned().expect(
734 "we must have called `EventCache::subscribe()` before calling here.",
735 ),
736 &self.state,
737 self.automatic_pagination.get().cloned(),
738 )
739 .await?;
740
741 by_room_guard.insert(room_id.to_owned(), caches);
742
743 Ok(OwnedRwLockWriteGuard::try_downgrade_map(by_room_guard, |by_room| {
744 by_room.get(room_id)
745 })
746 .expect("`Caches` has just been inserted"))
747 }
748 }
749 }
750}
751
752#[derive(Debug, Clone)]
754pub enum EventsOrigin {
755 Sync,
757
758 Pagination,
760
761 Cache,
763}
764
765#[cfg(test)]
766mod tests {
767 use std::{ops::Not, sync::Arc, time::Duration};
768
769 use assert_matches::assert_matches;
770 use futures_util::FutureExt as _;
771 use matrix_sdk_base::{
772 RoomState,
773 linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update},
774 sync::{JoinedRoomUpdate, RoomUpdates, Timeline},
775 };
776 use matrix_sdk_test::{
777 JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
778 };
779 use ruma::{event_id, room_id, user_id};
780 use tokio::time::sleep;
781
782 use super::{EventCacheError, RoomEventCacheGenericUpdate};
783 use crate::test_utils::{
784 assert_event_matches_msg, client::MockClientBuilder, logged_in_client,
785 };
786
787 #[async_test]
788 async fn test_must_explicitly_subscribe() {
789 let client = logged_in_client(None).await;
790
791 let event_cache = client.event_cache();
792
793 let room_id = room_id!("!omelette:fromage.fr");
796 let result = event_cache.room(room_id).await;
797
798 assert_matches!(result, Err(EventCacheError::NotSubscribedYet));
801 }
802
803 #[async_test]
804 async fn test_get_event_by_id() {
805 let client = logged_in_client(None).await;
806 let room_id1 = room_id!("!galette:saucisse.bzh");
807 let room_id2 = room_id!("!crepe:saucisse.bzh");
808
809 client.base_client().get_or_create_room(room_id1, RoomState::Joined);
810 client.base_client().get_or_create_room(room_id2, RoomState::Joined);
811
812 let event_cache = client.event_cache();
813 event_cache.subscribe().unwrap();
814
815 let f = EventFactory::new().room(room_id1).sender(user_id!("@ben:saucisse.bzh"));
817
818 let eid1 = event_id!("$1");
819 let eid2 = event_id!("$2");
820 let eid3 = event_id!("$3");
821
822 let joined_room_update1 = JoinedRoomUpdate {
823 timeline: Timeline {
824 events: vec![
825 f.text_msg("hey").event_id(eid1).into(),
826 f.text_msg("you").event_id(eid2).into(),
827 ],
828 ..Default::default()
829 },
830 ..Default::default()
831 };
832
833 let joined_room_update2 = JoinedRoomUpdate {
834 timeline: Timeline {
835 events: vec![f.text_msg("bjr").event_id(eid3).into()],
836 ..Default::default()
837 },
838 ..Default::default()
839 };
840
841 let mut updates = RoomUpdates::default();
842 updates.joined.insert(room_id1.to_owned(), joined_room_update1);
843 updates.joined.insert(room_id2.to_owned(), joined_room_update2);
844
845 event_cache.inner.handle_room_updates(updates).await.unwrap();
847
848 let room1 = client.get_room(room_id1).unwrap();
850
851 let (room_event_cache, _drop_handles) = room1.event_cache().await.unwrap();
852
853 let found1 = room_event_cache.find_event(eid1).await.unwrap().unwrap();
854 assert_event_matches_msg(&found1, "hey");
855
856 let found2 = room_event_cache.find_event(eid2).await.unwrap().unwrap();
857 assert_event_matches_msg(&found2, "you");
858
859 assert!(room_event_cache.find_event(eid3).await.unwrap().is_none());
862 }
863
864 #[async_test]
865 async fn test_generic_update_when_loading_rooms() {
866 let user = user_id!("@mnt_io:matrix.org");
868 let client = logged_in_client(None).await;
869 let room_id_0 = room_id!("!raclette:patate.ch");
870 let room_id_1 = room_id!("!fondue:patate.ch");
871
872 let event_factory = EventFactory::new().room(room_id_0).sender(user);
873
874 let event_cache = client.event_cache();
875 event_cache.subscribe().unwrap();
876
877 client.base_client().get_or_create_room(room_id_0, RoomState::Joined);
878 client.base_client().get_or_create_room(room_id_1, RoomState::Joined);
879
880 client
881 .event_cache_store()
882 .lock()
883 .await
884 .expect("Could not acquire the event cache lock")
885 .as_clean()
886 .expect("Could not acquire a clean event cache lock")
887 .handle_linked_chunk_updates(
888 LinkedChunkId::Room(room_id_0),
889 vec![
890 Update::NewItemsChunk {
892 previous: None,
893 new: ChunkIdentifier::new(0),
894 next: None,
895 },
896 Update::PushItems {
897 at: Position::new(ChunkIdentifier::new(0), 0),
898 items: vec![
899 event_factory
900 .text_msg("hello")
901 .sender(user)
902 .event_id(event_id!("$ev0"))
903 .into_event(),
904 ],
905 },
906 ],
907 )
908 .await
909 .unwrap();
910
911 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
912
913 {
915 let _room_event_cache = event_cache.room(room_id_0).await.unwrap();
916
917 assert_matches!(
918 generic_stream.recv().await,
919 Ok(RoomEventCacheGenericUpdate { room_id }) => {
920 assert_eq!(room_id, room_id_0);
921 }
922 );
923 }
924
925 {
927 let _room_event_cache = event_cache.room(room_id_1).await.unwrap();
928
929 assert!(generic_stream.recv().now_or_never().is_none());
930 }
931 }
932
933 #[async_test]
934 async fn test_generic_update_when_paginating_room() {
935 let user = user_id!("@mnt_io:matrix.org");
937 let client = logged_in_client(None).await;
938 let room_id = room_id!("!raclette:patate.ch");
939
940 let event_factory = EventFactory::new().room(room_id).sender(user);
941
942 let event_cache = client.event_cache();
943 event_cache.subscribe().unwrap();
944
945 client.base_client().get_or_create_room(room_id, RoomState::Joined);
946
947 client
948 .event_cache_store()
949 .lock()
950 .await
951 .expect("Could not acquire the event cache lock")
952 .as_clean()
953 .expect("Could not acquire a clean event cache lock")
954 .handle_linked_chunk_updates(
955 LinkedChunkId::Room(room_id),
956 vec![
957 Update::NewItemsChunk {
959 previous: None,
960 new: ChunkIdentifier::new(0),
961 next: None,
962 },
963 Update::NewItemsChunk {
965 previous: Some(ChunkIdentifier::new(0)),
966 new: ChunkIdentifier::new(1),
967 next: None,
968 },
969 Update::NewItemsChunk {
971 previous: Some(ChunkIdentifier::new(1)),
972 new: ChunkIdentifier::new(2),
973 next: None,
974 },
975 Update::PushItems {
976 at: Position::new(ChunkIdentifier::new(2), 0),
977 items: vec![
978 event_factory
979 .text_msg("hello")
980 .sender(user)
981 .event_id(event_id!("$ev0"))
982 .into_event(),
983 ],
984 },
985 Update::NewItemsChunk {
987 previous: Some(ChunkIdentifier::new(2)),
988 new: ChunkIdentifier::new(3),
989 next: None,
990 },
991 Update::PushItems {
992 at: Position::new(ChunkIdentifier::new(3), 0),
993 items: vec![
994 event_factory
995 .text_msg("world")
996 .sender(user)
997 .event_id(event_id!("$ev1"))
998 .into_event(),
999 ],
1000 },
1001 ],
1002 )
1003 .await
1004 .unwrap();
1005
1006 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1007
1008 let (room_event_cache, _) = event_cache.room(room_id).await.unwrap();
1010
1011 assert_matches!(
1012 generic_stream.recv().await,
1013 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1014 assert_eq!(room_id, expected_room_id);
1015 }
1016 );
1017
1018 let pagination = room_event_cache.pagination();
1019
1020 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1022
1023 assert_eq!(pagination_outcome.events.len(), 1);
1024 assert!(pagination_outcome.reached_start.not());
1025 assert_matches!(
1026 generic_stream.recv().await,
1027 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
1028 assert_eq!(room_id, expected_room_id);
1029 }
1030 );
1031
1032 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1034
1035 assert!(pagination_outcome.events.is_empty());
1036 assert!(pagination_outcome.reached_start.not());
1037 assert!(generic_stream.recv().now_or_never().is_none());
1038
1039 let pagination_outcome = pagination.run_backwards_once(1).await.unwrap();
1041
1042 assert!(pagination_outcome.reached_start);
1043 assert!(generic_stream.recv().now_or_never().is_none());
1044 }
1045
1046 #[async_test]
1047 async fn test_for_room_when_room_is_not_found() {
1048 let client = logged_in_client(None).await;
1049 let room_id = room_id!("!raclette:patate.ch");
1050
1051 let event_cache = client.event_cache();
1052 event_cache.subscribe().unwrap();
1053
1054 assert_matches!(
1056 event_cache.room(room_id).await,
1057 Err(EventCacheError::RoomNotFound { room_id: not_found_room_id }) => {
1058 assert_eq!(room_id, not_found_room_id);
1059 }
1060 );
1061
1062 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1064
1065 assert!(event_cache.room(room_id).await.is_ok());
1067 }
1068
1069 #[cfg(not(target_family = "wasm"))]
1072 #[async_test]
1073 async fn test_no_refcycle_event_cache_tasks() {
1074 let client = MockClientBuilder::new(None).build().await;
1075
1076 sleep(Duration::from_secs(1)).await;
1078
1079 let event_cache_weak = Arc::downgrade(&client.event_cache().inner);
1080 assert_eq!(event_cache_weak.strong_count(), 1);
1081
1082 {
1083 let room_id = room_id!("!room:example.org");
1084
1085 let response = SyncResponseBuilder::default()
1087 .add_joined_room(JoinedRoomBuilder::new(room_id))
1088 .build_sync_response();
1089 client.inner.base_client.receive_sync_response(response).await.unwrap();
1090
1091 client.event_cache().subscribe().unwrap();
1092
1093 let (_room_event_cache, _drop_handles) =
1094 client.get_room(room_id).unwrap().event_cache().await.unwrap();
1095 }
1096
1097 drop(client);
1098
1099 sleep(Duration::from_secs(1)).await;
1101
1102 assert_eq!(
1104 event_cache_weak.strong_count(),
1105 0,
1106 "Too many strong references to the event cache {}",
1107 event_cache_weak.strong_count()
1108 );
1109 }
1110}