Skip to content

Commit 1c19e74

Browse files
committed
fix(event cache): when some thread events came from sync, don't consider we've reached the start if we haven't seen the root yet
1 parent 6a1576a commit 1c19e74

File tree

3 files changed

+96
-8
lines changed

3 files changed

+96
-8
lines changed

crates/matrix-sdk/src/event_cache/room/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1447,7 +1447,9 @@ mod private {
14471447
fn get_or_reload_thread(&mut self, root_event_id: OwnedEventId) -> &mut ThreadEventCache {
14481448
// TODO: when there's persistent storage, try to lazily reload from disk, if
14491449
// missing from memory.
1450-
self.threads.entry(root_event_id).or_insert_with(ThreadEventCache::new)
1450+
self.threads
1451+
.entry(root_event_id.clone())
1452+
.or_insert_with(|| ThreadEventCache::new(root_event_id))
14511453
}
14521454

14531455
#[instrument(skip_all)]

crates/matrix-sdk/src/event_cache/room/threads.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ pub struct ThreadEventCacheUpdate {
4242

4343
/// All the information related to a single thread.
4444
pub(crate) struct ThreadEventCache {
45+
/// The ID of the thread root event, which is the first event in the thread
46+
/// (and eventually the first in the linked chunk).
47+
thread_root: OwnedEventId,
48+
4549
/// The linked chunk for this thread.
4650
chunk: EventLinkedChunk,
4751

@@ -51,8 +55,8 @@ pub(crate) struct ThreadEventCache {
5155

5256
impl ThreadEventCache {
5357
/// Create a new empty thread event cache.
54-
pub fn new() -> Self {
55-
Self { chunk: EventLinkedChunk::new(), sender: Sender::new(32) }
58+
pub fn new(thread_root: OwnedEventId) -> Self {
59+
Self { chunk: EventLinkedChunk::new(), sender: Sender::new(32), thread_root }
5660
}
5761

5862
/// Subscribe to live events from this thread.
@@ -113,10 +117,16 @@ impl ThreadEventCache {
113117
return LoadMoreEventsBackwardsOutcome::Gap { prev_token: Some(prev_token) };
114118
}
115119

116-
// If we don't have any gap anymore, but we do have events, then we're done.
117-
if self.chunk.events().next().is_some() {
118-
trace!("thread chunk is fully loaded and non-empty: reached_start=true");
119-
return LoadMoreEventsBackwardsOutcome::StartOfTimeline;
120+
// If we don't have a gap, then the first event should be the the thread's root;
121+
// otherwise, we'll restart a pagination from the end.
122+
if let Some((_pos, event)) = self.chunk.events().next() {
123+
let first_event_id =
124+
event.event_id().expect("a linked chunk only stores events with IDs");
125+
126+
if first_event_id == self.thread_root {
127+
trace!("thread chunk is fully loaded and non-empty: reached_start=true");
128+
return LoadMoreEventsBackwardsOutcome::StartOfTimeline;
129+
}
120130
}
121131

122132
// Otherwise, we don't have a gap nor events. We don't have anything. Poor us.

crates/matrix-sdk/tests/integration/event_cache.rs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ use matrix_sdk::{
1010
deserialized_responses::TimelineEvent,
1111
event_cache::{
1212
BackPaginationOutcome, EventCacheError, RoomEventCacheUpdate, RoomPaginationStatus,
13+
ThreadEventCacheUpdate,
1314
},
1415
linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update},
1516
store::StoreConfig,
1617
test_utils::{
1718
assert_event_matches_msg,
18-
mocks::{MatrixMockServer, RoomMessagesResponseTemplate},
19+
mocks::{MatrixMockServer, RoomMessagesResponseTemplate, RoomRelationsResponseTemplate},
1920
},
2021
};
2122
use matrix_sdk_base::event_cache::{
@@ -2682,3 +2683,78 @@ async fn test_relations_ordering() {
26822683
assert_eq!(relations[2].event_id().unwrap(), edit3);
26832684
assert_eq!(relations[3].event_id().unwrap(), edit4);
26842685
}
2686+
2687+
#[async_test]
2688+
async fn test_thread_can_paginate_even_if_seen_sync_event() {
2689+
let server = MatrixMockServer::new().await;
2690+
let client = server.client_builder().build().await;
2691+
2692+
let room_id = room_id!("!galette:saucisse.bzh");
2693+
2694+
let event_cache = client.event_cache();
2695+
event_cache.subscribe().unwrap();
2696+
2697+
let thread_root_id = event_id!("$thread_root");
2698+
let thread_resp_id = event_id!("$thread_resp");
2699+
2700+
// Receive an in-thread event.
2701+
let f = EventFactory::new().room(room_id).sender(*ALICE);
2702+
let room = server
2703+
.sync_room(
2704+
&client,
2705+
JoinedRoomBuilder::new(room_id).add_timeline_event(
2706+
f.text_msg("that's a good point")
2707+
.in_thread(thread_root_id, thread_root_id)
2708+
.event_id(thread_resp_id),
2709+
),
2710+
)
2711+
.await;
2712+
2713+
let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
2714+
2715+
let (mut thread_events, mut thread_stream) =
2716+
room_event_cache.subscribe_to_thread(thread_root_id.to_owned()).await;
2717+
2718+
// Sanity check: the sync event is added to the thread. This is racy because the
2719+
// update might not have been handled by the event cache yet.
2720+
let first_event = if thread_events.is_empty() {
2721+
assert_let_timeout!(Ok(ThreadEventCacheUpdate { diffs, .. }) = thread_stream.recv());
2722+
assert_eq!(diffs.len(), 1);
2723+
let mut diffs = diffs;
2724+
assert_let!(VectorDiff::Append { values } = diffs.remove(0));
2725+
assert_eq!(values.len(), 1);
2726+
let mut values = values;
2727+
values.remove(0)
2728+
} else {
2729+
assert_eq!(thread_events.len(), 1);
2730+
thread_events.remove(0)
2731+
};
2732+
assert_eq!(first_event.event_id().as_deref(), Some(thread_resp_id));
2733+
2734+
// It's possible to paginate the thread, and this will push the thread root
2735+
// because there's no prev-batch token.
2736+
server
2737+
.mock_room_relations()
2738+
.match_target_event(thread_root_id.to_owned())
2739+
.ok(RoomRelationsResponseTemplate::default())
2740+
.mock_once()
2741+
.mount()
2742+
.await;
2743+
2744+
server
2745+
.mock_room_event()
2746+
.match_event_id()
2747+
.ok(f.text_msg("Thread root").event_id(thread_root_id).into())
2748+
.mock_once()
2749+
.mount()
2750+
.await;
2751+
2752+
let hit_start =
2753+
room_event_cache.paginate_thread_backwards(thread_root_id.to_owned(), 42).await.unwrap();
2754+
assert!(hit_start);
2755+
2756+
assert_let_timeout!(Ok(ThreadEventCacheUpdate { diffs, .. }) = thread_stream.recv());
2757+
assert_eq!(diffs.len(), 1);
2758+
assert_let!(VectorDiff::Insert { index: 0, value } = &diffs[0]);
2759+
assert_eq!(value.event_id().as_deref(), Some(thread_root_id));
2760+
}

0 commit comments

Comments
 (0)