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 cache_state = state
112 .try_insert_once_with(
113 ThreadStateSelector::new(room_id.clone(), thread_id.clone()),
114 |store_guard| {
115 ThreadEventCacheState::new(
116 room_id.clone(),
117 thread_id.clone(),
118 weak_room.clone(),
119 own_user_id,
120 room_version_rules,
121 store_guard,
122 update_sender.clone(),
123 linked_chunk_update_sender,
124 )
125 },
126 )
127 .await?;
128
129 let timeline_is_not_empty =
130 cache_state.read().await?.thread_linked_chunk().revents().next().is_some();
131
132 let cache = Self {
133 inner: Arc::new(ThreadEventCacheInner {
134 room_id: room_id.clone(),
135 thread_id,
136 weak_room,
137 state: cache_state,
138 pagination_batch_token_notifier: Notify::new(),
139 auto_shrink_sender,
140 update_sender,
141 }),
142 };
143
144 if timeline_is_not_empty {
147 let _ = generic_update_sender
148 .send(RoomEventCacheGenericUpdate { room_id: room_id.to_owned() });
149 }
150
151 Ok(cache)
152 }
153
154 pub fn room_id(&self) -> &RoomId {
156 &self.inner.room_id
157 }
158
159 pub fn thread_id(&self) -> &EventId {
161 &self.inner.thread_id
162 }
163
164 pub async fn num_unread_messages(&self) -> Result<u64> {
169 Ok(self.inner.state.read().await?.thread_info.read().await.read_receipts.num_unread)
170 }
171
172 pub async fn num_unread_notifications(&self) -> Result<u64> {
177 Ok(self.inner.state.read().await?.thread_info.read().await.read_receipts.num_notifications)
178 }
179
180 pub async fn num_unread_mentions(&self) -> Result<u64> {
186 Ok(self.inner.state.read().await?.thread_info.read().await.read_receipts.num_mentions)
187 }
188
189 pub async fn read_receipts(&self) -> Result<ReadReceipts> {
191 Ok(self.inner.state.read().await?.thread_info.read().await.read_receipts.clone())
192 }
193
194 pub async fn subscribe_to_thread_info(
196 &self,
197 ) -> Result<eyeball::Subscriber<ThreadInfo, AsyncLock>> {
198 Ok(self.inner.state.read().await?.thread_info.subscribe().await)
199 }
200
201 pub async fn subscribe(&self) -> Result<(Vec<Event>, Subscriber<ThreadEventCacheUpdate>)> {
207 let state = self.inner.state.read().await?;
208 let events =
209 state.thread_linked_chunk().events().map(|(_position, item)| item.clone()).collect();
210
211 let subscribers_handle = state.subscribers_handle();
212
213 let subscriber = Subscriber::new(
214 self.inner.update_sender.new_thread_receiver(),
215 AutoShrinkMessage::Thread {
216 room_id: self.inner.room_id.clone(),
217 thread_id: self.inner.thread_id.clone(),
218 },
219 self.inner.auto_shrink_sender.clone(),
220 subscribers_handle,
221 );
222
223 trace!("added a thread event cache subscriber; new count: {}", subscribers_handle.count());
224
225 Ok((events, subscriber))
226 }
227
228 pub fn pagination(&self) -> ThreadPagination {
231 ThreadPagination::new(self.inner.clone())
232 }
233
234 pub(in super::super) fn state(&self) -> &CacheStateLock<ThreadStateSelector> {
236 &self.inner.state
237 }
238
239 #[instrument(skip_all, fields(room_id = %self.inner.room_id, thread_root = %self.inner.thread_id))]
241 pub(super) async fn handle_joined_room_update(
242 &self,
243 timeline: Timeline,
244 read_receipts: MaybeReceiptEventContent,
245 ) -> Result<()> {
246 self.handle_timeline(timeline, read_receipts).await
247 }
248
249 #[instrument(skip_all, fields(room_id = %self.inner.room_id, thread_root = %self.inner.thread_id))]
251 pub(super) async fn handle_left_room_update(&self, timeline: Timeline) -> Result<()> {
252 self.handle_timeline(timeline, MaybeReceiptEventContent::none()).await
253 }
254
255 async fn handle_timeline(
258 &self,
259 timeline: Timeline,
260 read_receipts: MaybeReceiptEventContent,
261 ) -> Result<()> {
262 if timeline.events.is_empty() && timeline.prev_batch.is_none() && read_receipts.is_none() {
263 return Ok(());
264 }
265
266 trace!("adding new events");
267
268 let mut state = self.inner.state.write().await?;
269
270 let (stored_prev_batch_token, timeline_event_diffs) =
271 state.handle_sync(timeline, &read_receipts).await?;
272
273 if stored_prev_batch_token {
276 self.inner.pagination_batch_token_notifier.notify_one();
277 }
278
279 if !timeline_event_diffs.is_empty() {
280 state.update_sender.send(
281 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
282 diffs: timeline_event_diffs,
283 origin: EventsOrigin::Sync,
284 }),
285 None,
288 );
289 }
290
291 if let Some(read_receipts) = read_receipts.into_inner() {
292 state
293 .update_sender
294 .send(ThreadEventCacheUpdate::AddReadReceiptEvent { event: read_receipts }, None);
295 }
296
297 Ok(())
298 }
299
300 pub(in super::super) async fn update_thread_summary(&self) -> Result<ThreadSummary> {
302 let mut state = self.inner.state.write().await?;
303
304 let thread_summary = state.update_thread_summary().await?;
305
306 state
307 .update_sender
308 .send(ThreadEventCacheUpdate::UpdateSummary(thread_summary.clone()), None);
309
310 Ok(thread_summary)
311 }
312
313 #[cfg(test)]
318 async fn find_event(
319 &self,
320 event_id: &EventId,
321 ) -> Result<Option<(super::EventLocation, Event)>> {
322 self.inner.state.read().await?.find_event(event_id).await
323 }
324
325 pub async fn find_event_with_relations(
338 &self,
339 event_id: &EventId,
340 filter: Option<Vec<RelationType>>,
341 ) -> Result<Option<(Event, Vec<Event>)>> {
342 Ok(self
344 .inner
345 .state
346 .read()
347 .await?
348 .find_event_with_relations(event_id, filter)
349 .await
350 .ok()
351 .flatten())
352 }
353
354 #[cfg(feature = "e2e-encryption")]
360 pub(in super::super) async fn replace_in_memory_utds(
361 &self,
362 resolved_events: &[MaybeResolvedEvent],
363 ) -> Result<bool> {
364 let mut state = self.inner.state.write().await?;
365 let timeline_event_diffs = state.replace_in_memory_utds(resolved_events)?;
366
367 let _ = state.thread_linked_chunk_mut().store_updates().take();
370
371 state
372 .post_process_upserted_events(
373 resolved_events.iter().filter_map(|resolved_event| resolved_event.as_resolved()),
374 None,
383 )
384 .await?;
385
386 let timeline_event_diffs = timeline_event_diffs
387 .into_iter()
388 .flatten()
389 .chain(state.thread_linked_chunk_mut().updates_as_vector_diffs())
390 .collect::<Vec<_>>();
391
392 Ok(if !timeline_event_diffs.is_empty() {
393 state.update_sender.send(
394 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {
395 diffs: timeline_event_diffs,
396 origin: EventsOrigin::Cache,
397 }),
398 Some(RoomEventCacheGenericUpdate { room_id: self.inner.room_id.clone() }),
399 );
400
401 true
402 } else {
403 false
404 })
405 }
406}
407
408#[cfg(all(test, not(target_family = "wasm")))] mod timed_tests {
410 use std::sync::Arc;
411
412 use assert_matches::assert_matches;
413 use eyeball_im::VectorDiff;
414 use futures_util::FutureExt as _;
415 use matrix_sdk_base::{
416 RoomState, ThreadingSupport,
417 cross_process_lock::CrossProcessLockConfig,
418 event_cache::{
419 Gap,
420 store::{EventCacheStore as _, MemoryStore},
421 },
422 linked_chunk::{
423 ChunkContent, ChunkIdentifier, LinkedChunkId, Position, Update,
424 lazy_loader::from_all_chunks,
425 },
426 store::StoreConfig,
427 sync::Timeline,
428 };
429 use matrix_sdk_test::{ALICE, async_test, event_factory::EventFactory};
430 use ruma::{
431 event_id,
432 events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent},
433 room_id, user_id,
434 };
435 use strass::assert_let;
436 use tokio::task::yield_now;
437
438 use super::{
439 super::{super::RoomEventCacheGenericUpdate, TimelineVectorDiffs},
440 MaybeReceiptEventContent, ThreadEventCacheUpdate,
441 };
442 use crate::{assert_let_timeout, test_utils::client::MockClientBuilder};
443
444 #[async_test]
445 async fn test_write_to_storage() {
446 let room_id = room_id!("!r0");
447 let thread_root = event_id!("$t0_ev0");
448 let thread_event_id_0 = event_id!("$t0_ev1");
449
450 let f = EventFactory::new().room(room_id).sender(user_id!("@mnt_io:matrix.org"));
451
452 let event_cache_store = Arc::new(MemoryStore::new());
453
454 let client = MockClientBuilder::new(None)
455 .on_builder(|builder| {
456 builder
457 .store_config(
458 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
459 .event_cache_store(event_cache_store.clone()),
460 )
461 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
462 })
463 .build()
464 .await;
465
466 let event_cache = client.event_cache();
467 event_cache.subscribe().unwrap();
468
469 client.base_client().get_or_create_room(room_id, RoomState::Joined);
470
471 let (thread_event_cache, _drop_handles) =
472 event_cache.thread(room_id, thread_root).await.unwrap();
473 let (thread_events, mut thread_stream) = thread_event_cache.subscribe().await.unwrap();
474
475 assert!(thread_events.is_empty());
476
477 let timeline = Timeline {
479 limited: true,
480 prev_batch: Some("raclette".to_owned()),
481 events: vec![
482 f.text_msg("salut")
483 .event_id(thread_event_id_0)
484 .in_thread(thread_root, thread_root)
485 .into_event(),
486 ],
487 };
488
489 thread_event_cache
490 .handle_joined_room_update(timeline, MaybeReceiptEventContent::none())
491 .await
492 .unwrap();
493
494 assert_matches!(
495 thread_stream.recv().await,
496 Ok(ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) => {
497 assert_eq!(diffs.len(), 2);
498 assert_matches!(&diffs[0], VectorDiff::Clear);
499 assert_matches!(&diffs[1], VectorDiff::Append { values: events } => {
500 assert_eq!(events.len(), 1);
501 assert_eq!(events[0].event_id(), Some(thread_event_id_0));
502 });
503 }
504 );
505 assert!(thread_stream.is_empty());
506
507 let linked_chunk = from_all_chunks::<3, _, _>(
509 event_cache_store
510 .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root))
511 .await
512 .unwrap(),
513 )
514 .unwrap()
515 .unwrap();
516
517 assert_eq!(linked_chunk.chunks().count(), 2);
518
519 let mut chunks = linked_chunk.chunks();
520
521 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Gap(gap) => {
523 assert_eq!(gap.token, "raclette");
524 });
525
526 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
528 assert_eq!(events.len(), 1);
529 assert_eq!(events[0].event_id(), Some(thread_event_id_0));
530 });
531
532 assert!(chunks.next().is_none());
534 }
535
536 #[async_test]
537 async fn test_write_to_storage_strips_bundled_relations() {
538 let sender = user_id!("@mnt_io:matrix.org");
539 let room_id = room_id!("!r0");
540 let thread_root = event_id!("$t0_ev0");
541 let thread_event_id_0 = event_id!("$t0_ev1");
542
543 let f = EventFactory::new().room(room_id).sender(sender);
544
545 let event_cache_store = Arc::new(MemoryStore::new());
546
547 let client = MockClientBuilder::new(None)
548 .on_builder(|builder| {
549 builder
550 .store_config(
551 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
552 .event_cache_store(event_cache_store.clone()),
553 )
554 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
555 })
556 .build()
557 .await;
558
559 let event_cache = client.event_cache();
560 event_cache.subscribe().unwrap();
561
562 client.base_client().get_or_create_room(room_id, RoomState::Joined);
563
564 let (thread_event_cache, _drop_handles) =
565 event_cache.thread(room_id, thread_root).await.unwrap();
566
567 let timeline = Timeline {
569 limited: false,
570 prev_batch: None,
571 events: vec![
572 f.text_msg("s 'up")
573 .event_id(thread_event_id_0)
574 .with_bundled_edit(f.text_msg("Hello, Kind Sir").sender(sender))
575 .in_thread(thread_root, thread_root)
576 .into_event(),
577 ],
578 };
579
580 thread_event_cache
581 .handle_joined_room_update(timeline, MaybeReceiptEventContent::none())
582 .await
583 .unwrap();
584
585 {
587 let (events, _) = thread_event_cache.subscribe().await.unwrap();
588
589 assert_eq!(events.len(), 1);
590
591 let event = events[0].raw().deserialize().unwrap();
592 assert_eq!(event.event_id(), thread_event_id_0);
593 assert_let!(
594 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) =
595 event
596 );
597 assert!(msg.as_original().unwrap().unsigned.relations.replace.is_some());
598 }
599
600 let linked_chunk = from_all_chunks::<3, _, _>(
602 event_cache_store
603 .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root))
604 .await
605 .unwrap(),
606 )
607 .unwrap()
608 .unwrap();
609
610 assert_eq!(linked_chunk.chunks().count(), 1);
611
612 let mut chunks = linked_chunk.chunks();
613 assert_matches!(chunks.next().unwrap().content(), ChunkContent::Items(events) => {
614 assert_eq!(events.len(), 1);
615
616 let event = events[0].raw().deserialize().unwrap();
617 assert_eq!(event.event_id(), thread_event_id_0);
618
619 assert_let!(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(msg)) = event);
620 assert!(msg.as_original().unwrap().unsigned.relations.replace.is_none());
621 });
622
623 assert!(chunks.next().is_none());
625 }
626
627 #[async_test]
628 async fn test_clear() {
629 let room_id = room_id!("!r0");
630 let f = EventFactory::new().room(room_id).sender(user_id!("@mnt_io:matrix.org"));
631
632 let event_cache_store = Arc::new(MemoryStore::new());
633
634 let thread_root = event_id!("$t0_ev0");
635 let thread_event_id_0 = event_id!("$t0_ev1");
636 let thread_event_id_1 = event_id!("$t0_ev2");
637
638 let thread_event_0 = f
639 .text_msg("foo")
640 .event_id(thread_event_id_0)
641 .in_thread(thread_root, thread_root)
642 .into_event();
643 let thread_event_1 = f
644 .text_msg("bar")
645 .event_id(thread_event_id_1)
646 .in_thread(thread_root, thread_event_id_0)
647 .into_event();
648
649 event_cache_store
651 .handle_linked_chunk_updates(
652 LinkedChunkId::Thread(room_id, thread_root),
653 vec![
654 Update::NewItemsChunk {
656 previous: None,
657 new: ChunkIdentifier::new(0),
658 next: None,
659 },
660 Update::NewGapChunk {
662 previous: Some(ChunkIdentifier::new(0)),
663 new: ChunkIdentifier::new(42),
665 next: None,
666 gap: Gap { token: "comté".to_owned() },
667 },
668 Update::NewItemsChunk {
670 previous: Some(ChunkIdentifier::new(42)),
671 new: ChunkIdentifier::new(1),
672 next: None,
673 },
674 Update::PushItems {
675 at: Position::new(ChunkIdentifier::new(1), 0),
676 items: vec![thread_event_0.clone()],
677 },
678 Update::NewItemsChunk {
680 previous: Some(ChunkIdentifier::new(1)),
681 new: ChunkIdentifier::new(2),
682 next: None,
683 },
684 Update::PushItems {
685 at: Position::new(ChunkIdentifier::new(2), 0),
686 items: vec![thread_event_1.clone()],
687 },
688 ],
689 )
690 .await
691 .unwrap();
692
693 let client = MockClientBuilder::new(None)
694 .on_builder(|builder| {
695 builder
696 .store_config(
697 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
698 .event_cache_store(event_cache_store.clone()),
699 )
700 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
701 })
702 .build()
703 .await;
704
705 let event_cache = client.event_cache();
706 event_cache.subscribe().unwrap();
707
708 client.base_client().get_or_create_room(room_id, RoomState::Joined);
709
710 let (thread_event_cache, _drop_handles) =
711 event_cache.thread(room_id, thread_root).await.unwrap();
712 let (thread_events, mut thread_stream) = thread_event_cache.subscribe().await.unwrap();
713
714 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
715
716 {
718 assert!(thread_event_cache.find_event(thread_event_id_0).await.unwrap().is_some());
719 assert!(thread_event_cache.find_event(thread_event_id_1).await.unwrap().is_some());
720 }
721
722 {
724 assert_eq!(thread_events.len(), 1);
727 assert_eq!(thread_events[0].event_id().unwrap(), thread_event_id_1);
728
729 assert!(thread_stream.is_empty());
730 }
731
732 {
734 thread_event_cache.pagination().run_backwards_once(20).await.unwrap();
735
736 assert_matches!(
737 thread_stream.recv().await,
738 Ok(ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) => {
739 assert_eq!(diffs.len(), 1);
740 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
741 assert_eq!(event.event_id(), Some(thread_event_id_0));
743 });
744 }
745 );
746 assert!(thread_stream.is_empty());
747
748 assert_matches!(
749 generic_stream.recv().await,
750 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
751 assert_eq!(room_id, expected_room_id);
752 }
753 );
754 assert!(generic_stream.is_empty());
755 }
756
757 event_cache.clear_all_rooms().await.unwrap();
759
760 assert_matches!(
762 thread_stream.recv().await,
763 Ok(ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) => {
764 assert_eq!(diffs.len(), 1);
765 assert_matches!(&diffs[0], VectorDiff::Clear);
766 }
767 );
768
769 assert_matches!(
771 generic_stream.recv().await,
772 Ok(RoomEventCacheGenericUpdate { room_id: received_room_id }) => {
773 assert_eq!(received_room_id, room_id);
774 }
775 );
776 assert_matches!(
778 generic_stream.recv().await,
779 Ok(RoomEventCacheGenericUpdate { room_id: received_room_id }) => {
780 assert_eq!(received_room_id, room_id);
781 }
782 );
783 assert!(generic_stream.is_empty());
784
785 assert!(thread_event_cache.find_event(thread_event_id_0).await.unwrap().is_none());
788 assert!(thread_event_cache.find_event(thread_event_id_1).await.unwrap().is_none());
789
790 let (thread_events, _) = thread_event_cache.subscribe().await.unwrap();
792 assert!(thread_events.is_empty());
793
794 assert!(
796 event_cache_store
797 .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root))
798 .await
799 .unwrap()
800 .is_empty()
801 );
802 }
803
804 #[async_test]
805 async fn test_load_from_storage() {
806 let room_id = room_id!("!r0");
807 let f = EventFactory::new().room(room_id).sender(user_id!("@mnt_io:matrix.org"));
808
809 let event_cache_store = Arc::new(MemoryStore::new());
810
811 let thread_root = event_id!("$t0");
812 let thread_event_id_0 = event_id!("$t0_ev0");
813 let thread_event_id_1 = event_id!("$t0_ev1");
814
815 let thread_event_0 = f
816 .text_msg("hello world")
817 .event_id(thread_event_id_0)
818 .in_thread(thread_root, thread_root)
819 .into_event();
820 let thread_event_1 = f
821 .text_msg("how's it going")
822 .event_id(thread_event_id_1)
823 .in_thread(thread_root, thread_event_id_1)
824 .into_event();
825
826 let updates = vec![
830 Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(0), next: None },
832 Update::NewGapChunk {
834 previous: Some(ChunkIdentifier::new(0)),
835 new: ChunkIdentifier::new(42),
837 next: None,
838 gap: Gap { token: "gruyère".to_owned() },
839 },
840 Update::NewItemsChunk {
842 previous: Some(ChunkIdentifier::new(42)),
843 new: ChunkIdentifier::new(1),
844 next: None,
845 },
846 Update::PushItems {
847 at: Position::new(ChunkIdentifier::new(1), 0),
848 items: vec![thread_event_0.clone()],
849 },
850 Update::NewItemsChunk {
852 previous: Some(ChunkIdentifier::new(1)),
853 new: ChunkIdentifier::new(2),
854 next: None,
855 },
856 Update::PushItems {
857 at: Position::new(ChunkIdentifier::new(2), 0),
858 items: vec![thread_event_1.clone()],
859 },
860 ];
861 event_cache_store
862 .handle_linked_chunk_updates(LinkedChunkId::Room(room_id), updates.clone())
863 .await
864 .unwrap();
865 event_cache_store
866 .handle_linked_chunk_updates(LinkedChunkId::Thread(room_id, thread_root), updates)
867 .await
868 .unwrap();
869
870 let client = MockClientBuilder::new(None)
871 .on_builder(|builder| {
872 builder
873 .store_config(
874 StoreConfig::new(CrossProcessLockConfig::multi_process("hodor"))
875 .event_cache_store(event_cache_store.clone()),
876 )
877 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
878 })
879 .build()
880 .await;
881
882 let event_cache = client.event_cache();
883 event_cache.subscribe().unwrap();
884
885 client.base_client().get_or_create_room(room_id, RoomState::Joined);
886
887 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
890 let (thread_event_cache, _drop_handles) =
891 event_cache.thread(room_id, thread_root).await.unwrap();
892 let (thread_events, mut thread_stream) = thread_event_cache.subscribe().await.unwrap();
893
894 for _ in 0..2 {
897 assert_matches!(
898 generic_stream.recv().await,
899 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
900 assert_eq!(room_id, expected_room_id);
901 }
902 );
903 }
904 assert!(generic_stream.is_empty());
905
906 assert_eq!(thread_events.len(), 1);
909 assert_eq!(thread_events[0].event_id().unwrap(), thread_event_id_1);
910 assert!(thread_stream.is_empty());
911
912 assert!(thread_event_cache.find_event(thread_event_id_0).await.unwrap().is_some());
915 assert!(thread_event_cache.find_event(thread_event_id_1).await.unwrap().is_some());
916
917 thread_event_cache.pagination().run_backwards_once(20).await.unwrap();
919
920 assert_matches!(
921 thread_stream.recv().await,
922 Ok(ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) => {
923 assert_eq!(diffs.len(), 1);
924 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value: event } => {
925 assert_eq!(event.event_id(), Some(thread_event_id_0));
926 });
927 }
928 );
929 assert!(thread_stream.is_empty());
930
931 assert_matches!(
933 generic_stream.recv().await,
934 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) => {
935 assert_eq!(expected_room_id, room_id);
936 }
937 );
938 assert!(generic_stream.is_empty());
939
940 let timeline = Timeline { limited: false, prev_batch: None, events: vec![thread_event_1] };
942
943 thread_event_cache
944 .handle_joined_room_update(timeline, MaybeReceiptEventContent::none())
945 .await
946 .unwrap();
947
948 assert!(generic_stream.recv().now_or_never().is_none());
951
952 let (thread_events, _) = thread_event_cache.subscribe().await.unwrap();
957 assert_eq!(thread_events.len(), 2);
958 assert_eq!(thread_events[0].event_id(), Some(thread_event_id_0));
959 assert_eq!(thread_events[1].event_id(), Some(thread_event_id_1));
960 }
961
962 #[async_test]
963 async fn test_load_from_storage_resilient_to_failure() {
964 let room_id = room_id!("!r0");
965 let f = EventFactory::new().room(room_id).sender(user_id!("@mnt_io:matrix.org"));
966
967 let event_cache_store = Arc::new(MemoryStore::new());
968
969 let thread_root = event_id!("$t0");
970 let thread_event_id_0 = event_id!("$t0_ev0");
971
972 let thread_event_0 = f
973 .text_msg("hello world")
974 .event_id(thread_event_id_0)
975 .in_thread(thread_root, thread_root)
976 .into_event();
977
978 event_cache_store
980 .handle_linked_chunk_updates(
981 LinkedChunkId::Thread(room_id, thread_root),
982 vec![
983 Update::NewItemsChunk {
984 previous: None,
985 new: ChunkIdentifier::new(0),
986 next: None,
987 },
988 Update::PushItems {
989 at: Position::new(ChunkIdentifier::new(0), 0),
990 items: vec![thread_event_0],
991 },
992 Update::NewItemsChunk {
993 previous: Some(ChunkIdentifier::new(0)),
994 new: ChunkIdentifier::new(1),
995 next: Some(ChunkIdentifier::new(0)),
996 },
997 ],
998 )
999 .await
1000 .unwrap();
1001
1002 let client = MockClientBuilder::new(None)
1003 .on_builder(|builder| {
1004 builder
1005 .store_config(
1006 StoreConfig::new(CrossProcessLockConfig::multi_process("holder"))
1007 .event_cache_store(event_cache_store.clone()),
1008 )
1009 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
1010 })
1011 .build()
1012 .await;
1013
1014 let event_cache = client.event_cache();
1015 event_cache.subscribe().unwrap();
1016
1017 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1018
1019 let (thread_event_cache, _drop_handles) =
1020 event_cache.thread(room_id, thread_root).await.unwrap();
1021 let (thread_events, _) = thread_event_cache.subscribe().await.unwrap();
1022
1023 assert!(thread_events.is_empty());
1026
1027 let raw_chunks = event_cache_store
1030 .load_all_chunks(LinkedChunkId::Thread(room_id, thread_root))
1031 .await
1032 .unwrap();
1033 assert!(raw_chunks.is_empty());
1034 }
1035
1036 #[async_test]
1037 async fn test_reload_when_dirty() {
1038 let user_id = user_id!("@mnt_io:matrix.org");
1039 let room_id = room_id!("!raclette:patate.ch");
1040
1041 let event_cache_store = MemoryStore::new();
1043
1044 let client_p0 = MockClientBuilder::new(None)
1046 .on_builder(|builder| {
1047 builder
1048 .store_config(
1049 StoreConfig::new(CrossProcessLockConfig::multi_process("process #0"))
1050 .event_cache_store(event_cache_store.clone()),
1051 )
1052 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
1053 })
1054 .build()
1055 .await;
1056
1057 let client_p1 = MockClientBuilder::new(None)
1059 .on_builder(|builder| {
1060 builder
1061 .store_config(
1062 StoreConfig::new(CrossProcessLockConfig::multi_process("process #1"))
1063 .event_cache_store(event_cache_store),
1064 )
1065 .with_threading_support(ThreadingSupport::Enabled { with_subscriptions: true })
1066 })
1067 .build()
1068 .await;
1069
1070 let event_factory = EventFactory::new().room(room_id).sender(user_id);
1071
1072 let thread_root = event_id!("$t0");
1073 let thread_event_id_0 = event_id!("$t0_ev0");
1074 let thread_event_id_1 = event_id!("$t0_ev1");
1075
1076 let thread_event_0 = event_factory
1077 .text_msg("comté")
1078 .event_id(thread_event_id_0)
1079 .in_thread(thread_root, thread_root)
1080 .into_event();
1081 let thread_event_1 = event_factory
1082 .text_msg("morbier")
1083 .event_id(thread_event_id_1)
1084 .in_thread(thread_root, thread_event_id_0)
1085 .into_event();
1086
1087 client_p0
1089 .event_cache_store()
1090 .lock()
1091 .await
1092 .expect("[p0] Could not acquire the event cache lock")
1093 .as_clean()
1094 .expect("[p0] Could not acquire a clean event cache lock")
1095 .handle_linked_chunk_updates(
1096 LinkedChunkId::Thread(room_id, thread_root),
1097 vec![
1098 Update::NewItemsChunk {
1099 previous: None,
1100 new: ChunkIdentifier::new(0),
1101 next: None,
1102 },
1103 Update::PushItems {
1104 at: Position::new(ChunkIdentifier::new(0), 0),
1105 items: vec![thread_event_0],
1106 },
1107 Update::NewItemsChunk {
1108 previous: Some(ChunkIdentifier::new(0)),
1109 new: ChunkIdentifier::new(1),
1110 next: None,
1111 },
1112 Update::PushItems {
1113 at: Position::new(ChunkIdentifier::new(1), 0),
1114 items: vec![thread_event_1],
1115 },
1116 ],
1117 )
1118 .await
1119 .unwrap();
1120
1121 let (thread_event_cache_p0, thread_event_cache_p1) = {
1123 let event_cache_p0 = client_p0.event_cache();
1124 event_cache_p0.subscribe().unwrap();
1125
1126 let event_cache_p1 = client_p1.event_cache();
1127 event_cache_p1.subscribe().unwrap();
1128
1129 client_p0.base_client().get_or_create_room(room_id, RoomState::Joined);
1130 client_p1.base_client().get_or_create_room(room_id, RoomState::Joined);
1131
1132 let (thread_event_cache_p0, _drop_handles) =
1133 event_cache_p0.thread(room_id, thread_root).await.unwrap();
1134 let (thread_event_cache_p1, _drop_handles) =
1135 event_cache_p1.thread(room_id, thread_root).await.unwrap();
1136
1137 (thread_event_cache_p0, thread_event_cache_p1)
1138 };
1139
1140 let mut updates_stream_p0 = {
1146 let thread_event_cache = &thread_event_cache_p0;
1147
1148 let (initial_updates, mut updates_stream) =
1149 thread_event_cache_p0.subscribe().await.unwrap();
1150
1151 assert_eq!(initial_updates.len(), 1);
1153 assert_eq!(initial_updates[0].event_id(), Some(thread_event_id_1));
1154 assert!(updates_stream.is_empty());
1155
1156 thread_event_cache.pagination().run_backwards_once(1).await.unwrap();
1158
1159 assert_matches!(
1161 updates_stream.recv().await.unwrap(),
1162 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
1163 assert_eq!(diffs.len(), 1, "{diffs:#?}");
1164 assert_matches!(
1165 &diffs[0],
1166 VectorDiff::Insert { index: 0, value: event } => {
1167 assert_eq!(event.event_id(), Some(thread_event_id_0));
1168 }
1169 );
1170 }
1171 );
1172
1173 updates_stream
1174 };
1175
1176 let mut updates_stream_p1 = {
1178 let thread_event_cache = &thread_event_cache_p1;
1179 let (initial_updates, mut updates_stream) =
1180 thread_event_cache_p1.subscribe().await.unwrap();
1181
1182 assert_eq!(initial_updates.len(), 1);
1184 assert_eq!(initial_updates[0].event_id(), Some(thread_event_id_1));
1185 assert!(updates_stream.is_empty());
1186
1187 thread_event_cache.pagination().run_backwards_once(1).await.unwrap();
1189
1190 assert_matches!(
1192 updates_stream.recv().await.unwrap(),
1193 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
1194 assert_eq!(diffs.len(), 1, "{diffs:#?}");
1195 assert_matches!(
1196 &diffs[0],
1197 VectorDiff::Insert { index: 0, value: event } => {
1198 assert_eq!(event.event_id(), Some(thread_event_id_0));
1199 }
1200 );
1201 }
1202 );
1203
1204 updates_stream
1205 };
1206
1207 for _ in 0..3 {
1209 {
1213 let thread_event_cache = &thread_event_cache_p0;
1214 let updates_stream = &mut updates_stream_p0;
1215
1216 let (initial_updates, _) = thread_event_cache.subscribe().await.unwrap();
1221
1222 assert_eq!(initial_updates.len(), 1);
1223 assert_eq!(initial_updates[0].event_id(), Some(thread_event_id_1));
1224
1225 assert_matches!(
1227 updates_stream.recv().await.unwrap(),
1228 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
1229 assert_eq!(diffs.len(), 2, "{diffs:#?}");
1230 assert_matches!(&diffs[0], VectorDiff::Clear);
1231 assert_matches!(
1232 &diffs[1],
1233 VectorDiff::Append { values: events } => {
1234 assert_eq!(events.len(), 1);
1235 assert_eq!(events[0].event_id(), Some(thread_event_id_1));
1236 }
1237 );
1238 }
1239 );
1240 assert_matches!(
1241 updates_stream.recv().await.unwrap(),
1242 ThreadEventCacheUpdate::UpdateSummary(summary) => {
1243 assert_eq!(summary.latest_reply.as_deref(), Some(thread_event_id_1));
1244 assert_eq!(summary.num_replies, 2);
1245 }
1246 );
1247
1248 thread_event_cache.pagination().run_backwards_once(1).await.unwrap();
1250
1251 assert_matches!(
1254 updates_stream.recv().await.unwrap(),
1255 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
1256 assert_eq!(diffs.len(), 1, "{diffs:#?}");
1257 assert_matches!(
1258 &diffs[0],
1259 VectorDiff::Insert { index: 0, value: event } => {
1260 assert_eq!(event.event_id(), Some(thread_event_id_0));
1261 }
1262 );
1263 }
1264 );
1265
1266 assert!(updates_stream.is_empty());
1267 }
1268
1269 {
1273 let thread_event_cache = &thread_event_cache_p1;
1274 let updates_stream = &mut updates_stream_p1;
1275
1276 let (initial_updates, _) = thread_event_cache.subscribe().await.unwrap();
1281
1282 assert_eq!(initial_updates.len(), 1);
1283 assert_eq!(initial_updates[0].event_id(), Some(thread_event_id_1));
1284
1285 assert_matches!(
1287 updates_stream.recv().await.unwrap(),
1288 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
1289 assert_eq!(diffs.len(), 2, "{diffs:#?}");
1290 assert_matches!(&diffs[0], VectorDiff::Clear);
1291 assert_matches!(
1292 &diffs[1],
1293 VectorDiff::Append { values: events } => {
1294 assert_eq!(events.len(), 1);
1295 assert_eq!(events[0].event_id(), Some(thread_event_id_1));
1296 }
1297 );
1298 }
1299 );
1300 assert_matches!(
1301 updates_stream.recv().await.unwrap(),
1302 ThreadEventCacheUpdate::UpdateSummary(summary) => {
1303 assert_eq!(summary.latest_reply.as_deref(), Some(thread_event_id_1));
1304 assert_eq!(summary.num_replies, 2);
1305 }
1306 );
1307
1308 thread_event_cache.pagination().run_backwards_once(1).await.unwrap();
1310
1311 assert_matches!(
1314 updates_stream.recv().await.unwrap(),
1315 ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. }) => {
1316 assert_eq!(diffs.len(), 1, "{diffs:#?}");
1317 assert_matches!(
1318 &diffs[0],
1319 VectorDiff::Insert { index: 0, value: event } => {
1320 assert_eq!(event.event_id(), Some(thread_event_id_0));
1321 }
1322 );
1323 }
1324 );
1325 }
1326 }
1327 }
1328
1329 #[async_test]
1330 async fn test_auto_shrink_after_all_subscribers_are_gone() {
1331 let room_id = room_id!("!r0");
1332 let thread_id = event_id!("$t0");
1333
1334 let client = MockClientBuilder::new(None).build().await;
1335
1336 let f = EventFactory::new().room(room_id).sender(*ALICE);
1337
1338 let event_id_0 = event_id!("$ev0");
1339 let event_id_1 = event_id!("$ev1");
1340
1341 let thread_root =
1342 f.text_msg("gr00t").event_id(thread_id).in_thread(thread_id, thread_id).into_event();
1343 let event_0 =
1344 f.text_msg("hello").event_id(event_id_0).in_thread(thread_id, event_id_0).into_event();
1345 let event_1 =
1346 f.text_msg("world").event_id(event_id_1).in_thread(thread_id, event_id_1).into_event();
1347
1348 {
1351 client
1352 .event_cache_store()
1353 .lock()
1354 .await
1355 .expect("Could not acquire the event cache lock")
1356 .as_clean()
1357 .expect("Could not acquire a clean event cache lock")
1358 .handle_linked_chunk_updates(
1359 LinkedChunkId::Thread(room_id, thread_id),
1360 vec![
1361 Update::NewItemsChunk {
1362 previous: None,
1363 new: ChunkIdentifier::new(0),
1364 next: None,
1365 },
1366 Update::PushItems {
1367 at: Position::new(ChunkIdentifier::new(0), 0),
1368 items: vec![thread_root, event_0],
1369 },
1370 Update::NewItemsChunk {
1371 previous: Some(ChunkIdentifier::new(0)),
1372 new: ChunkIdentifier::new(1),
1373 next: None,
1374 },
1375 Update::PushItems {
1376 at: Position::new(ChunkIdentifier::new(1), 0),
1377 items: vec![event_1],
1378 },
1379 ],
1380 )
1381 .await
1382 .unwrap();
1383 }
1384
1385 let event_cache = client.event_cache();
1386 event_cache.subscribe().unwrap();
1387
1388 client.base_client().get_or_create_room(room_id, RoomState::Joined);
1389
1390 let (thread_event_cache, _drop_handles) =
1391 event_cache.thread(room_id, thread_id).await.unwrap();
1392
1393 let (events1, mut stream1) = thread_event_cache.subscribe().await.unwrap();
1395 assert_eq!(events1.len(), 1);
1396 assert_eq!(events1[0].event_id(), Some(event_id_1));
1397 assert!(stream1.is_empty());
1398
1399 let mut generic_stream = event_cache.subscribe_to_room_generic_updates();
1400
1401 let outcome = thread_event_cache.pagination().run_backwards_once(20).await.unwrap();
1403 assert_eq!(outcome.events.len(), 2);
1404 assert_eq!(outcome.events[0].event_id(), Some(event_id_0));
1405 assert_eq!(outcome.events[1].event_id(), Some(thread_id));
1406 assert!(outcome.reached_start);
1407
1408 assert_let_timeout!(
1411 Ok(ThreadEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =
1412 stream1.recv()
1413 );
1414 assert_eq!(diffs.len(), 2);
1415 assert_matches!(&diffs[0], VectorDiff::Insert { index: 0, value } => {
1416 assert_eq!(value.event_id(), Some(thread_id));
1417 });
1418 assert_matches!(&diffs[1], VectorDiff::Insert { index: 1, value } => {
1419 assert_eq!(value.event_id(), Some(event_id_0));
1420 });
1421
1422 assert!(stream1.is_empty());
1423
1424 assert_let_timeout!(
1425 Ok(RoomEventCacheGenericUpdate { room_id: expected_room_id }) = generic_stream.recv()
1426 );
1427 assert_eq!(expected_room_id, room_id);
1428 assert!(generic_stream.is_empty());
1429
1430 let (events2, stream2) = thread_event_cache.subscribe().await.unwrap();
1434 assert_eq!(events2.len(), 3);
1435 assert_eq!(events2[0].event_id(), Some(thread_id));
1436 assert_eq!(events2[1].event_id(), Some(event_id_0));
1437 assert_eq!(events2[2].event_id(), Some(event_id_1));
1438 assert!(stream2.is_empty());
1439
1440 let subscriber = {
1442 let state = thread_event_cache.inner.state.read().await.unwrap();
1443 state.update_sender.new_thread_receiver()
1444 };
1445
1446 drop(stream1);
1448 yield_now().await;
1449
1450 assert!(stream2.is_empty());
1452
1453 drop(stream2);
1455 yield_now().await;
1456
1457 {
1460 let state = thread_event_cache.inner.state.read().await.unwrap();
1462 assert_eq!(state.subscribers_handle().count(), 0);
1463
1464 assert!(subscriber.is_empty());
1466 assert!(generic_stream.is_empty());
1467 }
1468
1469 let events3 = thread_event_cache
1471 .inner
1472 .state
1473 .read()
1474 .await
1475 .unwrap()
1476 .thread_linked_chunk()
1477 .events()
1478 .map(|(_position, item)| item.clone())
1479 .collect::<Vec<_>>();
1480 assert_eq!(events3.len(), 1);
1481 assert_eq!(events3[0].event_id(), Some(event_id_1));
1482 }
1483}