Commit ba8ba5be174f
tarball4 files changed+215-18
@@ -8,7 +8,7 @@ use matrix_sdk_base::{
88
event_cache::{Event, Gap},99
linked_chunk::ChunkContent,1010
};11
-use ruma::{OwnedEventId, OwnedTransactionId, TransactionId};11
+use ruma::{OwnedEventId, OwnedTransactionId, TransactionId, UserId};1212
use tokio::sync::{mpsc, oneshot, watch};1313
1414
use super::{HistoryCommitScope, RoomEventCache, RoomEventCacheInner};@@ -258,16 +258,21 @@ impl RoomEventCache {
258258
&self,259259
mut cancellation: watch::Receiver<bool>,260260
) -> Result<RecentHistoryOutcome> {261
- let (anchor, retained) = {261
+ let room = self.inner.weak_room.get().ok_or(EventCacheError::ClientDropped)?;262
+ let (anchor, retained, anchors) = {262263
let state = self.inner.state.write().await?;264
+ let anchors = Anchors::new(265
+ room.own_user_id(),266
+ state.room_linked_chunk().events().map(|(_, event)| event),267
+ );263268
let retained: HashSet<_> = state264269
.room_linked_chunk()265270
.events()271
+ .filter(|(_, event)| anchors.holds(event))266272
.filter_map(|(_, event)| event.event_id())267273
.collect();268
- (state.newest_retained_event_id().await?, retained)274
+ (state.newest_retained_event_id().await?, retained, anchors)269275
};270
- let room = self.inner.weak_room.get().ok_or(EventCacheError::ClientDropped)?;271276
let mut cursor = None;272277
let mut visited = Vec::new();273278
let mut events = Vec::new();@@ -293,7 +298,10 @@ impl RoomEventCache {
293298
let mut page = response.chunk;294299
if let Some(index) = anchor.as_ref().and_then(|anchor| {295300
page.iter().position(|event| {296
- event.event_id().is_some_and(|id| &id == anchor || retained.contains(&id))301
+ anchors.holds(event)302
+ && event303
+ .event_id()304
+ .is_some_and(|id| &id == anchor || retained.contains(&id))297305
})298306
}) {299307
page.truncate(index);@@ -472,22 +480,27 @@ impl RoomEventCache {
472480
let mut candidate = state.history_candidate().await?;473481
let known: HashSet<_> =474482
candidate.events().filter_map(|(_, event)| event.event_id()).collect();483
+ let anchors = Anchors::new(room.own_user_id(), candidate.events().map(|(_, event)| event));475484
let mut joined = false;476485
let mut missing = Vec::new();477486
for event in response.chunk {478487
match event.event_id() {479
- Some(id) if known.contains(&id) => {488
+ Some(id) if known.contains(&id) && anchors.holds(&event) => {480489
joined = true;481490
break;482491
}483492
_ => missing.push(event),484493
}485494
}486
- let count = missing.len();495
+ let count = missing496
+ .iter()497
+ .filter(|event| event.event_id().is_none_or(|id| !known.contains(&id)))498
+ .count();487499
tracing::debug!(room_id = %self.room_id(), new = count, joined, "history gap probe");488
- if missing.is_empty() {500
+ if count == 0 {489501
return Ok(Some(0));490502
}503
+ anchors.unplace(&mut candidate, &missing);491504
missing.reverse();492505
let gap = (!joined).then(|| response.end).flatten().map(|token| Gap {493506
token,@@ -676,6 +689,7 @@ impl RoomEventCache {
676689
};677690
let (gap, events) = apply_page(678691
&mut candidate,692
+ room.own_user_id(),679693
&gap_id,680694
events,681695
next,@@ -691,6 +705,40 @@ impl RoomEventCache {
691705
}692706
}693707
708
+pub(super) fn from_others(own_user_id: &UserId, event: &Event) -> bool {709
+ event.sender().is_some_and(|sender| sender != own_user_id)710
+}711
+712
+/// Which retained events show where a server page meets retained history. The send queue713
+/// appends our own sends at the live end, ahead of whatever sync has yet to deliver, so ours714
+/// only count while nobody else's event is retained, and a page re-places them otherwise.715
+#[derive(Clone, Copy)]716
+struct Anchors<'a> {717
+ own_user_id: &'a UserId,718
+ ours: bool,719
+}720
+721
+impl<'a> Anchors<'a> {722
+ fn new<'e>(own_user_id: &'a UserId, retained: impl IntoIterator<Item = &'e Event>) -> Self {723
+ let ours = !retained.into_iter().any(|event| from_others(own_user_id, event));724
+ Self { own_user_id, ours }725
+ }726
+727
+ fn holds(&self, event: &Event) -> bool {728
+ self.ours || from_others(self.own_user_id, event)729
+ }730
+731
+ fn unplace(&self, candidate: &mut EventLinkedChunk, page: &[Event]) {732
+ let positions = page733
+ .iter()734
+ .filter(|event| !self.holds(event))735
+ .filter_map(|event| candidate.find_event(&event.event_id()?))736
+ .map(|(position, _)| position)737
+ .collect();738
+ candidate.remove_events_by_position(positions).expect("positions were just found");739
+ }740
+}741
+694742
fn insert_run(695743
candidate: &mut EventLinkedChunk,696744
before: Option<&ruma::EventId>,@@ -743,6 +791,7 @@ fn find_gap(candidate: &EventLinkedChunk, gap_id: &TransactionId) -> Option<Gap>
743791
744792
fn apply_page(745793
candidate: &mut EventLinkedChunk,794
+ own_user_id: &UserId,746795
gap_id: &TransactionId,747796
mut events: Vec<Event>,748797
next: Option<String>,@@ -757,14 +806,20 @@ fn apply_page(
757806
let history = gap.history.as_mut().expect("the gap was checked under the state lock");758807
// `/messages` may never return the boundary itself (filtered, redacted, or ordered apart759808
// from sync arrival), so any event already below the gap proves the page reached it760
- let below: HashSet<_> = candidate809
+ let below_events: Vec<_> = candidate761810
.chunks()762811
.take_while(|chunk| chunk.identifier() != chunk_id)763812
.filter_map(|chunk| match chunk.content() {764
- ChunkContent::Items(items) => Some(items.iter().filter_map(Event::event_id)),813
+ ChunkContent::Items(items) => Some(items.iter()),765814
ChunkContent::Gap(_) => None,766815
})767816
.flatten()817
+ .collect();818
+ let anchors = Anchors::new(own_user_id, below_events.iter().copied());819
+ let below: HashSet<_> = below_events820
+ .into_iter()821
+ .filter(|event| anchors.holds(event))822
+ .filter_map(Event::event_id)768823
.chain(history.older_boundary.clone())769824
.collect();770825
let overlap =@@ -791,6 +846,7 @@ fn apply_page(
791846
if let Some(next) = next {792847
gap.token = next;793848
}849
+ anchors.unplace(candidate, &events);794850
let mut known: HashSet<_> =795851
candidate.events().filter_map(|(_, event)| event.event_id()).collect();796852
events.retain(|event| event.event_id().is_none_or(|id| known.insert(id)));@@ -859,8 +859,25 @@ impl<'a> RoomEventCacheStateLockWriteGuard<'a> {
859859
pub(super) async fn newest_retained_event_id(860860
&self,861861
) -> Result<Option<OwnedEventId>, EventCacheError> {862
+ self.newest_retained_event_id_where(|_| true).await863
+ }864
+865
+ async fn newest_retained_event_id_from_others(866
+ &self,867
+ ) -> Result<Option<OwnedEventId>, EventCacheError> {868
+ let own_user_id = &self.state.own_user_id;869
+ self.newest_retained_event_id_where(|event| super::history::from_others(own_user_id, event))870
+ .await871
+ }872
+873
+ async fn newest_retained_event_id_where(874
+ &self,875
+ keep: impl Fn(&Event) -> bool,876
+ ) -> Result<Option<OwnedEventId>, EventCacheError> {862877
let chunk = &self.state.room_linked_chunk;863
- if let Some(id) = chunk.revents().find_map(|(_, event)| event.event_id()) {878
+ if let Some(id) =879
+ chunk.revents().filter(|(_, event)| keep(event)).find_map(|(_, event)| event.event_id())880
+ {864881
return Ok(Some(id));865882
}866883
let linked_chunk_id = LinkedChunkId::Room(&self.state.room_id);@@ -870,7 +887,8 @@ impl<'a> RoomEventCacheStateLockWriteGuard<'a> {
870887
let mut seen = HashSet::from([first]);871888
while let Some(previous) = self.store.load_previous_chunk(linked_chunk_id, first).await? {872889
if let ChunkContent::Items(events) = &previous.content873
- && let Some(id) = events.iter().rev().find_map(Event::event_id)890
+ && let Some(id) =891
+ events.iter().rev().filter(|event| keep(event)).find_map(Event::event_id)874892
{875893
return Ok(Some(id));876894
}@@ -878,10 +896,7 @@ impl<'a> RoomEventCacheStateLockWriteGuard<'a> {
878896
if !seen.insert(first) {879897
error!(room_id = %self.state.room_id, "stored linked chunk loops back on itself");880898
return Err(EventCacheError::LinkedChunkLoader(881
- lazy_loader::LazyLoaderError::Cycle {882
- new_chunk: first,883
- with_chunk: first,884
- },899
+ lazy_loader::LazyLoaderError::Cycle { new_chunk: first, with_chunk: first },885900
));886901
}887902
}@@ -1071,7 +1086,11 @@ impl<'a> RoomEventCacheStateLockWriteGuard<'a> {
10711086
}10721087
10731088
let retained = newest_retained.is_some();1074
- let older_boundary = timeline.limited.then_some(newest_retained).flatten();1089
+ let older_boundary = if timeline.limited {1090
+ self.newest_retained_event_id_from_others().await?1091
+ } else {1092
+ None1093
+ };10751094
let newer_boundary =10761095
timeline.limited.then(|| events.iter().find_map(Event::event_id)).flatten();10771096
if older_boundary.is_some() && events.iter().any(|event| event.event_id() == older_boundary)@@ -773,3 +773,52 @@ async fn test_a_probe_behind_a_non_limited_sync_brings_back_what_it_skipped() {
773773
.await;774774
assert_eq!(cache.probe_sync_hole().await.unwrap(), Some(0));775775
}776
+777
+#[async_test]778
+async fn test_recent_history_does_not_join_on_our_own_send() {779
+ let server = MatrixMockServer::new().await;780
+ let client = server.client_builder().build().await;781
+ client.event_cache().subscribe().unwrap();782
+ let room_id = room_id!("!sent:example.org");783
+ let room = server.sync_joined_room(&client, room_id).await;784
+ let (cache, _handles) = room.event_cache().await.unwrap();785
+ let f = event_factory(room_id);786
+ let own_user_id = client.user_id().unwrap();787
+ server788
+ .sync_room(789
+ &client,790
+ JoinedRoomBuilder::new(room_id)791
+ .add_timeline_event(f.text_msg("first").event_id(event_id!("$first"))),792
+ )793
+ .await;794
+ server.mock_room_state_encryption().plain().mount().await;795
+ server.mock_room_send().ok(event_id!("$mine")).mock_once().mount().await;796
+ room.send_queue()797
+ .send(ruma::events::room::message::RoomMessageEventContent::text_plain("mine").into())798
+ .await799
+ .unwrap();800
+ tokio::time::timeout(Duration::from_secs(5), async {801
+ while cache.events().await.unwrap().len() < 2 {802
+ tokio::time::sleep(Duration::from_millis(10)).await;803
+ }804
+ })805
+ .await806
+ .unwrap();807
+ server808
+ .mock_room_messages()809
+ .match_limit(50)810
+ .ok(RoomMessagesResponseTemplate::default().end_token("unused").events(vec![811
+ f.text_msg("mine").sender(own_user_id).event_id(event_id!("$mine")),812
+ f.text_msg("theirs").event_id(event_id!("$theirs")),813
+ f.text_msg("first").event_id(event_id!("$first")),814
+ ]))815
+ .mock_once()816
+ .mount()817
+ .await;818
+819
+ let outcome = cache.load_recent_history().await.unwrap();820
+ assert_eq!(outcome.continuity, RecentHistoryContinuity::Joined);821
+ let ids: Vec<_> =822
+ cache.events().await.unwrap().iter().filter_map(|event| event.event_id()).collect();823
+ assert_eq!(ids, [event_id!("$first"), event_id!("$theirs"), event_id!("$mine")]);824
+}@@ -836,8 +836,81 @@ async fn test_cold_open_pagination_keeps_the_order_tracker_in_link_order() {
836836
cache.pagination().run_backwards_once(20).await.unwrap();837837
let ids: Vec<_> =838838
cache.events().await.unwrap().iter().filter_map(|event| event.event_id()).collect();839
+ assert_eq!(ids, vec![event_id!("$y"), event_id!("$z"), event_id!("$a"), event_id!("$live")]);840
+}841
+842
+#[async_test]843
+async fn test_a_gap_after_our_send_fills_what_others_sent_before_it() {844
+ let server = MatrixMockServer::new().await;845
+ let client = server.client_builder().build().await;846
+ client.event_cache().subscribe().unwrap();847
+ let room_id = room_id!("!sent:example.org");848
+ let room = server.sync_joined_room(&client, room_id).await;849
+ let (cache, _handles) = room.event_cache().await.unwrap();850
+ let f = EventFactory::new().room(room_id).sender(user_id!("@alice:example.org"));851
+ let own_user_id = client.user_id().unwrap();852
+ server853
+ .sync_room(854
+ &client,855
+ JoinedRoomBuilder::new(room_id)856
+ .add_timeline_event(f.text_msg("first").event_id(event_id!("$first")))857
+ .set_timeline_prev_batch("old-edge"),858
+ )859
+ .await;860
+ server.mock_room_state_encryption().plain().mount().await;861
+ server.mock_room_send().ok(event_id!("$mine")).mock_once().mount().await;862
+ room.send_queue()863
+ .send(ruma::events::room::message::RoomMessageEventContent::text_plain("mine").into())864
+ .await865
+ .unwrap();866
+ tokio::time::timeout(Duration::from_secs(5), async {867
+ while !cache868
+ .events()869
+ .await870
+ .unwrap()871
+ .iter()872
+ .any(|event| event.event_id().as_deref() == Some(event_id!("$mine")))873
+ {874
+ tokio::time::sleep(Duration::from_millis(10)).await;875
+ }876
+ })877
+ .await878
+ .unwrap();879
+880
+ server881
+ .sync_room(882
+ &client,883
+ JoinedRoomBuilder::new(room_id)884
+ .add_timeline_event(f.text_msg("later").event_id(event_id!("$later")))885
+ .set_timeline_limited()886
+ .set_timeline_prev_batch("before-later"),887
+ )888
+ .await;889
+ let gaps = cache.history_gaps().await.unwrap();890
+ assert_eq!(gaps.len(), 1);891
+ assert_eq!(gaps[0].older_boundary.as_deref(), Some(event_id!("$first")));892
+893
+ server894
+ .mock_room_messages()895
+ .match_from("before-later")896
+ .ok(RoomMessagesResponseTemplate::default().end_token("further").events(vec![897
+ f.text_msg("mine").sender(own_user_id).event_id(event_id!("$mine")),898
+ f.text_msg("theirs").event_id(event_id!("$theirs")),899
+ f.text_msg("first").event_id(event_id!("$first")),900
+ ]))901
+ .mock_once()902
+ .mount()903
+ .await;904
+ let HistoryGapFillOutcome::Updated { gap, .. } =905
+ cache.fill_history_gap_once(gaps[0].id.clone()).await.unwrap()906
+ else {907
+ panic!()908
+ };909
+ assert_eq!(gap.status, HistoryGapStatus::Joined);910
+ let ids: Vec<_> =911
+ cache.events().await.unwrap().iter().filter_map(|event| event.event_id()).collect();839912
assert_eq!(840913
ids,841
- vec![event_id!("$y"), event_id!("$z"), event_id!("$a"), event_id!("$live")]914
+ vec![event_id!("$first"), event_id!("$theirs"), event_id!("$mine"), event_id!("$later")]842915
);843916
}