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