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