1pub mod pagination;
18mod state;
19mod updates;
20
21use std::{fmt, sync::Arc};
22
23use matrix_sdk_base::{
24 event_cache::Event,
25 sync::{JoinedRoomUpdate, LeftRoomUpdate, Timeline},
26};
27use ruma::{
28 EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, events::relation::RelationType,
29 room_version_rules::RoomVersionRules,
30};
31use tokio::sync::{Notify, broadcast::Sender, mpsc};
32use tracing::{instrument, trace};
33
34use self::pagination::ThreadPagination;
35pub(in super::super) use self::state::ThreadEventCacheState;
36pub(super) use self::updates::ThreadEventCacheUpdateSender;
37#[cfg(feature = "e2e-encryption")]
38use super::super::redecryptor::ResolvedUtd;
39use super::{
40 super::{
41 Result,
42 states::{CacheStateLock, StateLock, selectors::ThreadStateSelector},
43 },
44 EventsOrigin, TimelineVectorDiffs,
45 room::{RoomEventCacheGenericUpdate, RoomEventCacheLinkedChunkUpdate},
46 subscriber::{AutoShrinkMessage, Subscriber},
47};
48use crate::room::WeakRoom;
49
50#[derive(Clone)]
54pub struct ThreadEventCache {
55 inner: Arc<ThreadEventCacheInner>,
56}
57
58struct ThreadEventCacheInner {
60 room_id: OwnedRoomId,
62
63 thread_id: OwnedEventId,
65
66 weak_room: WeakRoom,
68
69 state: CacheStateLock<ThreadStateSelector>,
71
72 pagination_batch_token_notifier: Notify,
74
75 auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
80
81 update_sender: ThreadEventCacheUpdateSender,
83}
84
85impl fmt::Debug for ThreadEventCache {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 f.debug_struct("ThreadEventCache").finish_non_exhaustive()
88 }
89}
90
91impl ThreadEventCache {
92 #[allow(clippy::too_many_arguments)]
94 pub(super) async fn new(
95 room_id: OwnedRoomId,
96 thread_id: OwnedEventId,
97 own_user_id: OwnedUserId,
98 room_version_rules: RoomVersionRules,
99 weak_room: WeakRoom,
100 state: &StateLock,
101 auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
102 generic_update_sender: Sender<RoomEventCacheGenericUpdate>,
103 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
104 ) -> Result<Self> {
105 let update_sender = ThreadEventCacheUpdateSender::new(generic_update_sender.clone());
106
107 let cache_state = state
108 .try_insert_once_with(
109 ThreadStateSelector::new(room_id.clone(), thread_id.clone()),
110 |store_guard| {
111 ThreadEventCacheState::new(
112 room_id.clone(),
113 thread_id.clone(),
114 own_user_id,
115 room_version_rules,
116 store_guard,
117 update_sender.clone(),
118 linked_chunk_update_sender,
119 )
120 },
121 )
122 .await?;
123
124 let timeline_is_not_empty =
125 cache_state.read().await?.thread_linked_chunk().revents().next().is_some();
126
127 let cache = Self {
128 inner: Arc::new(ThreadEventCacheInner {
129 room_id: room_id.clone(),
130 thread_id,
131 weak_room,
132 state: cache_state,
133 pagination_batch_token_notifier: Notify::new(),
134 auto_shrink_sender,
135 update_sender,
136 }),
137 };
138
139 if timeline_is_not_empty {
142 let _ = generic_update_sender
143 .send(RoomEventCacheGenericUpdate { room_id: room_id.to_owned() });
144 }
145
146 Ok(cache)
147 }
148
149 pub fn room_id(&self) -> &RoomId {
151 &self.inner.room_id
152 }
153
154 pub fn thread_id(&self) -> &EventId {
156 &self.inner.thread_id
157 }
158
159 pub async fn subscribe(&self) -> Result<(Vec<Event>, Subscriber<TimelineVectorDiffs>)> {
165 let state = self.inner.state.read().await?;
166 let events =
167 state.thread_linked_chunk().events().map(|(_position, item)| item.clone()).collect();
168
169 let subscribers_handle = state.subscribers_handle();
170
171 let subscriber = Subscriber::new(
172 self.inner.update_sender.new_thread_receiver(),
173 AutoShrinkMessage::Thread {
174 room_id: self.inner.room_id.clone(),
175 thread_id: self.inner.thread_id.clone(),
176 },
177 self.inner.auto_shrink_sender.clone(),
178 subscribers_handle,
179 );
180
181 trace!("added a thread event cache subscriber; new count: {}", subscribers_handle.count());
182
183 Ok((events, subscriber))
184 }
185
186 pub fn pagination(&self) -> ThreadPagination {
189 ThreadPagination::new(self.inner.clone())
190 }
191
192 pub(in super::super) fn state(&self) -> &CacheStateLock<ThreadStateSelector> {
194 &self.inner.state
195 }
196
197 #[instrument(skip_all, fields(room_id = %self.inner.room_id, thread_root = %self.inner.thread_id))]
199 pub(super) async fn handle_joined_room_update(&self, updates: JoinedRoomUpdate) -> Result<()> {
200 self.handle_timeline(updates.timeline).await?;
201
202 Ok(())
203 }
204
205 #[instrument(skip_all, fields(room_id = %self.inner.room_id, thread_root = %self.inner.thread_id))]
207 pub(super) async fn handle_left_room_update(&self, updates: LeftRoomUpdate) -> Result<()> {
208 self.handle_timeline(updates.timeline).await?;
209
210 Ok(())
211 }
212
213 async fn handle_timeline(&self, timeline: Timeline) -> Result<()> {
216 if timeline.events.is_empty() && timeline.prev_batch.is_none() {
217 return Ok(());
218 }
219
220 trace!("adding new events");
221
222 let mut state = self.inner.state.write().await?;
223
224 let (stored_prev_batch_token, timeline_event_diffs) = state.handle_sync(timeline).await?;
225
226 if stored_prev_batch_token {
229 self.inner.pagination_batch_token_notifier.notify_one();
230 }
231
232 if !timeline_event_diffs.is_empty() {
233 state.update_sender.send(
234 TimelineVectorDiffs { diffs: timeline_event_diffs, origin: EventsOrigin::Sync },
235 None,
238 );
239 }
240
241 Ok(())
242 }
243
244 pub(super) async fn find_event(
249 &self,
250 event_id: &EventId,
251 ) -> Result<Option<(super::EventLocation, Event)>> {
252 self.inner.state.read().await?.find_event(event_id).await
253 }
254
255 pub async fn find_event_with_relations(
268 &self,
269 event_id: &EventId,
270 filter: Option<Vec<RelationType>>,
271 ) -> Result<Option<(Event, Vec<Event>)>> {
272 Ok(self
274 .inner
275 .state
276 .read()
277 .await?
278 .find_event_with_relations(event_id, filter)
279 .await
280 .ok()
281 .flatten())
282 }
283
284 #[cfg(feature = "e2e-encryption")]
290 pub(in super::super) async fn replace_utds(&self, events: &[ResolvedUtd]) -> Result<bool> {
291 let mut state = self.inner.state.write().await?;
292 let timeline_event_diffs = state.replace_utds(events).await?;
293
294 Ok(
295 if let Some(timeline_event_diffs) = timeline_event_diffs
296 && !timeline_event_diffs.is_empty()
297 {
298 state.update_sender.send(
299 TimelineVectorDiffs {
300 diffs: timeline_event_diffs,
301 origin: EventsOrigin::Cache,
302 },
303 Some(RoomEventCacheGenericUpdate { room_id: self.inner.room_id.clone() }),
304 );
305
306 true
307 } else {
308 false
309 },
310 )
311 }
312}
313
314#[cfg(all(test, not(target_family = "wasm")))] mod timed_tests {
316 use std::sync::Arc;
317
318 use assert_matches::assert_matches;
319 use assert_matches2::assert_let;
320 use eyeball_im::VectorDiff;
321 use futures_util::FutureExt as _;
322 use matrix_sdk_base::{
323 RoomState, ThreadingSupport,
324 cross_process_lock::CrossProcessLockConfig,
325 event_cache::{
326 Gap,
327 store::{EventCacheStore as _, MemoryStore},
328 },
329 linked_chunk::{
330 ChunkContent, ChunkIdentifier, LinkedChunkId, Position, Update,
331 lazy_loader::from_all_chunks,
332 },
333 store::StoreConfig,
334 sync::{JoinedRoomUpdate, Timeline},
335 };
336 use matrix_sdk_test::{ALICE, async_test, event_factory::EventFactory};
337 use ruma::{
338 event_id,
339 events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent},
340 room_id, user_id,
341 };
342 use tokio::task::yield_now;
343
344 use super::super::{super::RoomEventCacheGenericUpdate, TimelineVectorDiffs};
345 use crate::{assert_let_timeout, test_utils::client::MockClientBuilder};
346
347 #[async_test]
348 async fn test_write_to_storage() {
349 let room_id = room_id!("!r0");
350 let thread_root = event_id!("$t0_ev0");
351 let thread_event_id_0 = event_id!("$t0_ev1");
352
353 let f = EventFactory::new().room(room_id).sender(user_id!("@mnt_io:matrix.org"));
354
355 let event_cache_store = Arc::new(MemoryStore::new());
356
357 let client = MockClientBuilder::new(None)
358 .on_builder(|builder| {
359 builder
360 .store_config(
361 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
362 .event_cache_store(event_cache_store.clone()),
363 )
364 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
365 })
366 .build()
367 .await;
368
369 let event_cache = client.event_cache();
370 event_cache.subscribe().unwrap();
371
372 client.base_client().get_or_create_room(room_id, RoomState::Joined);
373
374 let (thread_event_cache, _drop_handles) =
375 event_cache.thread(room_id, thread_root).await.unwrap();
376 let (thread_events, mut thread_stream) = thread_event_cache.subscribe().await.unwrap();
377
378 assert!(thread_events.is_empty());
379
380 let timeline = Timeline {
382 limited: true,
383 prev_batch: Some("raclette".to_owned()),
384 events: vec![
385 f.text_msg("salut")
386 .event_id(thread_event_id_0)
387 .in_thread(thread_root, thread_root)
388 .into_event(),
389 ],
390 };
391
392 thread_event_cache
393 .handle_joined_room_update(JoinedRoomUpdate { timeline, ..Default::default() })
394 .await
395 .unwrap();
396
397 assert_matches!(
398 thread_stream.recv().await,
399 Ok(TimelineVectorDiffs { diffs, .. }) => {
400 assert_eq!(diffs.len(), 2);
401 assert_matches!(&diffs[0], VectorDiff::Clear);
402 assert_matches!(&diffs[1], VectorDiff::Append { values: events } => {
403 assert_eq!(events.len(), 1);
404 assert_eq!(events[0].event_id(), Some(thread_event_id_0));
405 });
406 }
407 );
408 assert!(thread_stream.is_empty());
409
410 let linked_chunk = from_all_chunks::<3, _, _>(
412 event_cache_store
413 .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root))
414 .await
415 .unwrap(),
416 )
417 .unwrap()
418 .unwrap();
419
420 assert_eq!(linked_chunk.chunks().count(), 2);
421
422 let mut chunks = linked_chunk.chunks();
423
424 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Gap(gap) => {
426 assert_eq!(gap.token, "raclette");
427 });
428
429 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
431 assert_eq!(events.len(), 1);
432 assert_eq!(events[0].event_id(), Some(thread_event_id_0));
433 });
434
435 assert!(chunks.next().is_none());
437 }
438
439 #[async_test]
440 async fn test_write_to_storage_strips_bundled_relations() {
441 let sender = user_id!("@mnt_io:matrix.org");
442 let room_id = room_id!("!r0");
443 let thread_root = event_id!("$t0_ev0");
444 let thread_event_id_0 = event_id!("$t0_ev1");
445
446 let f = EventFactory::new().room(room_id).sender(sender);
447
448 let event_cache_store = Arc::new(MemoryStore::new());
449
450 let client = MockClientBuilder::new(None)
451 .on_builder(|builder| {
452 builder
453 .store_config(
454 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
455 .event_cache_store(event_cache_store.clone()),
456 )
457 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
458 })
459 .build()
460 .await;
461
462 let event_cache = client.event_cache();
463 event_cache.subscribe().unwrap();
464
465 client.base_client().get_or_create_room(room_id, RoomState::Joined);
466
467 let (thread_event_cache, _drop_handles) =
468 event_cache.thread(room_id, thread_root).await.unwrap();
469
470 let timeline = Timeline {
472 limited: false,
473 prev_batch: None,
474 events: vec![
475 f.text_msg("s 'up")
476 .event_id(thread_event_id_0)
477 .with_bundled_edit(f.text_msg("Hello, Kind Sir").sender(sender))
478 .in_thread(thread_root, thread_root)
479 .into_event(),
480 ],
481 };
482
483 thread_event_cache
484 .handle_joined_room_update(JoinedRoomUpdate { timeline, ..Default::default() })
485 .await
486 .unwrap();
487
488 {
490 let (events, _) = thread_event_cache.subscribe().await.unwrap();
491
492 assert_eq!(events.len(), 1);
493
494 let event = events[0].raw().deserialize().unwrap();
495 assert_eq!(event.event_id(), thread_event_id_0);
496 assert_let!(
497 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) =
498 event
499 );
500 assert!(msg.as_original().unwrap().unsigned.relations.replace.is_some());
501 }
502
503 let linked_chunk = from_all_chunks::<3, _, _>(
505 event_cache_store
506 .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root))
507 .await
508 .unwrap(),
509 )
510 .unwrap()
511 .unwrap();
512
513 assert_eq!(linked_chunk.chunks().count(), 1);
514
515 let mut chunks = linked_chunk.chunks();
516 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
517 assert_eq!(events.len(), 1);
518
519 let event = events[0].raw().deserialize().unwrap();
520 assert_eq!(event.event_id(), thread_event_id_0);
521
522 assert_let!(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = event);
523 assert!(msg.as_original().unwrap().unsigned.relations.replace.is_none());
524 });
525
526 assert!(chunks.next().is_none());
528 }
529
530 #[async_test]
531 async fn test_clear() {
532 let room_id = room_id!("!r0");
533 let f = EventFactory::new().room(room_id).sender(user_id!("@mnt_io:matrix.org"));
534
535 let event_cache_store = Arc::new(MemoryStore::new());
536
537 let thread_root = event_id!("$t0_ev0");
538 let thread_event_id_0 = event_id!("$t0_ev1");
539 let thread_event_id_1 = event_id!("$t0_ev2");
540
541 let thread_event_0 = f
542 .text_msg("foo")
543 .event_id(thread_event_id_0)
544 .in_thread(thread_root, thread_root)
545 .into_event();
546 let thread_event_1 = f
547 .text_msg("bar")
548 .event_id(thread_event_id_1)
549 .in_thread(thread_root, thread_event_id_0)
550 .into_event();
551
552 event_cache_store
554 .handle_linked_chunk_updates(
555 LinkedChunkId::Thread(room_id, thread_root),
556 vec![
557 Update::NewItemsChunk {
559 previous: None,
560 new: ChunkIdentifier::new(0),
561 next: None,
562 },
563 Update::NewGapChunk {
565 previous: Some(ChunkIdentifier::new(0)),
566 new: ChunkIdentifier::new(42),
568 next: None,
569 gap: Gap { token: "comté".to_owned() },
570 },
571 Update::NewItemsChunk {
573 previous: Some(ChunkIdentifier::new(42)),
574 new: ChunkIdentifier::new(1),
575 next: None,
576 },
577 Update::PushItems {
578 at: Position::new(ChunkIdentifier::new(1), 0),
579 items: vec![thread_event_0.clone()],
580 },
581 Update::NewItemsChunk {
583 previous: Some(ChunkIdentifier::new(1)),
584 new: ChunkIdentifier::new(2),
585 next: None,
586 },
587 Update::PushItems {
588 at: Position::new(ChunkIdentifier::new(2), 0),
589 items: vec![thread_event_1.clone()],
590 },
591 ],
592 )
593 .await
594 .unwrap();
595
596 let client = MockClientBuilder::new(None)
597 .on_builder(|builder| {
598 builder
599 .store_config(
600 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
601 .event_cache_store(event_cache_store.clone()),
602 )
603 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
604 })
605 .build()
606 .await;
607
608 let event_cache = client.event_cache();
609 event_cache.subscribe().unwrap();
610
611 client.base_client().get_or_create_room(room_id, RoomState::Joined);
612
613 let (thread_event_cache, _drop_handles) =
614 event_cache.thread(room_id, thread_root).await.unwrap();
615 let (thread_events, mut thread_stream) = thread_event_cache.subscribe().await.unwrap();
616
617 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
618
619 {
621 assert!(thread_event_cache.find_event(thread_event_id_0).await.unwrap().is_some());
622 assert!(thread_event_cache.find_event(thread_event_id_1).await.unwrap().is_some());
623 }
624
625 {
627 assert_eq!(thread_events.len(), 1);
630 assert_eq!(thread_events[0].event_id().unwrap(), thread_event_id_1);
631
632 assert!(thread_stream.is_empty());
633 }
634
635 {
637 thread_event_cache.pagination().run_backwards_once(20).await.unwrap();
638
639 assert_matches!(
640 thread_stream.recv().await,
641 Ok(TimelineVectorDiffs { diffs, .. }) => {
642 assert_eq!(diffs.len(), 1);
643 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
644 assert_eq!(event.event_id(), Some(thread_event_id_0));
646 });
647 }
648 );
649 assert!(thread_stream.is_empty());
650
651 assert_matches!(
652 generic_stream.recv().await,
653 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
654 assert_eq!(room_id, expected_room_id);
655 }
656 );
657 assert!(generic_stream.is_empty());
658 }
659
660 event_cache.clear_all_rooms().await.unwrap();
662
663 assert_matches!(
665 thread_stream.recv().await,
666 Ok(TimelineVectorDiffs { diffs, .. }) => {
667 assert_eq!(diffs.len(), 1);
668 assert_matches!(&diffs[0], VectorDiff::Clear);
669 }
670 );
671
672 assert_matches!(
675 generic_stream.recv().await,
676 Ok(RoomEventCacheGenericUpdate { room_id: received_room_id }) => {
677 assert_eq!(received_room_id, room_id);
678 }
679 );
680 assert_matches!(
682 generic_stream.recv().await,
683 Ok(RoomEventCacheGenericUpdate { room_id: received_room_id }) => {
684 assert_eq!(received_room_id, room_id);
685 }
686 );
687 assert!(generic_stream.is_empty());
688
689 assert!(thread_event_cache.find_event(thread_event_id_0).await.unwrap().is_none());
692 assert!(thread_event_cache.find_event(thread_event_id_1).await.unwrap().is_none());
693
694 let (thread_events, _) = thread_event_cache.subscribe().await.unwrap();
696 assert!(thread_events.is_empty());
697
698 assert!(
700 event_cache_store
701 .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root))
702 .await
703 .unwrap()
704 .is_empty()
705 );
706 }
707
708 #[async_test]
709 async fn test_load_from_storage() {
710 let room_id = room_id!("!r0");
711 let f = EventFactory::new().room(room_id).sender(user_id!("@mnt_io:matrix.org"));
712
713 let event_cache_store = Arc::new(MemoryStore::new());
714
715 let thread_root = event_id!("$t0");
716 let thread_event_id_0 = event_id!("$t0_ev0");
717 let thread_event_id_1 = event_id!("$t0_ev1");
718
719 let thread_event_0 = f
720 .text_msg("hello world")
721 .event_id(thread_event_id_0)
722 .in_thread(thread_root, thread_root)
723 .into_event();
724 let thread_event_1 = f
725 .text_msg("how's it going")
726 .event_id(thread_event_id_1)
727 .in_thread(thread_root, thread_event_id_1)
728 .into_event();
729
730 let updates = vec![
734 Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(0), next: None },
736 Update::NewGapChunk {
738 previous: Some(ChunkIdentifier::new(0)),
739 new: ChunkIdentifier::new(42),
741 next: None,
742 gap: Gap { token: "gruyère".to_owned() },
743 },
744 Update::NewItemsChunk {
746 previous: Some(ChunkIdentifier::new(42)),
747 new: ChunkIdentifier::new(1),
748 next: None,
749 },
750 Update::PushItems {
751 at: Position::new(ChunkIdentifier::new(1), 0),
752 items: vec![thread_event_0.clone()],
753 },
754 Update::NewItemsChunk {
756 previous: Some(ChunkIdentifier::new(1)),
757 new: ChunkIdentifier::new(2),
758 next: None,
759 },
760 Update::PushItems {
761 at: Position::new(ChunkIdentifier::new(2), 0),
762 items: vec![thread_event_1.clone()],
763 },
764 ];
765 event_cache_store
766 .handle_linked_chunk_updates(LinkedChunkId::Room(room_id), updates.clone())
767 .await
768 .unwrap();
769 event_cache_store
770 .handle_linked_chunk_updates(LinkedChunkId::Thread(room_id, thread_root), updates)
771 .await
772 .unwrap();
773
774 let client = MockClientBuilder::new(None)
775 .on_builder(|builder| {
776 builder
777 .store_config(
778 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
779 .event_cache_store(event_cache_store.clone()),
780 )
781 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
782 })
783 .build()
784 .await;
785
786 let event_cache = client.event_cache();
787 event_cache.subscribe().unwrap();
788
789 client.base_client().get_or_create_room(room_id, RoomState::Joined);
790
791 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
793 let (thread_event_cache, _drop_handles) =
794 event_cache.thread(room_id, thread_root).await.unwrap();
795 let (thread_events, mut thread_stream) = thread_event_cache.subscribe().await.unwrap();
796
797 for _ in 0..2 {
800 assert_matches!(
801 generic_stream.recv().await,
802 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
803 assert_eq!(room_id, expected_room_id);
804 }
805 );
806 }
807 assert!(generic_stream.is_empty());
808
809 assert_eq!(thread_events.len(), 1);
812 assert_eq!(thread_events[0].event_id().unwrap(), thread_event_id_1);
813 assert!(thread_stream.is_empty());
814
815 assert!(thread_event_cache.find_event(thread_event_id_0).await.unwrap().is_some());
818 assert!(thread_event_cache.find_event(thread_event_id_1).await.unwrap().is_some());
819
820 thread_event_cache.pagination().run_backwards_once(20).await.unwrap();
822
823 assert_matches!(
824 thread_stream.recv().await,
825 Ok(TimelineVectorDiffs { diffs, .. }) => {
826 assert_eq!(diffs.len(), 1);
827 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
828 assert_eq!(event.event_id(), Some(thread_event_id_0));
829 });
830 }
831 );
832 assert!(thread_stream.is_empty());
833
834 assert_matches!(
836 generic_stream.recv().await,
837 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
838 assert_eq!(expected_room_id, room_id);
839 }
840 );
841 assert!(generic_stream.is_empty());
842
843 let timeline = Timeline { limited: false, prev_batch: None, events: vec![thread_event_1] };
845
846 thread_event_cache
847 .handle_joined_room_update(JoinedRoomUpdate { timeline, ..Default::default() })
848 .await
849 .unwrap();
850
851 assert!(generic_stream.recv().now_or_never().is_none());
854
855 let (thread_events, _) = thread_event_cache.subscribe().await.unwrap();
860 assert_eq!(thread_events.len(), 2);
861 assert_eq!(thread_events[0].event_id(), Some(thread_event_id_0));
862 assert_eq!(thread_events[1].event_id(), Some(thread_event_id_1));
863 }
864
865 #[async_test]
866 async fn test_load_from_storage_resilient_to_failure() {
867 let room_id = room_id!("!r0");
868 let f = EventFactory::new().room(room_id).sender(user_id!("@mnt_io:matrix.org"));
869
870 let event_cache_store = Arc::new(MemoryStore::new());
871
872 let thread_root = event_id!("$t0");
873 let thread_event_id_0 = event_id!("$t0_ev0");
874
875 let thread_event_0 = f
876 .text_msg("hello world")
877 .event_id(thread_event_id_0)
878 .in_thread(thread_root, thread_root)
879 .into_event();
880
881 event_cache_store
883 .handle_linked_chunk_updates(
884 LinkedChunkId::Thread(room_id, thread_root),
885 vec![
886 Update::NewItemsChunk {
887 previous: None,
888 new: ChunkIdentifier::new(0),
889 next: None,
890 },
891 Update::PushItems {
892 at: Position::new(ChunkIdentifier::new(0), 0),
893 items: vec![thread_event_0],
894 },
895 Update::NewItemsChunk {
896 previous: Some(ChunkIdentifier::new(0)),
897 new: ChunkIdentifier::new(1),
898 next: Some(ChunkIdentifier::new(0)),
899 },
900 ],
901 )
902 .await
903 .unwrap();
904
905 let client = MockClientBuilder::new(None)
906 .on_builder(|builder| {
907 builder
908 .store_config(
909 StoreConfig::new(CrossProcessLockConfig::multi_process("holder"))
910 .event_cache_store(event_cache_store.clone()),
911 )
912 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
913 })
914 .build()
915 .await;
916
917 let event_cache = client.event_cache();
918 event_cache.subscribe().unwrap();
919
920 client.base_client().get_or_create_room(room_id, RoomState::Joined);
921
922 let (thread_event_cache, _drop_handles) =
923 event_cache.thread(room_id, thread_root).await.unwrap();
924 let (thread_events, _) = thread_event_cache.subscribe().await.unwrap();
925
926 assert!(thread_events.is_empty());
929
930 let raw_chunks = event_cache_store
933 .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root))
934 .await
935 .unwrap();
936 assert!(raw_chunks.is_empty());
937 }
938
939 #[async_test]
940 async fn test_reload_when_dirty() {
941 let user_id = user_id!("@mnt_io:matrix.org");
942 let room_id = room_id!("!raclette:patate.ch");
943
944 let event_cache_store = MemoryStore::new();
946
947 let client_p0 = MockClientBuilder::new(None)
949 .on_builder(|builder| {
950 builder
951 .store_config(
952 StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
953 .event_cache_store(event_cache_store.clone()),
954 )
955 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
956 })
957 .build()
958 .await;
959
960 let client_p1 = MockClientBuilder::new(None)
962 .on_builder(|builder| {
963 builder
964 .store_config(
965 StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
966 .event_cache_store(event_cache_store),
967 )
968 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
969 })
970 .build()
971 .await;
972
973 let event_factory = EventFactory::new().room(room_id).sender(user_id);
974
975 let thread_root = event_id!("$t0");
976 let thread_event_id_0 = event_id!("$t0_ev0");
977 let thread_event_id_1 = event_id!("$t0_ev1");
978
979 let thread_event_0 = event_factory
980 .text_msg("comté")
981 .event_id(thread_event_id_0)
982 .in_thread(thread_root, thread_root)
983 .into_event();
984 let thread_event_1 = event_factory
985 .text_msg("morbier")
986 .event_id(thread_event_id_1)
987 .in_thread(thread_root, thread_event_id_0)
988 .into_event();
989
990 client_p0
992 .event_cache_store()
993 .lock()
994 .await
995 .expect("[p0] Could not acquire the event cache lock")
996 .as_clean()
997 .expect("[p0] Could not acquire a clean event cache lock")
998 .handle_linked_chunk_updates(
999 LinkedChunkId::Thread(room_id, thread_root),
1000 vec![
1001 Update::NewItemsChunk {
1002 previous: None,
1003 new: ChunkIdentifier::new(0),
1004 next: None,
1005 },
1006 Update::PushItems {
1007 at: Position::new(ChunkIdentifier::new(0), 0),
1008 items: vec![thread_event_0],
1009 },
1010 Update::NewItemsChunk {
1011 previous: Some(ChunkIdentifier::new(0)),
1012 new: ChunkIdentifier::new(1),
1013 next: None,
1014 },
1015 Update::PushItems {
1016 at: Position::new(ChunkIdentifier::new(1), 0),
1017 items: vec![thread_event_1],
1018 },
1019 ],
1020 )
1021 .await
1022 .unwrap();
1023
1024 let (thread_event_cache_p0, thread_event_cache_p1) = {
1026 let event_cache_p0 = client_p0.event_cache();
1027 event_cache_p0.subscribe().unwrap();
1028
1029 let event_cache_p1 = client_p1.event_cache();
1030 event_cache_p1.subscribe().unwrap();
1031
1032 client_p0.base_client().get_or_create_room(room_id, RoomState::Joined);
1033 client_p1.base_client().get_or_create_room(room_id, RoomState::Joined);
1034
1035 let (thread_event_cache_p0, _drop_handles) =
1036 event_cache_p0.thread(room_id, thread_root).await.unwrap();
1037 let (thread_event_cache_p1, _drop_handles) =
1038 event_cache_p1.thread(room_id, thread_root).await.unwrap();
1039
1040 (thread_event_cache_p0, thread_event_cache_p1)
1041 };
1042
1043 let mut updates_stream_p0 = {
1048 let thread_event_cache = &thread_event_cache_p0;
1049
1050 let (initial_updates, mut updates_stream) =
1051 thread_event_cache_p0.subscribe().await.unwrap();
1052
1053 assert_eq!(initial_updates.len(), 1);
1055 assert_eq!(initial_updates[0].event_id(), Some(thread_event_id_1));
1056 assert!(updates_stream.is_empty());
1057
1058 thread_event_cache.pagination().run_backwards_once(1).await.unwrap();
1060
1061 assert_matches!(
1063 updates_stream.recv().await.unwrap(),
1064 TimelineVectorDiffs { diffs, .. } => {
1065 assert_eq!(diffs.len(), 1, "{diffs:#?}");
1066 assert_matches!(
1067 &diffs[0],
1068 VectorDiff::Insert { index: 0, value: event } => {
1069 assert_eq!(event.event_id(), Some(thread_event_id_0));
1070 }
1071 );
1072 }
1073 );
1074
1075 updates_stream
1076 };
1077
1078 let mut updates_stream_p1 = {
1080 let thread_event_cache = &thread_event_cache_p1;
1081 let (initial_updates, mut updates_stream) =
1082 thread_event_cache_p1.subscribe().await.unwrap();
1083
1084 assert_eq!(initial_updates.len(), 1);
1086 assert_eq!(initial_updates[0].event_id(), Some(thread_event_id_1));
1087 assert!(updates_stream.is_empty());
1088
1089 thread_event_cache.pagination().run_backwards_once(1).await.unwrap();
1091
1092 assert_matches!(
1094 updates_stream.recv().await.unwrap(),
1095 TimelineVectorDiffs { diffs, .. } => {
1096 assert_eq!(diffs.len(), 1, "{diffs:#?}");
1097 assert_matches!(
1098 &diffs[0],
1099 VectorDiff::Insert { index: 0, value: event } => {
1100 assert_eq!(event.event_id(), Some(thread_event_id_0));
1101 }
1102 );
1103 }
1104 );
1105
1106 updates_stream
1107 };
1108
1109 for _ in 0..3 {
1111 {
1115 let thread_event_cache = &thread_event_cache_p0;
1116 let updates_stream = &mut updates_stream_p0;
1117
1118 let (initial_updates, _) = thread_event_cache.subscribe().await.unwrap();
1122
1123 assert_eq!(initial_updates.len(), 1);
1124 assert_eq!(initial_updates[0].event_id(), Some(thread_event_id_1));
1125
1126 assert_matches!(
1128 updates_stream.recv().await.unwrap(),
1129 TimelineVectorDiffs { diffs, .. } => {
1130 assert_eq!(diffs.len(), 2, "{diffs:#?}");
1131 assert_matches!(&diffs[0], VectorDiff::Clear);
1132 assert_matches!(
1133 &diffs[1],
1134 VectorDiff::Append { values: events } => {
1135 assert_eq!(events.len(), 1);
1136 assert_eq!(events[0].event_id(), Some(thread_event_id_1));
1137 }
1138 );
1139 }
1140 );
1141
1142 thread_event_cache.pagination().run_backwards_once(1).await.unwrap();
1144
1145 assert_matches!(
1148 updates_stream.recv().await.unwrap(),
1149 TimelineVectorDiffs { diffs, .. } => {
1150 assert_eq!(diffs.len(), 1, "{diffs:#?}");
1151 assert_matches!(
1152 &diffs[0],
1153 VectorDiff::Insert { index: 0, value: event } => {
1154 assert_eq!(event.event_id(), Some(thread_event_id_0));
1155 }
1156 );
1157 }
1158 );
1159 }
1160
1161 {
1165 let thread_event_cache = &thread_event_cache_p1;
1166 let updates_stream = &mut updates_stream_p1;
1167
1168 let (initial_updates, _) = thread_event_cache.subscribe().await.unwrap();
1172
1173 assert_eq!(initial_updates.len(), 1);
1174 assert_eq!(initial_updates[0].event_id(), Some(thread_event_id_1));
1175
1176 assert_matches!(
1178 updates_stream.recv().await.unwrap(),
1179 TimelineVectorDiffs { diffs, .. } => {
1180 assert_eq!(diffs.len(), 2, "{diffs:#?}");
1181 assert_matches!(&diffs[0], VectorDiff::Clear);
1182 assert_matches!(
1183 &diffs[1],
1184 VectorDiff::Append { values: events } => {
1185 assert_eq!(events.len(), 1);
1186 assert_eq!(events[0].event_id(), Some(thread_event_id_1));
1187 }
1188 );
1189 }
1190 );
1191
1192 thread_event_cache.pagination().run_backwards_once(1).await.unwrap();
1194
1195 assert_matches!(
1198 updates_stream.recv().await.unwrap(),
1199 TimelineVectorDiffs { diffs, .. } => {
1200 assert_eq!(diffs.len(), 1, "{diffs:#?}");
1201 assert_matches!(
1202 &diffs[0],
1203 VectorDiff::Insert { index: 0, value: event } => {
1204 assert_eq!(event.event_id(), Some(thread_event_id_0));
1205 }
1206 );
1207 }
1208 );
1209 }
1210 }
1211 }
1212
1213 #[async_test]
1214 async fn test_auto_shrink_after_all_subscribers_are_gone() {
1215 let room_id = room_id!("!r0");
1216 let thread_id = event_id!("$t0");
1217
1218 let client = MockClientBuilder::new(None).build().await;
1219
1220 let f = EventFactory::new().room(room_id).sender(*ALICE);
1221
1222 let event_id_0 = event_id!("$ev0");
1223 let event_id_1 = event_id!("$ev1");
1224
1225 let thread_root =
1226 f.text_msg("gr00t").event_id(thread_id).in_thread(thread_id, thread_id).into_event();
1227 let event_0 =
1228 f.text_msg("hello").event_id(event_id_0).in_thread(thread_id, event_id_0).into_event();
1229 let event_1 =
1230 f.text_msg("world").event_id(event_id_1).in_thread(thread_id, event_id_1).into_event();
1231
1232 {
1234 client
1235 .event_cache_store()
1236 .lock()
1237 .await
1238 .expect("Could not acquire the event cache lock")
1239 .as_clean()
1240 .expect("Could not acquire a clean event cache lock")
1241 .handle_linked_chunk_updates(
1242 LinkedChunkId::Thread(room_id, thread_id),
1243 vec![
1244 Update::NewItemsChunk {
1245 previous: None,
1246 new: ChunkIdentifier::new(0),
1247 next: None,
1248 },
1249 Update::PushItems {
1250 at: Position::new(ChunkIdentifier::new(0), 0),
1251 items: vec![thread_root, event_0],
1252 },
1253 Update::NewItemsChunk {
1254 previous: Some(ChunkIdentifier::new(0)),
1255 new: ChunkIdentifier::new(1),
1256 next: None,
1257 },
1258 Update::PushItems {
1259 at: Position::new(ChunkIdentifier::new(1), 0),
1260 items: vec![event_1],
1261 },
1262 ],
1263 )
1264 .await
1265 .unwrap();
1266 }
1267
1268 let event_cache = client.event_cache();
1269 event_cache.subscribe().unwrap();
1270
1271 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1272
1273 let (thread_event_cache, _drop_handles) =
1274 event_cache.thread(room_id, thread_id).await.unwrap();
1275
1276 let (events1, mut stream1) = thread_event_cache.subscribe().await.unwrap();
1278 assert_eq!(events1.len(), 1);
1279 assert_eq!(events1[0].event_id(), Some(event_id_1));
1280 assert!(stream1.is_empty());
1281
1282 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1283
1284 let outcome = thread_event_cache.pagination().run_backwards_once(20).await.unwrap();
1286 assert_eq!(outcome.events.len(), 2);
1287 assert_eq!(outcome.events[0].event_id(), Some(event_id_0));
1288 assert_eq!(outcome.events[1].event_id(), Some(thread_id));
1289 assert!(outcome.reached_start);
1290
1291 assert_let_timeout!(Ok(TimelineVectorDiffs { diffs, .. }) = stream1.recv());
1294 assert_eq!(diffs.len(), 2);
1295 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1296 assert_eq!(value.event_id(), Some(thread_id));
1297 });
1298 assert_matches!(&diffs[1], VectorDiff::Insert { index: 1, value } => {
1299 assert_eq!(value.event_id(), Some(event_id_0));
1300 });
1301
1302 assert!(stream1.is_empty());
1303
1304 assert_let_timeout!(
1305 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1306 );
1307 assert_eq!(expected_room_id, room_id);
1308 assert!(generic_stream.is_empty());
1309
1310 let (events2, stream2) = thread_event_cache.subscribe().await.unwrap();
1314 assert_eq!(events2.len(), 3);
1315 assert_eq!(events2[0].event_id(), Some(thread_id));
1316 assert_eq!(events2[1].event_id(), Some(event_id_0));
1317 assert_eq!(events2[2].event_id(), Some(event_id_1));
1318 assert!(stream2.is_empty());
1319
1320 let subscriber = {
1322 let state = thread_event_cache.inner.state.read().await.unwrap();
1323 state.update_sender.new_thread_receiver()
1324 };
1325
1326 drop(stream1);
1328 yield_now().await;
1329
1330 assert!(stream2.is_empty());
1332
1333 drop(stream2);
1335 yield_now().await;
1336
1337 {
1340 let state = thread_event_cache.inner.state.read().await.unwrap();
1342 assert_eq!(state.subscribers_handle().count(), 0);
1343
1344 assert!(subscriber.is_empty());
1346 assert!(generic_stream.is_empty());
1347 }
1348
1349 let events3 = thread_event_cache
1351 .inner
1352 .state
1353 .read()
1354 .await
1355 .unwrap()
1356 .thread_linked_chunk()
1357 .events()
1358 .map(|(_position, item)| item.clone())
1359 .collect::<Vec<_>>();
1360 assert_eq!(events3.len(), 1);
1361 assert_eq!(events3[0].event_id(), Some(event_id_1));
1362 }
1363}