Skip to content

Commit 712ecf5

Browse files
committed
timeline: after an event cache update lag, fetch previous events back from the cache
Fixes #3311. When the timeline is lagging behind the event cache, it should not only clear what it contains (because it may be lagging some information coming from the event cache), it should also retrieve the events the cache knows about, and adds them as if they were "initial" events. This makes sure that the event cache's events and the timeline's events are always the same, and that, in the case of a lag, there won't be any missing chunks (caused by the event cache back-pagination being further away in the past, than what's displayed in the timeline). The lag behind a bit too hard to reproduce, I've not included a test here; but I think this should be the right move.
1 parent 1f524f2 commit 712ecf5

File tree

2 files changed

+57
-20
lines changed

2 files changed

+57
-20
lines changed

crates/matrix-sdk-ui/src/timeline/builder.rs

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@ use matrix_sdk::{
2121
executor::spawn,
2222
Room,
2323
};
24-
use ruma::{
25-
events::{receipt::ReceiptType, AnySyncTimelineEvent},
26-
RoomVersionId,
27-
};
24+
use ruma::{events::AnySyncTimelineEvent, RoomVersionId};
2825
use tokio::sync::{broadcast, mpsc};
2926
use tracing::{info, info_span, trace, warn, Instrument, Span};
3027

@@ -35,7 +32,7 @@ use super::{
3532
queue::send_queued_messages,
3633
BackPaginationStatus, Timeline, TimelineDropHandle,
3734
};
38-
use crate::{timeline::inner::TimelineEnd, unable_to_decrypt_hook::UtdHookManager};
35+
use crate::unable_to_decrypt_hook::UtdHookManager;
3936

4037
/// Builder that allows creating and configuring various parts of a
4138
/// [`Timeline`].
@@ -137,26 +134,16 @@ impl TimelineBuilder {
137134
let (events, mut event_subscriber) = room_event_cache.subscribe().await?;
138135

139136
let has_events = !events.is_empty();
140-
let track_read_marker_and_receipts = settings.track_read_receipts;
141-
142-
let mut inner = TimelineInner::new(room, unable_to_decrypt_hook).with_settings(settings);
143137

144-
if track_read_marker_and_receipts {
145-
inner.populate_initial_user_receipt(ReceiptType::Read).await;
146-
inner.populate_initial_user_receipt(ReceiptType::ReadPrivate).await;
147-
}
138+
let inner = TimelineInner::new(room, unable_to_decrypt_hook).with_settings(settings);
148139

149-
if has_events {
150-
inner.add_events_at(events, TimelineEnd::Back { from_cache: true }).await;
151-
}
152-
if track_read_marker_and_receipts {
153-
inner.load_fully_read_event().await;
154-
}
140+
inner.replace_with_initial_events(events).await;
155141

156142
let room = inner.room();
157143
let client = room.client();
158144

159145
let room_update_join_handle = spawn({
146+
let room_event_cache = room_event_cache.clone();
160147
let inner = inner.clone();
161148

162149
let span =
@@ -177,7 +164,23 @@ impl TimelineBuilder {
177164
num_skipped,
178165
"Lagged behind event cache updates, resetting timeline"
179166
);
180-
inner.clear().await;
167+
168+
// The updates might have lagged, but the room event cache might have
169+
// events, so retrieve them and add them back again to the timeline,
170+
// after clearing it.
171+
//
172+
// If we can't get a handle on the room cache's events, just clear the
173+
// current timeline.
174+
match room_event_cache.subscribe().await {
175+
Ok((events, _)) => {
176+
inner.replace_with_initial_events(events).await;
177+
}
178+
Err(err) => {
179+
warn!("Error when re-inserting initial events into the timeline: {err}");
180+
inner.clear().await;
181+
}
182+
}
183+
181184
continue;
182185
}
183186
};

crates/matrix-sdk-ui/src/timeline/inner/mod.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ impl<P: RoomDataProvider> TimelineInner<P> {
384384

385385
/// Populates our own latest read receipt in the in-memory by-user read
386386
/// receipt cache.
387-
pub(super) async fn populate_initial_user_receipt(&mut self, receipt_type: ReceiptType) {
387+
pub(super) async fn populate_initial_user_receipt(&self, receipt_type: ReceiptType) {
388388
let own_user_id = self.room_data_provider.own_user_id().to_owned();
389389

390390
let mut read_receipt = self
@@ -965,6 +965,40 @@ impl TimelineInner {
965965
&self.room_data_provider
966966
}
967967

968+
/// Replaces the content of the current timeline with initial events.
969+
///
970+
/// Also sets up read receipts and the read marker for a live timeline of a
971+
/// room.
972+
///
973+
/// This is all done with a single lock guard, since we don't want the state
974+
/// to be modified between the clear and re-insertion of new events.
975+
pub(super) async fn replace_with_initial_events(&self, events: Vec<SyncTimelineEvent>) {
976+
let mut state = self.state.write().await;
977+
978+
state.clear();
979+
980+
let track_read_markers = self.settings.track_read_receipts;
981+
if track_read_markers {
982+
self.populate_initial_user_receipt(ReceiptType::Read).await;
983+
self.populate_initial_user_receipt(ReceiptType::ReadPrivate).await;
984+
}
985+
986+
if !events.is_empty() {
987+
state
988+
.add_events_at(
989+
events,
990+
TimelineEnd::Back { from_cache: true },
991+
&self.room_data_provider,
992+
&self.settings,
993+
)
994+
.await;
995+
}
996+
997+
if track_read_markers {
998+
self.load_fully_read_event().await;
999+
}
1000+
}
1001+
9681002
/// Get the current fully-read event, from storage.
9691003
pub(super) async fn fully_read_event(&self) -> Option<FullyReadEvent> {
9701004
match self.room().account_data_static().await {

0 commit comments

Comments
 (0)