Skip to content

Commit 2927974

Browse files
committed
fix(event cache): don't try to remove a previous gap if it's the only chunk in memory
A linked chunk never wants to be empty. However, after a limited gap that doesn't contain events, it may be shrunk to the latest chunk that's a gap. If later we decide to remove the gap (because it's been resolved with no events), then we would try to remove the last chunk, which is not correct. Ideally, we'd keep an events chunk around; but if we have an events chunk *before* a gap, that may look like missing events to the user, at least until the gap has been resolved. The fix to this problem is to *not* optimize / remove the gap, if it's the only chunk kept in memory. This was only a memory optimization, but it's not absolutely required per se.
1 parent 8c780fc commit 2927974

File tree

3 files changed

+61
-12
lines changed

3 files changed

+61
-12
lines changed

crates/matrix-sdk/src/event_cache/pagination.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -367,17 +367,15 @@ impl RoomPagination {
367367
let insert_new_gap_pos = if let Some(gap_id) = prev_gap_id {
368368
// There is a prior gap, let's replace it by new events!
369369
if all_duplicates {
370-
// All the events were duplicated; don't act upon them, and only remove the
371-
// prior gap that we just filled.
372-
trace!("removing previous gap, as all events have been deduplicated");
373-
room_events.remove_empty_chunk_at(gap_id).expect("gap identifier is a valid gap chunk id we read previously")
374-
} else {
375-
trace!("replacing previous gap with the back-paginated events");
376-
377-
// Replace the gap with the events we just deduplicated.
378-
room_events.replace_gap_at(reversed_events.clone(), gap_id)
379-
.expect("gap_identifier is a valid chunk id we read previously")
370+
assert!(reversed_events.is_empty());
380371
}
372+
373+
trace!("replacing previous gap with the back-paginated events");
374+
375+
// Replace the gap with the events we just deduplicated. This might get rid of the
376+
// underlying gap, if the conditions are favorable to us.
377+
room_events.replace_gap_at(reversed_events.clone(), gap_id)
378+
.expect("gap_identifier is a valid chunk id we read previously")
381379
} else if let Some(pos) = first_event_pos {
382380
// No prior gap, but we had some events: assume we need to prepend events
383381
// before those.

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ impl RoomEvents {
126126

127127
/// Remove an empty chunk at the given position.
128128
///
129-
/// Note: the chunk must either be a gap, or an empty items chunk.
129+
/// Note: the chunk must either be a gap, or an empty items chunk, and it
130+
/// must NOT be the last one.
130131
///
131132
/// Returns the next insert position, if any, left after the chunk that has
132133
/// just been removed.
@@ -149,7 +150,24 @@ impl RoomEvents {
149150
events: Vec<Event>,
150151
gap_identifier: ChunkIdentifier,
151152
) -> Result<Option<Position>, Error> {
152-
let next_pos = if events.is_empty() {
153+
// As an optimization, we'll remove the empty chunk if it's a gap.
154+
//
155+
// However, our linked chunk requires that it includes at least one chunk in the
156+
// in-memory representation. We could tweak this invariant, but in the
157+
// meanwhile, don't remove the gap chunk if it's the only one we know
158+
// about.
159+
let has_only_one_chunk = {
160+
let mut it = self.chunks.chunks();
161+
162+
// If there's no chunks at all, then we won't be able to find the gap chunk.
163+
let _ =
164+
it.next().ok_or(Error::InvalidChunkIdentifier { identifier: gap_identifier })?;
165+
166+
// If there's no next chunk, we can conclude there's only one.
167+
it.next().is_none()
168+
};
169+
170+
let next_pos = if events.is_empty() && !has_only_one_chunk {
153171
// There are no new events, so there's no need to create a new empty items
154172
// chunk; instead, remove the gap.
155173
self.chunks.remove_empty_chunk_at(gap_identifier)?

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2507,3 +2507,36 @@ async fn test_timeline_then_empty_timeline_then_deduplication_with_storage() {
25072507
// That's all, folks!
25082508
assert!(subscriber.is_empty());
25092509
}
2510+
2511+
#[async_test]
2512+
async fn test_dont_remove_only_gap() {
2513+
let server = MatrixMockServer::new().await;
2514+
let client = server.client_builder().build().await;
2515+
2516+
client.event_cache().subscribe().unwrap();
2517+
client.event_cache().enable_storage().unwrap();
2518+
2519+
let room_id = room_id!("!galette:saucisse.bzh");
2520+
let room = server
2521+
.sync_room(
2522+
&client,
2523+
JoinedRoomBuilder::new(room_id)
2524+
.set_timeline_limited()
2525+
.set_timeline_prev_batch("brillat-savarin"),
2526+
)
2527+
.await;
2528+
2529+
let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();
2530+
2531+
server
2532+
.mock_room_messages()
2533+
.match_from("brillat-savarin")
2534+
.ok(RoomMessagesResponseTemplate::default())
2535+
.named("room/messages")
2536+
.mount()
2537+
.await;
2538+
2539+
// Back-paginate with the given token.
2540+
let outcome = room_event_cache.pagination().run_backwards_once(16).await.unwrap();
2541+
assert!(outcome.reached_start);
2542+
}

0 commit comments

Comments
 (0)