Commit 417da9e719bc
tarball7 files changed+177-45
@@ -2217,7 +2217,7 @@ dependencies = [
22172217
[[package]]22182218
name = "matrix-sdk"22192219
version = "0.18.0"2220
-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"2220
+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"22212221
dependencies = [22222222
"anymap2",22232223
"aquamarine",@@ -2280,7 +2280,7 @@ dependencies = [
22802280
[[package]]22812281
name = "matrix-sdk-base"22822282
version = "0.18.0"2283
-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"2283
+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"22842284
dependencies = [22852285
"as_variant",22862286
"assert_matches",@@ -2310,7 +2310,7 @@ dependencies = [
23102310
[[package]]23112311
name = "matrix-sdk-common"23122312
version = "0.18.0"2313
-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"2313
+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"23142314
dependencies = [23152315
"eyeball-im",23162316
"futures-core",@@ -2333,7 +2333,7 @@ dependencies = [
23332333
[[package]]23342334
name = "matrix-sdk-crypto"23352335
version = "0.18.0"2336
-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"2336
+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"23372337
dependencies = [23382338
"aes",23392339
"aquamarine",@@ -2374,7 +2374,7 @@ dependencies = [
23742374
[[package]]23752375
name = "matrix-sdk-indexeddb"23762376
version = "0.18.0"2377
-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"2377
+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"23782378
dependencies = [23792379
"async-trait",23802380
"base64",@@ -2406,7 +2406,7 @@ dependencies = [
24062406
[[package]]24072407
name = "matrix-sdk-sqlite"24082408
version = "0.18.0"2409
-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"2409
+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"24102410
dependencies = [24112411
"as_variant",24122412
"async-trait",@@ -2433,7 +2433,7 @@ dependencies = [
24332433
[[package]]24342434
name = "matrix-sdk-store-encryption"24352435
version = "0.18.0"2436
-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"2436
+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"24372437
dependencies = [24382438
"base64",24392439
"blake3",@@ -2454,7 +2454,7 @@ dependencies = [
24542454
[[package]]24552455
name = "matrix-sdk-test"24562456
version = "0.18.0"2457
-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"2457
+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"24582458
dependencies = [24592459
"as_variant",24602460
"ctor",@@ -2477,7 +2477,7 @@ dependencies = [
24772477
[[package]]24782478
name = "matrix-sdk-test-macros"24792479
version = "0.18.0"2480
-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"2480
+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"24812481
dependencies = [24822482
"quote",24832483
"syn 2.0.119",@@ -2486,7 +2486,7 @@ dependencies = [
24862486
[[package]]24872487
name = "matrix-sdk-ui"24882488
version = "0.18.0"2489
-source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd#5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd"2489
+source = "git+ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk?rev=ec026bc9ea2393fd0cce6212b950d91ab6d36097#ec026bc9ea2393fd0cce6212b950d91ab6d36097"24902490
dependencies = [24912491
"as_variant",24922492
"async-rx",@@ -26,11 +26,11 @@ indexeddb = [
2626
futures-util = { version = "0.3.31", default-features = false, features = ["std"] }2727
http = "1"2828
imbl = { version = "6.1.0", default-features = false }29
-matrix-sdk = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd", default-features = false, features = ["e2e-encryption"] }30
-matrix-sdk-base = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd", default-features = false }31
-matrix-sdk-crypto = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd", default-features = false }32
-matrix-sdk-store-encryption = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd", optional = true }33
-matrix-sdk-ui = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd", default-features = false }29
+matrix-sdk = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "ec026bc9ea2393fd0cce6212b950d91ab6d36097", default-features = false, features = ["e2e-encryption"] }30
+matrix-sdk-base = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "ec026bc9ea2393fd0cce6212b950d91ab6d36097", default-features = false }31
+matrix-sdk-crypto = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "ec026bc9ea2393fd0cce6212b950d91ab6d36097", default-features = false }32
+matrix-sdk-store-encryption = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "ec026bc9ea2393fd0cce6212b950d91ab6d36097", optional = true }33
+matrix-sdk-ui = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "ec026bc9ea2393fd0cce6212b950d91ab6d36097", default-features = false }3434
ruma = { version = "0.16.0", default-features = false, features = ["unstable-msc2545", "unstable-msc4095"] }3535
serde = { version = "1.0.219", features = ["derive", "rc"], optional = true }3636
serde_json = { version = "1.0.143", features = ["raw_value"] }@@ -54,8 +54,8 @@ js-sys = { version = "0.3.105", optional = true }
5454
# tantivy 0.26's merger reads Instant::now() whenever debug logging is on, and that panics in5555
# wasm. both caps, since a release_max_level a host sets would otherwise win in release5656
log = { version = "0.4.29", features = ["max_level_info", "release_max_level_info"] }57
-matrix-sdk = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd", default-features = false, features = ["js"] }58
-matrix-sdk-ui = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd", default-features = false, features = ["js"] }57
+matrix-sdk = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "ec026bc9ea2393fd0cce6212b950d91ab6d36097", default-features = false, features = ["js"] }58
+matrix-sdk-ui = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "ec026bc9ea2393fd0cce6212b950d91ab6d36097", default-features = false, features = ["js"] }5959
# the sdk's own wasm reqwest leaves out stream, which the bounded media download needs6060
reqwest = { version = "0.13.1", default-features = false, features = ["stream"] }6161
uuid = { version = "1", features = ["js"] }@@ -70,8 +70,8 @@ web-sys = { version = "0.3.105", optional = true, features = [
7070
] }7171
7272
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]73
-matrix-sdk = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd", default-features = false, features = ["testing"] }74
-matrix-sdk-test = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "5ec7600f0a5f648c6e2a34b5be0a1494a4e2eafd" }73
+matrix-sdk = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "ec026bc9ea2393fd0cce6212b950d91ab6d36097", default-features = false, features = ["testing"] }74
+matrix-sdk-test = { git = "ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk", rev = "ec026bc9ea2393fd0cce6212b950d91ab6d36097" }7575
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread", "signal", "time"] }7676
wiremock = "0.6.5"7777
@@ -103,8 +103,29 @@ impl RepairController {
103103
self.running.is_some()104104
}105105
106
+ /// a room with retained events already shows the live end, and a limited sync has put its107
+ /// own gap there, so only an empty room walks back from the live end. otherwise the newest108
+ /// gap gets one page, so a limited sync that skipped nothing leaves no gap behind106109
pub(super) async fn start_recent(&mut self) {107
- self.start_recent_with(None).await;110
+ let retained = async {111
+ let (cache, _handles) = self.room.event_cache().await?;112
+ if !cache.has_retained_events().await? {113
+ return Ok(None);114
+ }115
+ let gaps = cache.history_gaps().await?;116
+ Ok::<_, EventCacheError>(Some(gaps.into_iter().rev().find(|gap| {117
+ matches!(118
+ gap.status,119
+ HistoryGapStatus::Idle | HistoryGapStatus::Failed { .. }120
+ )121
+ })))122
+ }123
+ .await;124
+ match retained {125
+ Ok(Some(Some(gap))) => self.start_gap_fill(gap.id, None, 1, false).await,126
+ Ok(Some(None)) => {}127
+ Ok(None) | Err(_) => self.start_recent_with(None).await,128
+ }108129
}109130
110131
pub(super) async fn execute(&mut self, command: HistoryRepairCommand, reply: Responder) {@@ -122,7 +143,8 @@ impl RepairController {
122143
}123144
HistoryRepairCommand::RetryRecent { .. } => self.start_recent_with(Some(reply)).await,124145
HistoryRepairCommand::FillGap { gap_id, .. } => {125
- self.start_gap_fill(gap_id, reply).await;146
+ self.start_gap_fill(gap_id, Some(reply), MAX_GAP_PAGES, true)147
+ .await;126148
}127149
}128150
}@@ -169,9 +191,17 @@ impl RepairController {
169191
});170192
}171193
172
- async fn start_gap_fill(&mut self, gap_id: OwnedTransactionId, responder: Responder) {194
+ async fn start_gap_fill(195
+ &mut self,196
+ gap_id: OwnedTransactionId,197
+ responder: Option<Responder>,198
+ pages: u32,199
+ spend_budget: bool,200
+ ) {173201
if self.is_running() {174
- responder.done(Err(room_busy()));202
+ if let Some(responder) = responder {203
+ responder.done(Err(room_busy()));204
+ }175205
return;176206
}177207
let room = self.room.clone();@@ -183,18 +213,24 @@ impl RepairController {
183213
.await184214
.is_err()185215
{186
- responder.done(Err(Error::interrupted()));216
+ if let Some(responder) = responder {217
+ responder.done(Err(Error::interrupted()));218
+ }187219
return;188220
}189221
let (cancel, receiver) = watch::channel(false);190
- let task = spawn(async move { fill_gap(room, progress, receiver).await });222
+ let task =223
+ spawn(async move { fill_gap(room, progress, receiver, pages, spend_budget).await });191224
self.running = Some(Running {192225
task,193226
cancel,194
- responders: vec![PendingResponder {195
- responder,196
- cancel_ack: false,197
- }],227
+ responders: responder228
+ .map(|responder| PendingResponder {229
+ responder,230
+ cancel_ack: false,231
+ })232
+ .into_iter()233
+ .collect(),198234
gap_id: Some(gap_id),199235
closing: false,200236
});@@ -285,11 +321,13 @@ async fn fill_gap(
285321
room: Room,286322
mut report: HistoryRepairReport,287323
mut cancel: watch::Receiver<bool>,324
+ pages: u32,325
+ spend_budget: bool,288326
) -> Finished {289327
let mut committed = false;290328
let stopped = async {291329
let (cache, _handles) = room.event_cache().await.map_err(cache_error)?;292
- for _ in 0..MAX_GAP_PAGES {330
+ for _ in 0..pages {293331
let next = tokio::select! {294332
biased;295333
_ = cancel.changed() => return Ok(Stopped::Cancelled),@@ -339,10 +377,16 @@ async fn fill_gap(
339377
HistoryGapStatus::Failed { message } => {340378
return Err(Error::new(ErrorKind::Network, message));341379
}342
- HistoryGapStatus::Idle => {}380
+ HistoryGapStatus::Idle | HistoryGapStatus::BudgetSpent => {}343381
}344382
}345383
report.stop = Some(HistoryRepairStop::Budget);384
+ if spend_budget {385
+ if let Err(error) = cache.spend_history_budget(report.gap_id.clone()).await {386
+ tracing::debug!(%error, "marking a history gap's spent budget failed");387
+ }388
+ committed = true;389
+ }346390
Ok(Stopped::Done)347391
}348392
.await@@ -77,8 +77,8 @@ pub use model::{
7777
VerificationAction, VerificationFailure, VerificationState, VerificationTarget,7878
};7979
pub use repair_model::{80
- HistoryContinuity, HistoryGap, HistoryGapLifecycle, HistoryRepairCommand, HistoryRepairPhase,81
- HistoryRepairReport, HistoryRepairStop,80
+ HistoryContinuity, HistoryGap, HistoryGapLifecycle, HistoryGapPause, HistoryRepairCommand,81
+ HistoryRepairPhase, HistoryRepairReport, HistoryRepairStop,8282
};8383
pub use ruma;8484
pub use search_model::{@@ -19,9 +19,10 @@ use ruma::{
1919
};2020
2121
use crate::{22
- Change, HistoryGap, HistoryGapLifecycle, MediaId, MembershipChange, Message, MessageKind,23
- MessageMentions, Reaction, ReplyDetails, ReplyReference, SendState, Sender, ThreadSummary,24
- TimelineContent, TimelineDiff, TimelineEvent, TimelineItem, TimelineItemId, UtdCause,22
+ Change, HistoryGap, HistoryGapLifecycle, HistoryGapPause, MediaId, MembershipChange, Message,23
+ MessageKind, MessageMentions, Reaction, ReplyDetails, ReplyReference, SendState, Sender,24
+ ThreadSummary, TimelineContent, TimelineDiff, TimelineEvent, TimelineItem, TimelineItemId,25
+ UtdCause,2526
};2627
2728
use super::link_preview::sender_previews;@@ -75,7 +76,18 @@ impl Normaliser<'_> {
7576
HistoryGapStatus::Idle | HistoryGapStatus::Joined => {7677
(HistoryGapLifecycle::Idle, None)7778
}78
- HistoryGapStatus::Paused => (HistoryGapLifecycle::Paused, None),79
+ HistoryGapStatus::Paused => (80
+ HistoryGapLifecycle::Paused {81
+ reason: HistoryGapPause::CursorStalled,82
+ },83
+ None,84
+ ),85
+ HistoryGapStatus::BudgetSpent => (86
+ HistoryGapLifecycle::Paused {87
+ reason: HistoryGapPause::Budget,88
+ },89
+ None,90
+ ),7991
HistoryGapStatus::Failed { message } => {8092
(HistoryGapLifecycle::Failed, Some(message.clone()))8193
}@@ -107,7 +119,8 @@ impl Normaliser<'_> {
107119
timestamp_ms: embedded.timestamp.get().into(),108120
},109121
TimelineDetails::Unavailable => {110
- self.missing_replies.extend(event.event_id().map(ToOwned::to_owned));122
+ self.missing_replies123
+ .extend(event.event_id().map(ToOwned::to_owned));111124
ReplyDetails::Pending112125
}113126
TimelineDetails::Pending => ReplyDetails::Pending,@@ -34,12 +34,22 @@ pub struct HistoryGap {
3434
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]3535
pub enum HistoryGapLifecycle {3636
Idle,37
- Paused,37
+ /// the last fill stopped short of joining, and another one carries on from there38
+ Paused {39
+ reason: HistoryGapPause,40
+ },3841
Failed,3942
/// accessible history ends before the older section, so this interval stays4043
Unavailable,4144
}4245
46
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]47
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]48
+pub enum HistoryGapPause {49
+ CursorStalled,50
+ Budget,51
+}52
+4353
#[derive(Clone, Debug, PartialEq, Eq)]4454
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]4555
pub enum HistoryContinuity {@@ -181,7 +181,7 @@ fn gap_in(items: &imbl::Vector<TimelineItem>) -> Option<HistoryGap> {
181181
}182182
183183
#[tokio::test]184
-async fn automatic_recent_load_is_bounded_and_keeps_cached_history_behind_a_gap() {184
+async fn a_recent_load_is_bounded_and_keeps_cached_history_behind_a_gap() {185185
let room_id = room_id!("!recent-repair:localhost");186186
let old_id = event_id!("$old:localhost");187187
let new_ids = [@@ -216,6 +216,10 @@ async fn automatic_recent_load_is_bounded_and_keeps_cached_history_behind_a_gap(
216216
}217217
cache_event_before_open(&room, room_id, old_id, "retained old event").await;218218
open_room(&mut room, room_id).await;219
+ room.handle220
+ .retry_recent_history(room_id.to_owned())221
+ .await222
+ .unwrap();219223
timeline_until(&mut room, |items| {220224
gap_in(items).is_some()221225
&& has_event(items, old_id)@@ -342,6 +346,10 @@ async fn fill_gap_returns_after_the_mirror_contains_the_committed_rows() {
342346
)343347
.await;344348
handle.open_room(room_id.to_owned()).await.unwrap();349
+ handle350
+ .retry_recent_history(room_id.to_owned())351
+ .await352
+ .unwrap();345353
let timeline_id = TimelineId::from(room_id.to_owned());346354
let gap = tokio::time::timeout(Duration::from_secs(5), async {347355
loop {@@ -452,6 +460,10 @@ async fn cancelling_a_gap_fill_keeps_committed_progress_for_retry() {
452460
.await;453461
cache_event_before_open(&room, room_id, old_id, "old cached").await;454462
open_room(&mut room, room_id).await;463
+ room.handle464
+ .retry_recent_history(room_id.to_owned())465
+ .await466
+ .unwrap();455467
timeline_until(&mut room, |items| gap_in(items).is_some()).await;456468
let gap = gap_in(&room.items).unwrap();457469
assert_eq!(@@ -548,7 +560,7 @@ async fn cancelling_a_gap_fill_keeps_committed_progress_for_retry() {
548560
}549561
550562
#[tokio::test]551
-async fn failed_automatic_recent_load_is_public_and_keeps_the_cached_timeline() {563
+async fn failed_recent_load_is_public_and_keeps_the_cached_timeline() {552564
let room_id = room_id!("!failed-recent:localhost");553565
let old_id = event_id!("$failed-old:localhost");554566
let mut room = start_room(room_id).await;@@ -563,6 +575,12 @@ async fn failed_automatic_recent_load_is_public_and_keeps_the_cached_timeline()
563575
.await;564576
cache_event_before_open(&room, room_id, old_id, "kept event").await;565577
open_room(&mut room, room_id).await;578
+ assert!(579
+ room.handle580
+ .retry_recent_history(room_id.to_owned())581
+ .await582
+ .is_err()583
+ );566584
let state = wait_for_recent(&mut room, room_id).await;567585
assert!(matches!(state, RecentHistoryState::Failed(_)));568586
shut_down(&room.handle, &mut room.events, room.running).await;@@ -653,12 +671,59 @@ async fn a_reply_whose_target_is_not_loaded_fetches_it() {
653671
timeline_until(&mut room, |items| {654672
items.iter().any(|item| {655673
matches!(item, TimelineItem::Event(event)656
- if event.event_id.as_deref() == Some(reply_id)657
- && event.reply_to.as_ref().is_some_and(|reply| {658
- matches!(reply.details, macltok::ReplyDetails::Ready { .. })659
- }))674
+ if event.event_id.as_deref() == Some(reply_id)675
+ && event.reply_to.as_ref().is_some_and(|reply| {676
+ matches!(reply.details, macltok::ReplyDetails::Ready { .. })677
+ }))660678
})661679
})662680
.await;663681
shut_down(&room.handle, &mut room.events, room.running).await;664682
}683
+684
+#[tokio::test]685
+async fn opening_a_retained_room_only_probes_its_newest_gap() {686
+ let room_id = room_id!("!probe:localhost");687
+ let old_id = event_id!("$probe-old:localhost");688
+ let live_id = event_id!("$probe-live:localhost");689
+ let mut room = start_room(room_id).await;690
+ cache_event_before_open(&room, room_id, old_id, "retained").await;691
+ room.server692
+ .sync_room(693
+ &room.sync_client,694
+ JoinedRoomBuilder::new(room_id)695
+ .add_timeline_event(696
+ EventFactory::new()697
+ .text_msg("live")698
+ .event_id(live_id)699
+ .sender(&ALICE),700
+ )701
+ .set_timeline_limited()702
+ .set_timeline_prev_batch("sync-edge"),703
+ )704
+ .await;705
+ room.server706
+ .mock_room_messages()707
+ .match_from("sync-edge")708
+ .ok(RoomMessagesResponseTemplate::default()709
+ .end_token("further")710
+ .events(vec![history_event(room_id, old_id, "retained")]))711
+ .mock_once()712
+ .mount()713
+ .await;714
+ open_room(&mut room, room_id).await;715
+ let report = wait_for_settled_fill(&mut room).await;716
+ assert_eq!(report.pages, 1);717
+ assert_eq!(report.stop, None);718
+ timeline_until(&mut room, |items| {719
+ gap_in(items).is_none() && has_event(items, old_id) && has_event(items, live_id)720
+ })721
+ .await;722
+ let requests = room.server.received_requests().await.unwrap();723
+ let messages = requests724
+ .iter()725
+ .filter(|request| request.url.path().ends_with("/messages"))726
+ .count();727
+ assert_eq!(messages, 1);728
+ shut_down(&room.handle, &mut room.events, room.running).await;729
+}