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