atagen

matrix-rust-sdk

public
Star0
Watch0

Commit b5fb6f1fd41a

tarball

atagen <boss@atagen.co>2026-10-25 08:25 UTC

1 file changed+56-2
event-cache: rewrite a gap in place after an empty backwards page

push_backwards_pagination_events removed the gap and reinserted its successor at the next chunk, which panics with ChunkIsAGap when that chunk is a gap too. A fill_history_gap_once page with no events hit it and took the room history coordinator down with it, leaving every later history call to fail with ClientDropped.
Modified file: crates/matrix-sdk/src/event_cache/caches/event_linked_chunk.rs+56 additions−2 deletions
@@ -146,6 +146,15 @@ impl EventLinkedChunk {
146146 Ok(next_pos)
147147 }
148148
149+ /// Replace the gap identified by `gap_identifier` by another gap, keeping
150+ /// its place even when the next chunk is a gap too.
151+ fn rewrite_gap_at(&mut self, gap_identifier: ChunkIdentifier, gap: Gap) -> Result<(), Error> {
152+ let placeholder = self.chunks.replace_gap_at(Vec::new(), gap_identifier)?.identifier();
153+ self.chunks.insert_gap_at(gap, Position::new(placeholder, 0))?;
154+ self.chunks.remove_empty_chunk_at(placeholder)?;
155+ Ok(())
156+ }
157+
149158 /// Remove some events from the linked chunk.
150159 ///
151160 /// If a chunk becomes empty, it's going to be removed.
@@ -361,9 +370,21 @@ impl EventLinkedChunk {
361370 events: &[Event],
362371 ) -> bool {
363372 let first_event_pos = self.events().next().map(|(item_pos, _)| item_pos);
373+ let has_new_gap = new_gap.is_some();
374+ let mut new_gap = new_gap;
364375
365376 // First, insert events.
366- let insert_new_gap_pos = if let Some(gap_id) = prev_gap_id {
377+ let insert_new_gap_pos = if let Some(gap_id) = prev_gap_id
378+ && events.is_empty()
379+ && let Some(gap) = new_gap.take()
380+ {
381+ trace!("rewriting the previous gap in place");
382+
383+ self.rewrite_gap_at(gap_id, gap)
384+ .expect("gap_identifier is a valid gap chunk id we read previously");
385+
386+ None
387+ } else if let Some(gap_id) = prev_gap_id {
367388 // There is a prior gap, let's replace it with the new events!
368389 trace!("replacing previous gap with the back-paginated events");
369390
@@ -397,7 +418,6 @@ impl EventLinkedChunk {
397418 // We only do this when at least one new, non-duplicated event, has been added
398419 // to the chunk. Otherwise it means we've back-paginated all the
399420 // known events.
400- let has_new_gap = new_gap.is_some();
401421 if let Some(new_gap) = new_gap {
402422 if let Some(new_pos) = insert_new_gap_pos {
403423 self.chunks
@@ -748,6 +768,40 @@ mod tests {
748768 }
749769
750770 #[test]
771+ fn test_empty_backwards_page_before_another_gap_keeps_both_gaps() {
772+ let (_, event_0) = new_event("$ev0");
773+ let (_, event_1) = new_event("$ev1");
774+
775+ let mut linked_chunk = EventLinkedChunk::new();
776+
777+ linked_chunk.chunks.push_items_back([event_0]);
778+ linked_chunk.chunks.push_gap_back(Gap { token: "older".to_owned(), history: None });
779+ linked_chunk.chunks.push_gap_back(Gap { token: "newer".to_owned(), history: None });
780+ linked_chunk.chunks.push_items_back([event_1]);
781+
782+ let older_gap_id = linked_chunk
783+ .chunks()
784+ .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier()))
785+ .unwrap();
786+
787+ linked_chunk.push_backwards_pagination_events(
788+ Some(older_gap_id),
789+ Some(Gap { token: "next".to_owned(), history: None }),
790+ &[],
791+ );
792+
793+ let tokens: Vec<_> = linked_chunk
794+ .chunks()
795+ .filter_map(|chunk| match chunk.content() {
796+ ChunkContent::Gap(gap) => Some(gap.token.clone()),
797+ ChunkContent::Items(_) => None,
798+ })
799+ .collect();
800+ assert_eq!(tokens, ["next", "newer"]);
801+ assert_eq!(linked_chunk.events().count(), 2);
802+ }
803+
804+ #[test]
751805 fn test_replace_gap_at_with_no_new_events() {
752806 let (_, event_0) = new_event("$ev0");
753807 let (_, event_1) = new_event("$ev1");