atagen

matrix-rust-sdk

public
Star0
Watch0

Commit 0371afd50876

tarball

atagen <boss@atagen.co>2026-10-27 04:05 UTC

2 files changed+74-2
event-cache: load history candidates with chunk metadata in link order

load_from_store handed load_all_chunks_metadata straight to the order tracker, which needs link order, while stores return their own order (sqlite by id). Once back-pagination puts a newer chunk id at the head the two diverge, so every history candidate, Loaded reload and cold-open swap installed a tracker with the wrong front. Debug builds hit the as_vector debug_assert, and release builds corrupted it into a VecDeque out-of-bounds panic. It now goes through load_linked_chunk_metadata like the initial load. That also fixes test_relations_ordering.
Modified file: crates/matrix-sdk/src/event_cache/caches/room/state.rs+3 additions−2 deletions
@@ -824,10 +824,11 @@ impl<'a> RoomEventCacheStateLockWriteGuard<'a> {
824824 ) -> Result<EventLinkedChunk, EventCacheError> {
825825 let linked_chunk_id = LinkedChunkId::Room(&self.state.room_id);
826826 let (last, generator) = self.store.load_last_chunk(linked_chunk_id).await?;
827- let metadata = self.store.load_all_chunks_metadata(linked_chunk_id).await?;
827+ // the order tracker needs link order, and stores hand metadata back in their own order
828+ let metadata = load_linked_chunk_metadata(&self.store, linked_chunk_id).await?;
828829 let mut loaded = EventLinkedChunk::with_initial_linked_chunk(
829830 lazy_loader::from_last_chunk(last, generator)?,
830- (!metadata.is_empty()).then_some(metadata),
831+ metadata,
831832 );
832833 loop {
833834 let current =
Modified file: crates/matrix-sdk/tests/segmented_history.rs+71 additions
@@ -770,3 +770,74 @@ async fn test_a_spent_budget_marks_only_an_idle_gap_and_a_fill_clears_it() {
770770 };
771771 assert_eq!(filled.status, HistoryGapStatus::Idle);
772772 }
773+
774+#[async_test]
775+async fn test_cold_open_pagination_keeps_the_order_tracker_in_link_order() {
776+ let server = MatrixMockServer::new().await;
777+ let config = StoreConfig::new(CrossProcessLockConfig::multi_process("cold-open-order"))
778+ .state_store(matrix_sdk_base::store::MemoryStore::new())
779+ .event_cache_store(Arc::new(MemoryStore::new()));
780+ let room_id = room_id!("!history:example.org");
781+ let f = EventFactory::new().room(room_id).sender(user_id!("@alice:example.org"));
782+ {
783+ let client = server
784+ .client_builder()
785+ .on_builder(|builder| builder.store_config(config.clone()))
786+ .build()
787+ .await;
788+ client.event_cache().subscribe().unwrap();
789+ let room = server.sync_joined_room(&client, room_id).await;
790+ let (cache, _handles) = room.event_cache().await.unwrap();
791+ server
792+ .sync_room(
793+ &client,
794+ JoinedRoomBuilder::new(room_id)
795+ .add_timeline_event(f.text_msg("a").event_id(event_id!("$a")))
796+ .set_timeline_prev_batch("t1"),
797+ )
798+ .await;
799+ server
800+ .mock_room_messages()
801+ .match_from("t1")
802+ .ok(RoomMessagesResponseTemplate::default()
803+ .end_token("t2")
804+ .events(vec![f.text_msg("z").event_id(event_id!("$z"))]))
805+ .mock_once()
806+ .mount()
807+ .await;
808+ cache.pagination().run_backwards_once(20).await.unwrap();
809+ server
810+ .sync_room(
811+ &client,
812+ JoinedRoomBuilder::new(room_id)
813+ .add_timeline_event(f.text_msg("live").event_id(event_id!("$live")))
814+ .set_timeline_limited()
815+ .set_timeline_prev_batch("t-live"),
816+ )
817+ .await;
818+ assert_eq!(cache.history_gaps().await.unwrap().len(), 1);
819+ }
820+ let client =
821+ server.client_builder().on_builder(|builder| builder.store_config(config)).build().await;
822+ client.event_cache().subscribe().unwrap();
823+ let room = client.get_room(room_id).unwrap();
824+ let (cache, _handles) = room.event_cache().await.unwrap();
825+ cache.load_retained_history().await.unwrap();
826+ let (_events, _gaps, _subscriber) = cache.subscribe_with_history().await.unwrap();
827+ server
828+ .mock_room_messages()
829+ .match_from("t2")
830+ .ok(RoomMessagesResponseTemplate::default()
831+ .end_token("t3")
832+ .events(vec![f.text_msg("y").event_id(event_id!("$y"))]))
833+ .mock_once()
834+ .mount()
835+ .await;
836+ cache.pagination().run_backwards_once(20).await.unwrap();
837+ let ids: Vec<_> =
838+ cache.events().await.unwrap().iter().filter_map(|event| event.event_id()).collect();
839+ assert_eq!(
840+ ids,
841+ vec![event_id!("$y"), event_id!("$z"), event_id!("$a"), event_id!("$live")]
842+ );
843+}