Commit b04108dd8339
tarball2 files changed+18-333
@@ -30,7 +30,7 @@ use matrix_sdk::{
3030
event_cache::PaginationStatus,3131
test_utils::mocks::{MatrixMockServer, RoomMessagesResponseTemplate},3232
};33
-use matrix_sdk_test::{ALICE, BOB, JoinedRoomBuilder, async_test, event_factory::EventFactory};33
+use matrix_sdk_test::{ALICE, JoinedRoomBuilder, async_test, event_factory::EventFactory};3434
use matrix_sdk_ui::timeline::{AnyOtherStateEventContentChange, RoomExt, TimelineItemContent};3535
use ruma::{3636
EventId, event_id,@@ -538,163 +538,6 @@ async fn test_dedup_pagination() {
538538
server.server().verify().await;539539
}540540
541
-#[async_test]542
-async fn test_timeline_reset_while_paginating() {543
- let room_id = room_id!("!a98sd12bjh:example.org");544
- let server = MatrixMockServer::new().await;545
- let client = server.client_builder().build().await;546
-547
- let f = EventFactory::new();548
- let room = server.sync_joined_room(&client, room_id).await;549
- let timeline = Arc::new(room.timeline().await.unwrap());550
-551
- let alice_event = f.text_msg("live event!").sender(&ALICE).room(room_id).into_raw_timeline();552
- server553
- .mock_sync()554
- .ok(|b| {555
- b.add_joined_room(556
- JoinedRoomBuilder::new(room_id)557
- .add_timeline_event(alice_event.clone().cast())558
- .set_timeline_prev_batch("pagination_1".to_owned())559
- .set_timeline_limited(),560
- );561
- })562
- .mock_once()563
- .mount()564
- .await;565
- client.sync_once(Default::default()).await.unwrap();566
-567
- // Next sync response will respond with pagination_2 token and limited568
- // response, resetting the timeline.569
- let sync_mock_guard = server570
- .mock_sync()571
- .ok(|b| {572
- b.add_joined_room(573
- JoinedRoomBuilder::new(room_id)574
- .add_timeline_event(f.text_msg("new live event.").sender(&BOB))575
- .set_timeline_prev_batch("pagination_2".to_owned())576
- .set_timeline_limited(),577
- );578
- })579
- .mock_once()580
- .mount_as_scoped()581
- .await;582
-583
- // The pagination with the first token will be hit twice:584
- // - first, before the sync response comes, then the gap is stored in the cache.585
- // - second, after all other gaps have been resolved, we get back to resolving586
- // this one.587
- server588
- .mock_room_messages()589
- .match_from("pagination_1")590
- .respond_with(591
- ResponseTemplate::new(200)592
- .set_body_json(json!({593
- "chunk": [],594
- "start": "pagination_1",595
- "end": "pagination_3",596
- }))597
- // Make sure the concurrent sync request returns first598
- .set_delay(Duration::from_millis(200)),599
- )600
- .expect(2)601
- .named("pagination_1")602
- .mount()603
- .await;604
-605
- // The pagination with the second token must return Alice's event, to be606
- // consistent with the room's timeline (Alice's event was observed before607
- // Bob's event).608
- server609
- .mock_room_messages()610
- .match_from("pagination_2")611
- .respond_with(ResponseTemplate::new(200).set_body_json(json!({612
- "chunk": [alice_event],613
- "start": "pagination_2",614
- })))615
- .expect(1)616
- .named("pagination_2")617
- .mount()618
- .await;619
-620
- // pagination with third token621
- server622
- .mock_room_messages()623
- .match_from("pagination_3")624
- .respond_with(ResponseTemplate::new(200).set_body_json(json!({625
- "chunk": [],626
- "start": "pagination_3",627
- })))628
- .expect(1)629
- .named("pagination_3")630
- .mount()631
- .await;632
-633
- let (_, mut back_pagination_status) = timeline.live_back_pagination_status().await.unwrap();634
-635
- let paginate = async {636
- let mut hit_start;637
- loop {638
- hit_start = timeline.paginate_backwards(10).await.unwrap();639
- if hit_start {640
- break;641
- }642
- }643
- hit_start644
- };645
-646
- let observe_paginating = async {647
- let mut seen_paginating = false;648
- let mut seen_idle_no_start = false;649
-650
- // Observe paginating updates: we want to make sure we see at least once651
- // Paginating, and that it settles with Idle.652
- while let Ok(update) =653
- timeout(Duration::from_millis(500), back_pagination_status.next()).await654
- {655
- match update {656
- Some(state) => {657
- if state == PaginationStatus::Paginating {658
- seen_paginating = true;659
- }660
- if matches!(state, PaginationStatus::Idle { hit_timeline_start: false }) {661
- seen_idle_no_start = true;662
- }663
- }664
- None => break,665
- }666
- }667
-668
- assert!(seen_paginating);669
- assert!(seen_idle_no_start);670
-671
- let (status, _) = timeline.live_back_pagination_status().await.unwrap();672
-673
- // Timeline start reached because second pagination response contains no end674
- // field.675
- assert_eq!(status, PaginationStatus::Idle { hit_timeline_start: true });676
- };677
-678
- let sync = async {679
- client.sync_once(Default::default()).await.unwrap();680
- };681
-682
- let (hit_start, _, _) =683
- timeout(Duration::from_secs(5), join3(paginate, observe_paginating, sync)).await.unwrap();684
-685
- // Timeline start reached because second pagination response contains no end686
- // field.687
- assert!(hit_start);688
-689
- // No events in back-pagination responses, start of timeline + date divider +690
- // all events from the previous syncs are present.691
- assert_eq!(timeline.items().await.len(), 4);692
-693
- // Make sure both pagination mocks were called.694
- drop(sync_mock_guard);695
- server.server().verify().await;696
-}697
-698541
pub static ROOM_MESSAGES_BATCH_1: LazyLock<JsonValue> = LazyLock::new(|| {699542
json!({700543
"chunk": [@@ -9,8 +9,8 @@ use matrix_sdk::{
99
assert_let_timeout, assert_next_matches_with_timeout,1010
deserialized_responses::TimelineEvent,1111
event_cache::{12
- BackPaginationOutcome, EventCacheError, PaginationStatus, RoomEventCacheUpdate,13
- TimelineVectorDiffs,12
+ BackPaginationOutcome, EventCacheError, HistoryGapFillOutcome, PaginationStatus,13
+ RoomEventCacheUpdate, TimelineVectorDiffs,1414
},1515
linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update},1616
store::StoreConfig,@@ -567,134 +567,6 @@ async fn test_backpaginate_many_times_with_one_iteration() {
567567
}568568
569569
#[async_test]570
-async fn test_reset_while_backpaginating() {571
- let server = MatrixMockServer::new().await;572
- let client = server.client_builder().build().await;573
-574
- let event_cache = client.event_cache();575
-576
- // Immediately subscribe the event cache to sync updates.577
- event_cache.subscribe().unwrap();578
-579
- // If I sync and get informed I've joined The Room, and get a previous batch580
- // token,581
- let room_id = room_id!("!omelette:fromage.fr");582
-583
- let f = EventFactory::new().room(room_id).sender(user_id!("@a:b.c"));584
-585
- server586
- .sync_room(587
- &client,588
- JoinedRoomBuilder::new(room_id)589
- // Note to self: a timeline must have at least single event to be properly590
- // serialized.591
- .add_timeline_event(f.text_msg("heyo").into_raw_sync())592
- .set_timeline_prev_batch("first_backpagination".to_owned())593
- .set_timeline_limited(),594
- )595
- .await;596
-597
- let (room_event_cache, _drop_handles) =598
- client.get_room(room_id).unwrap().event_cache().await.unwrap();599
-600
- let (events, mut room_stream) = room_event_cache.subscribe().await.unwrap();601
-602
- wait_for_initial_events(events, &mut room_stream).await;603
-604
- // We're going to cause a small race:605
- // - a pagination request to sync will be sent,606
- // - a backpagination will be sent concurrently.607
- //608
- // So events have to happen in this order:609
- // - the backpagination request is sent, with a prev-batch A610
- // - the sync endpoint returns *after* the backpagination started, before the611
- // backpagination ends612
- // - the backpagination ends, with a prev-batch token that's now stale.613
- //614
- // The backpagination should result in an unknown-token-error.615
-616
- // Mock the first back-pagination request, with a delay.617
- server618
- .mock_room_messages()619
- .match_from("first_backpagination")620
- .ok(RoomMessagesResponseTemplate::default()621
- .events(vec![f.text_msg("lalala").into_raw_timeline()])622
- .with_delay(Duration::from_millis(500)))623
- .mock_once()624
- .mount()625
- .await;626
-627
- // Mock the second back-pagination request, that will be hit after the reset628
- // caused by the sync.629
- server630
- .mock_room_messages()631
- .match_from("second_backpagination")632
- .ok(RoomMessagesResponseTemplate::default()633
- .end_token("third_backpagination")634
- .events(vec![f.text_msg("finally!").into_raw_timeline()]))635
- .mock_once()636
- .mount()637
- .await;638
-639
- // Run the pagination!640
- let backpagination = spawn({641
- let pagination = room_event_cache.pagination();642
- async move { pagination.run_backwards_once(20).await }643
- });644
-645
- // Receive the sync response (which clears the timeline).646
- server647
- .sync_room(648
- &client,649
- JoinedRoomBuilder::new(room_id)650
- // Note to self: a timeline must have at least single event to be properly651
- // serialized.652
- .add_timeline_event(f.text_msg("heyo").into_raw_sync())653
- .set_timeline_prev_batch("second_backpagination".to_owned())654
- .set_timeline_limited(),655
- )656
- .await;657
-658
- let outcome = backpagination.await.expect("join failed").unwrap();659
-660
- // Backpagination will automatically restart, so eventually we get the events.661
- let BackPaginationOutcome { events, .. } = outcome;662
- assert!(!events.is_empty());663
-664
- // Assert the updates as diffs.665
- {666
- // The room shrinks the linked chunk to the last event chunk, so it clears and667
- // re-adds the latest event.668
- assert_let_timeout!(669
- Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =670
- room_stream.recv()671
- );672
- assert_eq!(diffs.len(), 2);673
- assert_matches!(&diffs[0], VectorDiff::Clear);674
- assert_matches!(&diffs[1], VectorDiff::Append { values } => {675
- assert_eq!(values.len(), 1);676
- assert_event_matches_msg(&values[0], "heyo");677
- });678
- }679
-680
- {681
- // Then we receive the event from the restarted back-pagination with682
- // `second_backpagination`.683
- assert_let_timeout!(684
- Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =685
- room_stream.recv()686
- );687
- assert_eq!(diffs.len(), 1);688
- assert_matches!(&diffs[0], VectorDiff::Insert { index, value: event } => {689
- assert_eq!(*index, 0);690
- assert_event_matches_msg(event, "finally!");691
- });692
- }693
-694
- assert!(room_stream.is_empty());695
-}696
-697
-#[async_test]698570
async fn test_backpaginating_without_token() {699571
let server = MatrixMockServer::new().await;700572
let client = server.client_builder().build().await;@@ -1212,6 +1084,7 @@ async fn test_no_gap_stored_after_deduplicated_backpagination() {
12121084
}12131085
12141086
drop(events);1087
+ let events_before_expansion = f.text_msg("sup").event_id(event_id!("$3")).into_event();12151088
12161089
// Now, simulate that we expanded the timeline window with sliding sync, by12171090
// returning more items.@@ -1229,45 +1102,23 @@ async fn test_no_gap_stored_after_deduplicated_backpagination() {
12291102
)12301103
.await;12311104
1105
+ // The retained `$3` is part of the expanded window, so the sync is continuous1106
+ // with it: prev-batch2 is dropped, and `$3` moves behind `$1` and `$2`.12321107
assert_let_timeout!(12331108
Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =12341109
stream.recv()12351110
);1236
- assert_eq!(diffs.len(), 2);1237
-1238
- assert_matches!(&diffs[0], VectorDiff::Clear);1239
-1240
- // Then the latest event chunk is reloaded.1241
- // `$ev1`, `$ev2` and `$ev3` are added.1242
- assert_matches!(&diffs[1], VectorDiff::Append { values: events } => {1243
- assert_eq!(events.len(), 3);1244
- assert_eq!(events[0].event_id().unwrap().as_str(), "$1");1245
- assert_eq!(events[1].event_id().unwrap().as_str(), "$2");1246
- assert_eq!(events[2].event_id().unwrap().as_str(), "$3");1247
- });1111
+ let mut visible = imbl::vector![events_before_expansion];1112
+ for diff in diffs {1113
+ diff.apply(&mut visible);1114
+ }1115
+ let ids: Vec<_> = visible.iter().filter_map(|event| event.event_id()).collect();1116
+ assert_eq!(ids, [event_id!("$1"), event_id!("$2"), event_id!("$3")]);12481117
12491118
assert!(stream.is_empty());12501119
1251
- // For prev-batch2, the back-pagination returns nothing.1252
- server1253
- .mock_room_messages()1254
- .match_from("prev-batch2")1255
- .ok(RoomMessagesResponseTemplate::default())1256
- .mock_once()1257
- .mount()1258
- .await;1259
-1260
- // Run pagination once: it will consume prev-batch2 first, which is the most1261
- // recent token, which returns an empty batch, thus indicating the start of the1262
- // room; but we still have a chunk in storage, so it appears like it's not the1263
- // start *yet*.12641120
let pagination = room_event_cache.pagination();12651121
1266
- let outcome = pagination.run_backwards_once(20).await.unwrap();1267
- assert!(outcome.reached_start.not());1268
- assert!(outcome.events.is_empty());1269
- assert!(stream.is_empty());1270
-12711122
// For prev-batch, the back-pagination returns two events we already know, and a12721123
// previous batch token.12731124
server@@ -2652,8 +2503,8 @@ async fn test_relations_ordering() {
26522503
.edit(target_event_id, RoomMessageEventContentWithoutRelation::text_plain("hello world"))26532504
.event_id(edit4);26542505
2655
- // We receive two edit events via sync, as well as a gap; this will shrink the2656
- // linked chunk.2506
+ // We receive two edit events via sync, as well as a gap, which keeps the2507
+ // retained target and records a history gap after it.26572508
server26582509
.sync_room(26592510
&client,@@ -2671,8 +2522,7 @@ async fn test_relations_ordering() {
26712522
Ok(RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs { diffs, .. })) =26722523
listener.recv()26732524
);2674
- // We've received the shrink.2675
- if diffs.iter().any(|diff| matches!(diff, VectorDiff::Clear)) {2525
+ if diffs.iter().any(|diff| matches!(diff, VectorDiff::Append { .. })) {26762526
break;26772527
}26782528
}@@ -2696,17 +2546,9 @@ async fn test_relations_ordering() {
26962546
.mount()26972547
.await;26982548
2699
- // Run the pagination.2700
- let outcome = room_event_cache.pagination().run_backwards_once(1).await.unwrap();2701
- assert!(outcome.reached_start.not());2702
- assert_eq!(outcome.events.len(), 1);2703
-2704
- {2705
- // Sanity check: we load the first chunk with the first event, from disk, and2706
- // reach the start of the timeline.2707
- let outcome = room_event_cache.pagination().run_backwards_once(1).await.unwrap();2708
- assert!(outcome.reached_start);2709
- }2549
+ let gap_id = room_event_cache.history_gaps().await.unwrap()[0].id.clone();2550
+ let outcome = room_event_cache.fill_history_gap_once(gap_id).await.unwrap();2551
+ assert_matches!(outcome, HistoryGapFillOutcome::Updated { events: 1, .. });27102552
27112553
// Relations are returned accordingly.27122554
let (_, relations) =