1pub mod pagination;
18mod state;
19mod updates;
20
21use std::{fmt, sync::Arc};
22
23use eyeball::AsyncLock;
24use matrix_sdk_base::{
25 event_cache::{Event, thread::ThreadInfo},
26 read_receipts::ReadReceipts,
27 sync::Timeline,
28};
29use ruma::{
30 EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, events::relation::RelationType,
31 room_version_rules::RoomVersionRules,
32};
33use tokio::sync::{Notify, broadcast::Sender, mpsc};
34use tracing::{instrument, trace};
35
36pub(in super::super) use self::state::ThreadEventCacheState;
37pub(super) use self::updates::ThreadEventCacheUpdateSender;
38pub use self::{pagination::ThreadPagination, updates::ThreadEventCacheUpdate};
39#[cfg(feature = "e2e-encryption")]
40use super::super::redecryptor::MaybeResolvedEvent;
41use super::{
42 super::{
43 Result,
44 states::{CacheStateLock, StateLock, selectors::ThreadStateSelector},
45 },
46 EventsOrigin, TimelineVectorDiffs,
47 read_receipts::MaybeReceiptEventContent,
48 room::{RoomEventCacheGenericUpdate, RoomEventCacheLinkedChunkUpdate},
49 subscriber::{AutoShrinkMessage, Subscriber},
50};
51use crate::room::WeakRoom;
52
53#[derive(Clone)]
57pub struct ThreadEventCache {
58 inner: Arc<ThreadEventCacheInner>,
59}
60
61struct ThreadEventCacheInner {
63 room_id: OwnedRoomId,
65
66 thread_id: OwnedEventId,
68
69 weak_room: WeakRoom,
71
72 state: CacheStateLock<ThreadStateSelector>,
74
75 pagination_batch_token_notifier: Notify,
77
78 auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
83
84 update_sender: ThreadEventCacheUpdateSender,
86}
87
88impl fmt::Debug for ThreadEventCache {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 f.debug_struct("ThreadEventCache").finish_non_exhaustive()
91 }
92}
93
94impl ThreadEventCache {
95 #[allow(clippy::too_many_arguments)]
97 pub(super) async fn new(
98 room_id: OwnedRoomId,
99 thread_id: OwnedEventId,
100 own_user_id: OwnedUserId,
101 room_version_rules: RoomVersionRules,
102 weak_room: WeakRoom,
103 state: &StateLock,
104 auto_shrink_sender: mpsc::Sender<AutoShrinkMessage>,
105 generic_update_sender: Sender<RoomEventCacheGenericUpdate>,
106 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
107 ) -> Result<Self> {
108 let update_sender = ThreadEventCacheUpdateSender::new(generic_update_sender.clone());
109
110 let cache_state = state
111 .try_insert_once_with(
112 ThreadStateSelector::new(room_id.clone(), thread_id.clone()),
113 |store_guard| {
114 ThreadEventCacheState::new(
115 room_id.clone(),
116 thread_id.clone(),
117 weak_room.clone(),
118 own_user_id,
119 room_version_rules,
120 store_guard,
121 update_sender.clone(),
122 linked_chunk_update_sender,
123 )
124 },
125 )
126 .await?;
127
128 let timeline_is_not_empty =
129 cache_state.read().await?.thread_linked_chunk().revents().next().is_some();
130
131 let cache = Self {
132 inner: Arc::new(ThreadEventCacheInner {
133 room_id: room_id.clone(),
134 thread_id,
135 weak_room,
136 state: cache_state,
137 pagination_batch_token_notifier: Notify::new(),
138 auto_shrink_sender,
139 update_sender,
140 }),
141 };
142
143 if timeline_is_not_empty {
146 let _ = generic_update_sender
147 .send(RoomEventCacheGenericUpdate { room_id: room_id.to_owned() });
148 }
149
150 Ok(cache)
151 }
152
153 pub fn room_id(&self) -> &RoomId {
155 &self.inner.room_id
156 }
157
158 pub fn thread_id(&self) -> &EventId {
160 &self.inner.thread_id
161 }
162
163 pub async fn num_unread_messages(&self) -> Result<u64> {
168 Ok(self.inner.state.read().await?.thread_info.read().await.read_receipts.num_unread)
169 }
170
171 pub async fn num_unread_notifications(&self) -> Result<u64> {
176 Ok(self.inner.state.read().await?.thread_info.read().await.read_receipts.num_notifications)
177 }
178
179 pub async fn num_unread_mentions(&self) -> Result<u64> {
185 Ok(self.inner.state.read().await?.thread_info.read().await.read_receipts.num_mentions)
186 }
187
188 pub async fn read_receipts(&self) -> Result<ReadReceipts> {
190 Ok(self.inner.state.read().await?.thread_info.read().await.read_receipts.clone())
191 }
192
193 pub async fn subscribe_to_thread_info(
195 &self,
196 ) -> Result<eyeball::Subscriber<ThreadInfo, AsyncLock>> {
197 Ok(self.inner.state.read().await?.thread_info.subscribe().await)
198 }
199
200 pub async fn subscribe(&self) -> Result<(Vec<Event>, Subscriber<ThreadEventCacheUpdate>)> {
206 let state = self.inner.state.read().await?;
207 let events =
208 state.thread_linked_chunk().events().map(|(_position, item)| item.clone()).collect();
209
210 let subscribers_handle = state.subscribers_handle();
211
212 let subscriber = Subscriber::new(
213 self.inner.update_sender.new_thread_receiver(),
214 AutoShrinkMessage::Thread {
215 room_id: self.inner.room_id.clone(),
216 thread_id: self.inner.thread_id.clone(),
217 },
218 self.inner.auto_shrink_sender.clone(),
219 subscribers_handle,
220 );
221
222 trace!("added a thread event cache subscriber; new count: {}", subscribers_handle.count());
223
224 Ok((events, subscriber))
225 }
226
227 pub fn pagination(&self) -> ThreadPagination {
230 ThreadPagination::new(self.inner.clone())
231 }
232
233 pub(in super::super) fn state(&self) -> &CacheStateLock<ThreadStateSelector> {
235 &self.inner.state
236 }
237
238 #[instrument(skip_all, fields(room_id = %self.inner.room_id, thread_root = %self.inner.thread_id))]
240 pub(super) async fn handle_joined_room_update(
241 &self,
242 timeline: Timeline,
243 read_receipts: MaybeReceiptEventContent,
244 ) -> Result<()> {
245 self.handle_timeline(timeline, read_receipts).await
246 }
247
248 #[instrument(skip_all, fields(room_id = %self.inner.room_id, thread_root = %self.inner.thread_id))]
250 pub(super) async fn handle_left_room_update(&self, timeline: Timeline) -> Result<()> {
251 self.handle_timeline(timeline, MaybeReceiptEventContent::none()).await
252 }
253
254 async fn handle_timeline(
257 &self,
258 timeline: Timeline,
259 read_receipts: MaybeReceiptEventContent,
260 ) -> Result<()> {
261 if timeline.events.is_empty() && timeline.prev_batch.is_none() && read_receipts.is_none() {
262 return Ok(());
263 }
264
265 trace!("adding new events");
266
267 let mut state = self.inner.state.write().await?;
268
269 let (stored_prev_batch_token, timeline_event_diffs) =
270 state.handle_sync(timeline, &read_receipts).await?;
271
272 if stored_prev_batch_token {
275 self.inner.pagination_batch_token_notifier.notify_one();
276 }
277
278 if !timeline_event_diffs.is_empty() {
279 state.update_sender.send(
280 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
281 diffs: timeline_event_diffs,
282 origin: EventsOrigin::Sync,
283 }),
284 None,
287 );
288 }
289
290 if let Some(read_receipts) = read_receipts.into_inner() {
291 state
292 .update_sender
293 .send(ThreadEventCacheUpdate::AddReadReceiptEvent { event: read_receipts }, None);
294 }
295
296 Ok(())
297 }
298
299 #[cfg(test)]
304 async fn find_event(
305 &self,
306 event_id: &EventId,
307 ) -> Result<Option<(super::EventLocation, Event)>> {
308 self.inner.state.read().await?.find_event(event_id).await
309 }
310
311 pub async fn find_event_with_relations(
324 &self,
325 event_id: &EventId,
326 filter: Option<Vec<RelationType>>,
327 ) -> Result<Option<(Event, Vec<Event>)>> {
328 Ok(self
330 .inner
331 .state
332 .read()
333 .await?
334 .find_event_with_relations(event_id, filter)
335 .await
336 .ok()
337 .flatten())
338 }
339
340 #[cfg(feature = "e2e-encryption")]
346 pub(in super::super) async fn replace_in_memory_utds(
347 &self,
348 resolved_events: &[MaybeResolvedEvent],
349 ) -> Result<bool> {
350 let mut state = self.inner.state.write().await?;
351 let timeline_event_diffs = state.replace_in_memory_utds(resolved_events)?;
352
353 let _ = state.thread_linked_chunk_mut().store_updates().take();
356
357 state
358 .post_process_upserted_events(
359 resolved_events.iter().filter_map(|resolved_event| resolved_event.as_resolved()),
360 None,
368 )
369 .await?;
370
371 let timeline_event_diffs = timeline_event_diffs
372 .into_iter()
373 .flatten()
374 .chain(state.thread_linked_chunk_mut().updates_as_vector_diffs())
375 .collect::<Vec<_>>();
376
377 Ok(if !timeline_event_diffs.is_empty() {
378 state.update_sender.send(
379 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
380 diffs: timeline_event_diffs,
381 origin: EventsOrigin::Cache,
382 }),
383 Some(RoomEventCacheGenericUpdate { room_id: self.inner.room_id.clone() }),
384 );
385
386 true
387 } else {
388 false
389 })
390 }
391}
392
393#[cfg(all(test, not(target_family = "wasm")))] mod timed_tests {
395 use std::sync::Arc;
396
397 use assert_matches::assert_matches;
398 use assert_matches2::assert_let;
399 use eyeball_im::VectorDiff;
400 use futures_util::FutureExt as _;
401 use matrix_sdk_base::{
402 RoomState, ThreadingSupport,
403 cross_process_lock::CrossProcessLockConfig,
404 event_cache::{
405 Gap,
406 store::{EventCacheStore as _, MemoryStore},
407 },
408 linked_chunk::{
409 ChunkContent, ChunkIdentifier, LinkedChunkId, Position, Update,
410 lazy_loader::from_all_chunks,
411 },
412 store::StoreConfig,
413 sync::Timeline,
414 };
415 use matrix_sdk_test::{ALICE, async_test, event_factory::EventFactory};
416 use ruma::{
417 event_id,
418 events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent},
419 room_id, user_id,
420 };
421 use tokio::task::yield_now;
422
423 use super::{
424 super::{super::RoomEventCacheGenericUpdate, TimelineVectorDiffs},
425 MaybeReceiptEventContent, ThreadEventCacheUpdate,
426 };
427 use crate::{assert_let_timeout, test_utils::client::MockClientBuilder};
428
429 #[async_test]
430 async fn test_write_to_storage() {
431 let room_id = room_id!("!r0");
432 let thread_root = event_id!("$t0_ev0");
433 let thread_event_id_0 = event_id!("$t0_ev1");
434
435 let f = EventFactory::new().room(room_id).sender(user_id!("@mnt_io:matrix.org"));
436
437 let event_cache_store = Arc::new(MemoryStore::new());
438
439 let client = MockClientBuilder::new(None)
440 .on_builder(|builder| {
441 builder
442 .store_config(
443 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
444 .event_cache_store(event_cache_store.clone()),
445 )
446 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
447 })
448 .build()
449 .await;
450
451 let event_cache = client.event_cache();
452 event_cache.subscribe().unwrap();
453
454 client.base_client().get_or_create_room(room_id, RoomState::Joined);
455
456 let (thread_event_cache, _drop_handles) =
457 event_cache.thread(room_id, thread_root).await.unwrap();
458 let (thread_events, mut thread_stream) = thread_event_cache.subscribe().await.unwrap();
459
460 assert!(thread_events.is_empty());
461
462 let timeline = Timeline {
464 limited: true,
465 prev_batch: Some("raclette".to_owned()),
466 events: vec![
467 f.text_msg("salut")
468 .event_id(thread_event_id_0)
469 .in_thread(thread_root, thread_root)
470 .into_event(),
471 ],
472 };
473
474 thread_event_cache
475 .handle_joined_room_update(timeline, MaybeReceiptEventContent::none())
476 .await
477 .unwrap();
478
479 assert_matches!(
480 thread_stream.recv().await,
481 Ok(ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) => {
482 assert_eq!(diffs.len(), 2);
483 assert_matches!(&diffs[0], VectorDiff::Clear);
484 assert_matches!(&diffs[1], VectorDiff::Append { values: events } => {
485 assert_eq!(events.len(), 1);
486 assert_eq!(events[0].event_id(), Some(thread_event_id_0));
487 });
488 }
489 );
490 assert!(thread_stream.is_empty());
491
492 let linked_chunk = from_all_chunks::<3, _, _>(
494 event_cache_store
495 .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root))
496 .await
497 .unwrap(),
498 )
499 .unwrap()
500 .unwrap();
501
502 assert_eq!(linked_chunk.chunks().count(), 2);
503
504 let mut chunks = linked_chunk.chunks();
505
506 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Gap(gap) => {
508 assert_eq!(gap.token, "raclette");
509 });
510
511 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
513 assert_eq!(events.len(), 1);
514 assert_eq!(events[0].event_id(), Some(thread_event_id_0));
515 });
516
517 assert!(chunks.next().is_none());
519 }
520
521 #[async_test]
522 async fn test_write_to_storage_strips_bundled_relations() {
523 let sender = user_id!("@mnt_io:matrix.org");
524 let room_id = room_id!("!r0");
525 let thread_root = event_id!("$t0_ev0");
526 let thread_event_id_0 = event_id!("$t0_ev1");
527
528 let f = EventFactory::new().room(room_id).sender(sender);
529
530 let event_cache_store = Arc::new(MemoryStore::new());
531
532 let client = MockClientBuilder::new(None)
533 .on_builder(|builder| {
534 builder
535 .store_config(
536 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
537 .event_cache_store(event_cache_store.clone()),
538 )
539 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
540 })
541 .build()
542 .await;
543
544 let event_cache = client.event_cache();
545 event_cache.subscribe().unwrap();
546
547 client.base_client().get_or_create_room(room_id, RoomState::Joined);
548
549 let (thread_event_cache, _drop_handles) =
550 event_cache.thread(room_id, thread_root).await.unwrap();
551
552 let timeline = Timeline {
554 limited: false,
555 prev_batch: None,
556 events: vec![
557 f.text_msg("s 'up")
558 .event_id(thread_event_id_0)
559 .with_bundled_edit(f.text_msg("Hello, Kind Sir").sender(sender))
560 .in_thread(thread_root, thread_root)
561 .into_event(),
562 ],
563 };
564
565 thread_event_cache
566 .handle_joined_room_update(timeline, MaybeReceiptEventContent::none())
567 .await
568 .unwrap();
569
570 {
572 let (events, _) = thread_event_cache.subscribe().await.unwrap();
573
574 assert_eq!(events.len(), 1);
575
576 let event = events[0].raw().deserialize().unwrap();
577 assert_eq!(event.event_id(), thread_event_id_0);
578 assert_let!(
579 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) =
580 event
581 );
582 assert!(msg.as_original().unwrap().unsigned.relations.replace.is_some());
583 }
584
585 let linked_chunk = from_all_chunks::<3, _, _>(
587 event_cache_store
588 .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root))
589 .await
590 .unwrap(),
591 )
592 .unwrap()
593 .unwrap();
594
595 assert_eq!(linked_chunk.chunks().count(), 1);
596
597 let mut chunks = linked_chunk.chunks();
598 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
599 assert_eq!(events.len(), 1);
600
601 let event = events[0].raw().deserialize().unwrap();
602 assert_eq!(event.event_id(), thread_event_id_0);
603
604 assert_let!(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = event);
605 assert!(msg.as_original().unwrap().unsigned.relations.replace.is_none());
606 });
607
608 assert!(chunks.next().is_none());
610 }
611
612 #[async_test]
613 async fn test_clear() {
614 let room_id = room_id!("!r0");
615 let f = EventFactory::new().room(room_id).sender(user_id!("@mnt_io:matrix.org"));
616
617 let event_cache_store = Arc::new(MemoryStore::new());
618
619 let thread_root = event_id!("$t0_ev0");
620 let thread_event_id_0 = event_id!("$t0_ev1");
621 let thread_event_id_1 = event_id!("$t0_ev2");
622
623 let thread_event_0 = f
624 .text_msg("foo")
625 .event_id(thread_event_id_0)
626 .in_thread(thread_root, thread_root)
627 .into_event();
628 let thread_event_1 = f
629 .text_msg("bar")
630 .event_id(thread_event_id_1)
631 .in_thread(thread_root, thread_event_id_0)
632 .into_event();
633
634 event_cache_store
636 .handle_linked_chunk_updates(
637 LinkedChunkId::Thread(room_id, thread_root),
638 vec![
639 Update::NewItemsChunk {
641 previous: None,
642 new: ChunkIdentifier::new(0),
643 next: None,
644 },
645 Update::NewGapChunk {
647 previous: Some(ChunkIdentifier::new(0)),
648 new: ChunkIdentifier::new(42),
650 next: None,
651 gap: Gap { token: "comté".to_owned() },
652 },
653 Update::NewItemsChunk {
655 previous: Some(ChunkIdentifier::new(42)),
656 new: ChunkIdentifier::new(1),
657 next: None,
658 },
659 Update::PushItems {
660 at: Position::new(ChunkIdentifier::new(1), 0),
661 items: vec![thread_event_0.clone()],
662 },
663 Update::NewItemsChunk {
665 previous: Some(ChunkIdentifier::new(1)),
666 new: ChunkIdentifier::new(2),
667 next: None,
668 },
669 Update::PushItems {
670 at: Position::new(ChunkIdentifier::new(2), 0),
671 items: vec![thread_event_1.clone()],
672 },
673 ],
674 )
675 .await
676 .unwrap();
677
678 let client = MockClientBuilder::new(None)
679 .on_builder(|builder| {
680 builder
681 .store_config(
682 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
683 .event_cache_store(event_cache_store.clone()),
684 )
685 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
686 })
687 .build()
688 .await;
689
690 let event_cache = client.event_cache();
691 event_cache.subscribe().unwrap();
692
693 client.base_client().get_or_create_room(room_id, RoomState::Joined);
694
695 let (thread_event_cache, _drop_handles) =
696 event_cache.thread(room_id, thread_root).await.unwrap();
697 let (thread_events, mut thread_stream) = thread_event_cache.subscribe().await.unwrap();
698
699 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
700
701 {
703 assert!(thread_event_cache.find_event(thread_event_id_0).await.unwrap().is_some());
704 assert!(thread_event_cache.find_event(thread_event_id_1).await.unwrap().is_some());
705 }
706
707 {
709 assert_eq!(thread_events.len(), 1);
712 assert_eq!(thread_events[0].event_id().unwrap(), thread_event_id_1);
713
714 assert!(thread_stream.is_empty());
715 }
716
717 {
719 thread_event_cache.pagination().run_backwards_once(20).await.unwrap();
720
721 assert_matches!(
722 thread_stream.recv().await,
723 Ok(ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) => {
724 assert_eq!(diffs.len(), 1);
725 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
726 assert_eq!(event.event_id(), Some(thread_event_id_0));
728 });
729 }
730 );
731 assert!(thread_stream.is_empty());
732
733 assert_matches!(
734 generic_stream.recv().await,
735 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
736 assert_eq!(room_id, expected_room_id);
737 }
738 );
739 assert!(generic_stream.is_empty());
740 }
741
742 event_cache.clear_all_rooms().await.unwrap();
744
745 assert_matches!(
747 thread_stream.recv().await,
748 Ok(ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) => {
749 assert_eq!(diffs.len(), 1);
750 assert_matches!(&diffs[0], VectorDiff::Clear);
751 }
752 );
753
754 assert_matches!(
757 generic_stream.recv().await,
758 Ok(RoomEventCacheGenericUpdate { room_id: received_room_id }) => {
759 assert_eq!(received_room_id, room_id);
760 }
761 );
762 assert_matches!(
764 generic_stream.recv().await,
765 Ok(RoomEventCacheGenericUpdate { room_id: received_room_id }) => {
766 assert_eq!(received_room_id, room_id);
767 }
768 );
769 assert!(generic_stream.is_empty());
770
771 assert!(thread_event_cache.find_event(thread_event_id_0).await.unwrap().is_none());
774 assert!(thread_event_cache.find_event(thread_event_id_1).await.unwrap().is_none());
775
776 let (thread_events, _) = thread_event_cache.subscribe().await.unwrap();
778 assert!(thread_events.is_empty());
779
780 assert!(
782 event_cache_store
783 .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root))
784 .await
785 .unwrap()
786 .is_empty()
787 );
788 }
789
790 #[async_test]
791 async fn test_load_from_storage() {
792 let room_id = room_id!("!r0");
793 let f = EventFactory::new().room(room_id).sender(user_id!("@mnt_io:matrix.org"));
794
795 let event_cache_store = Arc::new(MemoryStore::new());
796
797 let thread_root = event_id!("$t0");
798 let thread_event_id_0 = event_id!("$t0_ev0");
799 let thread_event_id_1 = event_id!("$t0_ev1");
800
801 let thread_event_0 = f
802 .text_msg("hello world")
803 .event_id(thread_event_id_0)
804 .in_thread(thread_root, thread_root)
805 .into_event();
806 let thread_event_1 = f
807 .text_msg("how's it going")
808 .event_id(thread_event_id_1)
809 .in_thread(thread_root, thread_event_id_1)
810 .into_event();
811
812 let updates = vec![
816 Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(0), next: None },
818 Update::NewGapChunk {
820 previous: Some(ChunkIdentifier::new(0)),
821 new: ChunkIdentifier::new(42),
823 next: None,
824 gap: Gap { token: "gruyère".to_owned() },
825 },
826 Update::NewItemsChunk {
828 previous: Some(ChunkIdentifier::new(42)),
829 new: ChunkIdentifier::new(1),
830 next: None,
831 },
832 Update::PushItems {
833 at: Position::new(ChunkIdentifier::new(1), 0),
834 items: vec![thread_event_0.clone()],
835 },
836 Update::NewItemsChunk {
838 previous: Some(ChunkIdentifier::new(1)),
839 new: ChunkIdentifier::new(2),
840 next: None,
841 },
842 Update::PushItems {
843 at: Position::new(ChunkIdentifier::new(2), 0),
844 items: vec![thread_event_1.clone()],
845 },
846 ];
847 event_cache_store
848 .handle_linked_chunk_updates(LinkedChunkId::Room(room_id), updates.clone())
849 .await
850 .unwrap();
851 event_cache_store
852 .handle_linked_chunk_updates(LinkedChunkId::Thread(room_id, thread_root), updates)
853 .await
854 .unwrap();
855
856 let client = MockClientBuilder::new(None)
857 .on_builder(|builder| {
858 builder
859 .store_config(
860 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
861 .event_cache_store(event_cache_store.clone()),
862 )
863 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
864 })
865 .build()
866 .await;
867
868 let event_cache = client.event_cache();
869 event_cache.subscribe().unwrap();
870
871 client.base_client().get_or_create_room(room_id, RoomState::Joined);
872
873 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
875 let (thread_event_cache, _drop_handles) =
876 event_cache.thread(room_id, thread_root).await.unwrap();
877 let (thread_events, mut thread_stream) = thread_event_cache.subscribe().await.unwrap();
878
879 for _ in 0..2 {
882 assert_matches!(
883 generic_stream.recv().await,
884 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
885 assert_eq!(room_id, expected_room_id);
886 }
887 );
888 }
889 assert!(generic_stream.is_empty());
890
891 assert_eq!(thread_events.len(), 1);
894 assert_eq!(thread_events[0].event_id().unwrap(), thread_event_id_1);
895 assert!(thread_stream.is_empty());
896
897 assert!(thread_event_cache.find_event(thread_event_id_0).await.unwrap().is_some());
900 assert!(thread_event_cache.find_event(thread_event_id_1).await.unwrap().is_some());
901
902 thread_event_cache.pagination().run_backwards_once(20).await.unwrap();
904
905 assert_matches!(
906 thread_stream.recv().await,
907 Ok(ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) => {
908 assert_eq!(diffs.len(), 1);
909 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
910 assert_eq!(event.event_id(), Some(thread_event_id_0));
911 });
912 }
913 );
914 assert!(thread_stream.is_empty());
915
916 assert_matches!(
918 generic_stream.recv().await,
919 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
920 assert_eq!(expected_room_id, room_id);
921 }
922 );
923 assert!(generic_stream.is_empty());
924
925 let timeline = Timeline { limited: false, prev_batch: None, events: vec![thread_event_1] };
927
928 thread_event_cache
929 .handle_joined_room_update(timeline, MaybeReceiptEventContent::none())
930 .await
931 .unwrap();
932
933 assert!(generic_stream.recv().now_or_never().is_none());
936
937 let (thread_events, _) = thread_event_cache.subscribe().await.unwrap();
942 assert_eq!(thread_events.len(), 2);
943 assert_eq!(thread_events[0].event_id(), Some(thread_event_id_0));
944 assert_eq!(thread_events[1].event_id(), Some(thread_event_id_1));
945 }
946
947 #[async_test]
948 async fn test_load_from_storage_resilient_to_failure() {
949 let room_id = room_id!("!r0");
950 let f = EventFactory::new().room(room_id).sender(user_id!("@mnt_io:matrix.org"));
951
952 let event_cache_store = Arc::new(MemoryStore::new());
953
954 let thread_root = event_id!("$t0");
955 let thread_event_id_0 = event_id!("$t0_ev0");
956
957 let thread_event_0 = f
958 .text_msg("hello world")
959 .event_id(thread_event_id_0)
960 .in_thread(thread_root, thread_root)
961 .into_event();
962
963 event_cache_store
965 .handle_linked_chunk_updates(
966 LinkedChunkId::Thread(room_id, thread_root),
967 vec![
968 Update::NewItemsChunk {
969 previous: None,
970 new: ChunkIdentifier::new(0),
971 next: None,
972 },
973 Update::PushItems {
974 at: Position::new(ChunkIdentifier::new(0), 0),
975 items: vec![thread_event_0],
976 },
977 Update::NewItemsChunk {
978 previous: Some(ChunkIdentifier::new(0)),
979 new: ChunkIdentifier::new(1),
980 next: Some(ChunkIdentifier::new(0)),
981 },
982 ],
983 )
984 .await
985 .unwrap();
986
987 let client = MockClientBuilder::new(None)
988 .on_builder(|builder| {
989 builder
990 .store_config(
991 StoreConfig::new(CrossProcessLockConfig::multi_process("holder"))
992 .event_cache_store(event_cache_store.clone()),
993 )
994 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
995 })
996 .build()
997 .await;
998
999 let event_cache = client.event_cache();
1000 event_cache.subscribe().unwrap();
1001
1002 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1003
1004 let (thread_event_cache, _drop_handles) =
1005 event_cache.thread(room_id, thread_root).await.unwrap();
1006 let (thread_events, _) = thread_event_cache.subscribe().await.unwrap();
1007
1008 assert!(thread_events.is_empty());
1011
1012 let raw_chunks = event_cache_store
1015 .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root))
1016 .await
1017 .unwrap();
1018 assert!(raw_chunks.is_empty());
1019 }
1020
1021 #[async_test]
1022 async fn test_reload_when_dirty() {
1023 let user_id = user_id!("@mnt_io:matrix.org");
1024 let room_id = room_id!("!raclette:patate.ch");
1025
1026 let event_cache_store = MemoryStore::new();
1028
1029 let client_p0 = MockClientBuilder::new(None)
1031 .on_builder(|builder| {
1032 builder
1033 .store_config(
1034 StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
1035 .event_cache_store(event_cache_store.clone()),
1036 )
1037 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
1038 })
1039 .build()
1040 .await;
1041
1042 let client_p1 = MockClientBuilder::new(None)
1044 .on_builder(|builder| {
1045 builder
1046 .store_config(
1047 StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
1048 .event_cache_store(event_cache_store),
1049 )
1050 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
1051 })
1052 .build()
1053 .await;
1054
1055 let event_factory = EventFactory::new().room(room_id).sender(user_id);
1056
1057 let thread_root = event_id!("$t0");
1058 let thread_event_id_0 = event_id!("$t0_ev0");
1059 let thread_event_id_1 = event_id!("$t0_ev1");
1060
1061 let thread_event_0 = event_factory
1062 .text_msg("comté")
1063 .event_id(thread_event_id_0)
1064 .in_thread(thread_root, thread_root)
1065 .into_event();
1066 let thread_event_1 = event_factory
1067 .text_msg("morbier")
1068 .event_id(thread_event_id_1)
1069 .in_thread(thread_root, thread_event_id_0)
1070 .into_event();
1071
1072 client_p0
1074 .event_cache_store()
1075 .lock()
1076 .await
1077 .expect("[p0] Could not acquire the event cache lock")
1078 .as_clean()
1079 .expect("[p0] Could not acquire a clean event cache lock")
1080 .handle_linked_chunk_updates(
1081 LinkedChunkId::Thread(room_id, thread_root),
1082 vec![
1083 Update::NewItemsChunk {
1084 previous: None,
1085 new: ChunkIdentifier::new(0),
1086 next: None,
1087 },
1088 Update::PushItems {
1089 at: Position::new(ChunkIdentifier::new(0), 0),
1090 items: vec![thread_event_0],
1091 },
1092 Update::NewItemsChunk {
1093 previous: Some(ChunkIdentifier::new(0)),
1094 new: ChunkIdentifier::new(1),
1095 next: None,
1096 },
1097 Update::PushItems {
1098 at: Position::new(ChunkIdentifier::new(1), 0),
1099 items: vec![thread_event_1],
1100 },
1101 ],
1102 )
1103 .await
1104 .unwrap();
1105
1106 let (thread_event_cache_p0, thread_event_cache_p1) = {
1108 let event_cache_p0 = client_p0.event_cache();
1109 event_cache_p0.subscribe().unwrap();
1110
1111 let event_cache_p1 = client_p1.event_cache();
1112 event_cache_p1.subscribe().unwrap();
1113
1114 client_p0.base_client().get_or_create_room(room_id, RoomState::Joined);
1115 client_p1.base_client().get_or_create_room(room_id, RoomState::Joined);
1116
1117 let (thread_event_cache_p0, _drop_handles) =
1118 event_cache_p0.thread(room_id, thread_root).await.unwrap();
1119 let (thread_event_cache_p1, _drop_handles) =
1120 event_cache_p1.thread(room_id, thread_root).await.unwrap();
1121
1122 (thread_event_cache_p0, thread_event_cache_p1)
1123 };
1124
1125 let mut updates_stream_p0 = {
1130 let thread_event_cache = &thread_event_cache_p0;
1131
1132 let (initial_updates, mut updates_stream) =
1133 thread_event_cache_p0.subscribe().await.unwrap();
1134
1135 assert_eq!(initial_updates.len(), 1);
1137 assert_eq!(initial_updates[0].event_id(), Some(thread_event_id_1));
1138 assert!(updates_stream.is_empty());
1139
1140 thread_event_cache.pagination().run_backwards_once(1).await.unwrap();
1142
1143 assert_matches!(
1145 updates_stream.recv().await.unwrap(),
1146 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
1147 assert_eq!(diffs.len(), 1, "{diffs:#?}");
1148 assert_matches!(
1149 &diffs[0],
1150 VectorDiff::Insert { index: 0, value: event } => {
1151 assert_eq!(event.event_id(), Some(thread_event_id_0));
1152 }
1153 );
1154 }
1155 );
1156
1157 updates_stream
1158 };
1159
1160 let mut updates_stream_p1 = {
1162 let thread_event_cache = &thread_event_cache_p1;
1163 let (initial_updates, mut updates_stream) =
1164 thread_event_cache_p1.subscribe().await.unwrap();
1165
1166 assert_eq!(initial_updates.len(), 1);
1168 assert_eq!(initial_updates[0].event_id(), Some(thread_event_id_1));
1169 assert!(updates_stream.is_empty());
1170
1171 thread_event_cache.pagination().run_backwards_once(1).await.unwrap();
1173
1174 assert_matches!(
1176 updates_stream.recv().await.unwrap(),
1177 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
1178 assert_eq!(diffs.len(), 1, "{diffs:#?}");
1179 assert_matches!(
1180 &diffs[0],
1181 VectorDiff::Insert { index: 0, value: event } => {
1182 assert_eq!(event.event_id(), Some(thread_event_id_0));
1183 }
1184 );
1185 }
1186 );
1187
1188 updates_stream
1189 };
1190
1191 for _ in 0..3 {
1193 {
1197 let thread_event_cache = &thread_event_cache_p0;
1198 let updates_stream = &mut updates_stream_p0;
1199
1200 let (initial_updates, _) = thread_event_cache.subscribe().await.unwrap();
1204
1205 assert_eq!(initial_updates.len(), 1);
1206 assert_eq!(initial_updates[0].event_id(), Some(thread_event_id_1));
1207
1208 assert_matches!(
1210 updates_stream.recv().await.unwrap(),
1211 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
1212 assert_eq!(diffs.len(), 2, "{diffs:#?}");
1213 assert_matches!(&diffs[0], VectorDiff::Clear);
1214 assert_matches!(
1215 &diffs[1],
1216 VectorDiff::Append { values: events } => {
1217 assert_eq!(events.len(), 1);
1218 assert_eq!(events[0].event_id(), Some(thread_event_id_1));
1219 }
1220 );
1221 }
1222 );
1223
1224 thread_event_cache.pagination().run_backwards_once(1).await.unwrap();
1226
1227 assert_matches!(
1230 updates_stream.recv().await.unwrap(),
1231 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
1232 assert_eq!(diffs.len(), 1, "{diffs:#?}");
1233 assert_matches!(
1234 &diffs[0],
1235 VectorDiff::Insert { index: 0, value: event } => {
1236 assert_eq!(event.event_id(), Some(thread_event_id_0));
1237 }
1238 );
1239 }
1240 );
1241 }
1242
1243 {
1247 let thread_event_cache = &thread_event_cache_p1;
1248 let updates_stream = &mut updates_stream_p1;
1249
1250 let (initial_updates, _) = thread_event_cache.subscribe().await.unwrap();
1254
1255 assert_eq!(initial_updates.len(), 1);
1256 assert_eq!(initial_updates[0].event_id(), Some(thread_event_id_1));
1257
1258 assert_matches!(
1260 updates_stream.recv().await.unwrap(),
1261 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
1262 assert_eq!(diffs.len(), 2, "{diffs:#?}");
1263 assert_matches!(&diffs[0], VectorDiff::Clear);
1264 assert_matches!(
1265 &diffs[1],
1266 VectorDiff::Append { values: events } => {
1267 assert_eq!(events.len(), 1);
1268 assert_eq!(events[0].event_id(), Some(thread_event_id_1));
1269 }
1270 );
1271 }
1272 );
1273
1274 thread_event_cache.pagination().run_backwards_once(1).await.unwrap();
1276
1277 assert_matches!(
1280 updates_stream.recv().await.unwrap(),
1281 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
1282 assert_eq!(diffs.len(), 1, "{diffs:#?}");
1283 assert_matches!(
1284 &diffs[0],
1285 VectorDiff::Insert { index: 0, value: event } => {
1286 assert_eq!(event.event_id(), Some(thread_event_id_0));
1287 }
1288 );
1289 }
1290 );
1291 }
1292 }
1293 }
1294
1295 #[async_test]
1296 async fn test_auto_shrink_after_all_subscribers_are_gone() {
1297 let room_id = room_id!("!r0");
1298 let thread_id = event_id!("$t0");
1299
1300 let client = MockClientBuilder::new(None).build().await;
1301
1302 let f = EventFactory::new().room(room_id).sender(*ALICE);
1303
1304 let event_id_0 = event_id!("$ev0");
1305 let event_id_1 = event_id!("$ev1");
1306
1307 let thread_root =
1308 f.text_msg("gr00t").event_id(thread_id).in_thread(thread_id, thread_id).into_event();
1309 let event_0 =
1310 f.text_msg("hello").event_id(event_id_0).in_thread(thread_id, event_id_0).into_event();
1311 let event_1 =
1312 f.text_msg("world").event_id(event_id_1).in_thread(thread_id, event_id_1).into_event();
1313
1314 {
1316 client
1317 .event_cache_store()
1318 .lock()
1319 .await
1320 .expect("Could not acquire the event cache lock")
1321 .as_clean()
1322 .expect("Could not acquire a clean event cache lock")
1323 .handle_linked_chunk_updates(
1324 LinkedChunkId::Thread(room_id, thread_id),
1325 vec![
1326 Update::NewItemsChunk {
1327 previous: None,
1328 new: ChunkIdentifier::new(0),
1329 next: None,
1330 },
1331 Update::PushItems {
1332 at: Position::new(ChunkIdentifier::new(0), 0),
1333 items: vec![thread_root, event_0],
1334 },
1335 Update::NewItemsChunk {
1336 previous: Some(ChunkIdentifier::new(0)),
1337 new: ChunkIdentifier::new(1),
1338 next: None,
1339 },
1340 Update::PushItems {
1341 at: Position::new(ChunkIdentifier::new(1), 0),
1342 items: vec![event_1],
1343 },
1344 ],
1345 )
1346 .await
1347 .unwrap();
1348 }
1349
1350 let event_cache = client.event_cache();
1351 event_cache.subscribe().unwrap();
1352
1353 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1354
1355 let (thread_event_cache, _drop_handles) =
1356 event_cache.thread(room_id, thread_id).await.unwrap();
1357
1358 let (events1, mut stream1) = thread_event_cache.subscribe().await.unwrap();
1360 assert_eq!(events1.len(), 1);
1361 assert_eq!(events1[0].event_id(), Some(event_id_1));
1362 assert!(stream1.is_empty());
1363
1364 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1365
1366 let outcome = thread_event_cache.pagination().run_backwards_once(20).await.unwrap();
1368 assert_eq!(outcome.events.len(), 2);
1369 assert_eq!(outcome.events[0].event_id(), Some(event_id_0));
1370 assert_eq!(outcome.events[1].event_id(), Some(thread_id));
1371 assert!(outcome.reached_start);
1372
1373 assert_let_timeout!(
1376 Ok(ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1377 stream1.recv()
1378 );
1379 assert_eq!(diffs.len(), 2);
1380 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1381 assert_eq!(value.event_id(), Some(thread_id));
1382 });
1383 assert_matches!(&diffs[1], VectorDiff::Insert { index: 1, value } => {
1384 assert_eq!(value.event_id(), Some(event_id_0));
1385 });
1386
1387 assert!(stream1.is_empty());
1388
1389 assert_let_timeout!(
1390 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1391 );
1392 assert_eq!(expected_room_id, room_id);
1393 assert!(generic_stream.is_empty());
1394
1395 let (events2, stream2) = thread_event_cache.subscribe().await.unwrap();
1399 assert_eq!(events2.len(), 3);
1400 assert_eq!(events2[0].event_id(), Some(thread_id));
1401 assert_eq!(events2[1].event_id(), Some(event_id_0));
1402 assert_eq!(events2[2].event_id(), Some(event_id_1));
1403 assert!(stream2.is_empty());
1404
1405 let subscriber = {
1407 let state = thread_event_cache.inner.state.read().await.unwrap();
1408 state.update_sender.new_thread_receiver()
1409 };
1410
1411 drop(stream1);
1413 yield_now().await;
1414
1415 assert!(stream2.is_empty());
1417
1418 drop(stream2);
1420 yield_now().await;
1421
1422 {
1425 let state = thread_event_cache.inner.state.read().await.unwrap();
1427 assert_eq!(state.subscribers_handle().count(), 0);
1428
1429 assert!(subscriber.is_empty());
1431 assert!(generic_stream.is_empty());
1432 }
1433
1434 let events3 = thread_event_cache
1436 .inner
1437 .state
1438 .read()
1439 .await
1440 .unwrap()
1441 .thread_linked_chunk()
1442 .events()
1443 .map(|(_position, item)| item.clone())
1444 .collect::<Vec<_>>();
1445 assert_eq!(events3.len(), 1);
1446 assert_eq!(events3[0].event_id(), Some(event_id_1));
1447 }
1448}