Commit 9ef2e9aaf533
tarball3 files changed+174-29
@@ -11,7 +11,7 @@ use matrix_sdk_base::{
1111
use ruma::{OwnedEventId, OwnedTransactionId, TransactionId};1212
use tokio::sync::{mpsc, oneshot, watch};1313
14
-use super::{RoomEventCache, RoomEventCacheInner};14
+use super::{HistoryCommitScope, RoomEventCache, RoomEventCacheInner};1515
use crate::{1616
event_cache::{EventCacheError, Result, caches::event_linked_chunk::EventLinkedChunk},1717
room::MessagesOptions,@@ -277,7 +277,6 @@ impl RoomEventCache {
277277
{278278
let mut state = self.inner.state.write().await?;279279
let mut candidate = state.history_candidate().await?;280
- let baseline_count = candidate.events().count();281280
let mut known = HashSet::new();282281
let mut arrivals = Vec::new();283282
let mut leading = Vec::new();@@ -378,7 +377,7 @@ impl RoomEventCache {
378377
};379378
380379
inserted.reverse();381
- state.commit_history_candidate(candidate, inserted, baseline_count).await?;380
+ state.commit_history_candidate(candidate, inserted, HistoryCommitScope::Loaded).await?;382381
Ok(RecentHistoryOutcome { pages, events: raw_count, continuity })383382
}384383
}@@ -397,8 +396,7 @@ impl RoomEventCache {
397396
if !gaps(&candidate).is_empty()398397
&& candidate.chunks().count() != state.room_linked_chunk().chunks().count()399398
{400
- let baseline_count = candidate.events().count();401
- state.commit_history_candidate(candidate, Vec::new(), baseline_count).await?;399
+ state.commit_history_candidate(candidate, Vec::new(), HistoryCommitScope::All).await?;402400
}403401
Ok(())404402
}@@ -436,7 +434,6 @@ impl RoomEventCache {
436434
{437435
let mut state = self.inner.state.write().await?;438436
let mut candidate = state.history_candidate().await?;439
- let baseline_count = candidate.events().count();440437
let older_boundary = candidate.revents().find_map(|(_, event)| event.event_id());441438
let mut known: HashSet<_> =442439
candidate.events().filter_map(|(_, event)| event.event_id()).collect();@@ -464,7 +461,7 @@ impl RoomEventCache {
464461
None465462
};466463
candidate.push_live_events(gap, &events);467
- state.commit_history_candidate(candidate, events, baseline_count).await?;464
+ state.commit_history_candidate(candidate, events, HistoryCommitScope::Loaded).await?;468465
Ok(history)469466
}470467
}@@ -513,7 +510,6 @@ impl RoomEventCache {
513510
{514511
let mut state = self.inner.state.write().await?;515512
let mut candidate = state.history_candidate().await?;516
- let baseline_count = candidate.events().count();517513
let Some(current) = find_gap(&candidate, &gap_id) else {518514
return Ok(HistoryGapFillOutcome::NotFound);519515
};@@ -537,7 +533,7 @@ impl RoomEventCache {
537533
failure.as_ref().map(ToString::to_string),538534
);539535
let count = events.len();540
- state.commit_history_candidate(candidate, events, baseline_count).await?;536
+ state.commit_history_candidate(candidate, events, HistoryCommitScope::Loaded).await?;541537
if let Some(error) = failure {542538
return Err(EventCacheError::PaginationError(std::sync::Arc::new(error)));543539
}@@ -41,6 +41,7 @@ use ruma::{
4141
events::{AnyRoomAccountDataEvent, AnySyncEphemeralRoomEvent, relation::RelationType},4242
serde::Raw,4343
};44
+use state::HistoryCommitScope;4445
pub(super) use state::{LockedRoomEventCacheState, RoomEventCacheStateLockWriteGuard};4546
pub use subscriber::RoomEventCacheSubscriber;4647
use tokio::sync::{Notify, broadcast::Receiver, mpsc};@@ -1970,6 +1971,76 @@ mod timed_tests {
19701971
}19711972
19721973
#[async_test]1974
+ async fn test_history_commit_after_shrink_stays_within_the_loaded_window() {1975
+ let room_id = room_id!("!shrunk-window:saucisse.bzh");1976
+ let client = MockClientBuilder::new(None).build().await;1977
+ let event_cache = client.event_cache();1978
+ event_cache.subscribe().unwrap();1979
+ client.base_client().get_or_create_room(room_id, RoomState::Joined);1980
+ let room = client.get_room(room_id).unwrap();1981
+ let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();1982
+ let f = EventFactory::new().room(room_id).sender(*ALICE);1983
+ let old_id = event_id!("$window-old");1984
+ let new_id = event_id!("$window-new");1985
+ let newer_id = event_id!("$window-newer");1986
+1987
+ room_event_cache1988
+ .handle_joined_room_update(JoinedRoomUpdate {1989
+ timeline: Timeline {1990
+ events: vec![f.text_msg("old").event_id(old_id).into_event()],1991
+ ..Default::default()1992
+ },1993
+ ..Default::default()1994
+ })1995
+ .await1996
+ .unwrap();1997
+ room_event_cache1998
+ .handle_joined_room_update(JoinedRoomUpdate {1999
+ timeline: Timeline {2000
+ limited: true,2001
+ prev_batch: Some("new".to_owned()),2002
+ events: vec![f.text_msg("new").event_id(new_id).into_event()],2003
+ },2004
+ ..Default::default()2005
+ })2006
+ .await2007
+ .unwrap();2008
+ {2009
+ let mut state = room_event_cache.inner.state.write().await.unwrap();2010
+ state.shrink_to_last_chunk().await.unwrap();2011
+ let _ = state.room_linked_chunk_mut().updates_as_vector_diffs();2012
+ }2013
+2014
+ let (events, mut subscriber) = room_event_cache.subscribe().await.unwrap();2015
+ let ids: Vec<_> = events.iter().filter_map(|event| event.event_id()).collect();2016
+ assert_eq!(ids, [new_id]);2017
+2018
+ room_event_cache2019
+ .append_history_section(2020
+ vec![f.text_msg("newer").event_id(newer_id).into_event()],2021
+ Some("newer".to_owned()),2022
+ )2023
+ .await2024
+ .unwrap();2025
+2026
+ assert_let_timeout!(2027
+ Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =2028
+ subscriber.recv()2029
+ );2030
+ assert_matches!(diffs.as_slice(), [VectorDiff::Insert { index: 1, value }] => {2031
+ assert_eq!(value.event_id().as_deref(), Some(newer_id));2032
+ });2033
+ let ids: Vec<_> = room_event_cache2034
+ .events()2035
+ .await2036
+ .unwrap()2037
+ .iter()2038
+ .filter_map(|event| event.event_id())2039
+ .collect();2040
+ assert_eq!(ids, [new_id, newer_id]);2041
+ }2042
+2043
+ #[async_test]19732044
async fn test_limited_sync_after_shrink_to_a_gap_keeps_its_discontinuity() {19742045
let room_id = room_id!("!shrunk-history:saucisse.bzh");19752046
let client = MockClientBuilder::new(None).build().await;@@ -30,8 +30,8 @@ use matrix_sdk_base::{
3030
store::{EventCacheStoreLock, EventCacheStoreLockGuard, EventCacheStoreLockState},3131
},3232
linked_chunk::{33
- ChunkContent, ChunkMetadata, LinkedChunkId, OwnedLinkedChunkId, Position, Update,34
- lazy_loader,33
+ ChunkContent, ChunkIdentifier, ChunkMetadata, LinkedChunkId, OwnedLinkedChunkId, Position,34
+ Update, lazy_loader,3535
},3636
serde_helpers::{extract_edit_target, extract_thread_root},3737
sync::Timeline,@@ -775,25 +775,36 @@ impl<'a> RoomEventCacheStateLockWriteGuard<'a> {
775775
}776776
777777
pub(super) async fn history_candidate(&self) -> Result<EventLinkedChunk, EventCacheError> {778
+ self.load_from_store(None).await779
+ }780
+781
+ /// Loads stored chunks from the newest back to `first`, or all of them.782
+ async fn load_from_store(783
+ &self,784
+ first: Option<ChunkIdentifier>,785
+ ) -> Result<EventLinkedChunk, EventCacheError> {778786
let linked_chunk_id = LinkedChunkId::Room(&self.state.room_id);779787
let (last, generator) = self.store.load_last_chunk(linked_chunk_id).await?;780788
let metadata = self.store.load_all_chunks_metadata(linked_chunk_id).await?;781
- let mut candidate = EventLinkedChunk::with_initial_linked_chunk(789
+ let mut loaded = EventLinkedChunk::with_initial_linked_chunk(782790
lazy_loader::from_last_chunk(last, generator)?,783791
(!metadata.is_empty()).then_some(metadata),784792
);785793
loop {786
- let first =787
- candidate.chunks().next().expect("a linked chunk is never empty").identifier();788
- let Some(previous) = self.store.load_previous_chunk(linked_chunk_id, first).await?794
+ let current =795
+ loaded.chunks().next().expect("a linked chunk is never empty").identifier();796
+ if Some(current) == first {797
+ break;798
+ }799
+ let Some(previous) = self.store.load_previous_chunk(linked_chunk_id, current).await?789800
else {790801
break;791802
};792
- candidate.insert_new_chunk_as_first(previous)?;803
+ loaded.insert_new_chunk_as_first(previous)?;793804
}794
- let _ = candidate.store_updates().take();795
- let _ = candidate.updates_as_vector_diffs();796
- Ok(candidate)805
+ let _ = loaded.store_updates().take();806
+ let _ = loaded.updates_as_vector_diffs();807
+ Ok(loaded)797808
}798809
799810
pub(crate) fn history_gap_positions(&self) -> Vec<super::HistoryGapPosition> {@@ -825,20 +836,33 @@ impl<'a> RoomEventCacheStateLockWriteGuard<'a> {
825836
&mut self,826837
mut candidate: EventLinkedChunk,827838
events: Vec<Event>,828
- baseline_count: usize,839
+ scope: HistoryCommitScope,829840
) -> Result<(), EventCacheError> {830
- let use_incremental = self.state.room_linked_chunk.events().count() == baseline_count;831
- self.send_updates_to_store(candidate.store_updates().take()).await?;832
- self.state.room_linked_chunk = candidate;841
+ let previous: Vec<_> =842
+ self.state.room_linked_chunk.events().map(|(_, event)| event.event_id()).collect();843
+ let mut diffs = match scope {844
+ HistoryCommitScope::Loaded => {845
+ let start = self.loaded_window_start(&candidate).await?;846
+ self.send_updates_to_store(candidate.store_updates().take()).await?;847
+ self.state.room_linked_chunk = self.load_from_store(start).await?;848
+ insertions_from(&previous, &self.state.room_linked_chunk)849
+ }850
+ HistoryCommitScope::All => {851
+ self.send_updates_to_store(candidate.store_updates().take()).await?;852
+ let _ = candidate.updates_as_vector_diffs();853
+ self.state.room_linked_chunk = candidate;854
+ None855
+ }856
+ }857
+ .unwrap_or_else(|| {858
+ let values =859
+ self.state.room_linked_chunk.events().map(|(_, event)| event.clone()).collect();860
+ vec![VectorDiff::Reset { values }]861
+ });833862
self.reset_pagination_status(false);834863
let result =835864
self.post_process_new_events(events, PostProcessingOrigin::Backpagination, None).await;836
- let mut diffs = self.state.room_linked_chunk.updates_as_vector_diffs();837
- if !use_incremental {838
- let values =839
- self.state.room_linked_chunk.events().map(|(_, event)| event.clone()).collect();840
- diffs = vec![VectorDiff::Reset { values }];841
- }865
+ diffs.extend(self.state.room_linked_chunk.updates_as_vector_diffs());842866
self.state.update_sender.send(843867
RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {844868
diffs,@@ -850,6 +874,34 @@ impl<'a> RoomEventCacheStateLockWriteGuard<'a> {
850874
result851875
}852876
877
+ /// The first chunk of `candidate` that belongs to the window memory already878
+ /// loaded. A chunk created by the operation joins the window of the stored879
+ /// chunk after it, so a page filled into a gap right before the window shows,880
+ /// while history further back stays unloaded.881
+ async fn loaded_window_start(882
+ &self,883
+ candidate: &EventLinkedChunk,884
+ ) -> Result<Option<ChunkIdentifier>, EventCacheError> {885
+ let stored: HashSet<_> = self886
+ .store887
+ .load_all_chunks_metadata(LinkedChunkId::Room(&self.state.room_id))888
+ .await?889
+ .into_iter()890
+ .map(|metadata| metadata.identifier)891
+ .collect();892
+ let loaded: HashSet<_> =893
+ self.state.room_linked_chunk.chunks().map(|chunk| chunk.identifier()).collect();894
+ let mut start = None;895
+ for chunk in candidate.rchunks() {896
+ let identifier = chunk.identifier();897
+ if stored.contains(&identifier) && !loaded.contains(&identifier) {898
+ break;899
+ }900
+ start = Some(identifier);901
+ }902
+ Ok(start)903
+ }904
+853905
fn reset_pagination_status(&self, hit_timeline_start: bool) {854906
let mut status = self.state.pagination_status.write();855907
self.state.generation.fetch_add(1, Ordering::SeqCst);@@ -1523,6 +1575,32 @@ impl<'a> RoomEventCacheStateLockWriteGuard<'a> {
15231575
}15241576
}15251577
1578
+/// How much of the committed history ends up in memory.1579
+pub(super) enum HistoryCommitScope {1580
+ /// Keep the window memory already held, plus pages added right next to it.1581
+ Loaded,1582
+ /// Load every stored section, as before attaching a fresh subscriber.1583
+ All,1584
+}1585
+1586
+/// Insert diffs turning `previous` into the events now loaded, or `None` when1587
+/// the loaded events no longer extend it, which calls for a reset.1588
+fn insertions_from(1589
+ previous: &[Option<OwnedEventId>],1590
+ loaded: &EventLinkedChunk,1591
+) -> Option<Vec<VectorDiff<Event>>> {1592
+ let mut remaining = previous.iter().peekable();1593
+ let mut diffs = Vec::new();1594
+ for (index, (_, event)) in loaded.events().enumerate() {1595
+ if remaining.peek().is_some_and(|id| **id == event.event_id()) {1596
+ remaining.next();1597
+ } else {1598
+ diffs.push(VectorDiff::Insert { index, value: event.clone() });1599
+ }1600
+ }1601
+ remaining.next().is_none().then_some(diffs)1602
+}1603
+15261604
/// Extract a valid read receipt event from the ephemeral events, if15271605
/// available.15281606
fn extract_read_receipt(