Commit 4fae3b516f05
tarball13 files changed+437-1010
@@ -56,7 +56,6 @@ mod queue;
5656
mod reactions;5757
mod read_receipts;5858
mod redecryption;59
-mod reseed;6059
mod segmented_history;6160
mod replies;6261
mod subscribe;@@ -1,109 +0,0 @@
1
-use std::time::Duration;2
-3
-use assert_matches::assert_matches;4
-use futures_util::StreamExt;5
-use matrix_sdk::test_utils::mocks::MatrixMockServer;6
-use matrix_sdk_test::{ALICE, JoinedRoomBuilder, async_test, event_factory::EventFactory};7
-use matrix_sdk_ui::timeline::{EventSendState, RoomExt};8
-use ruma::{event_id, events::room::message::RoomMessageEventContent, room_id};9
-use tokio::time::timeout;10
-11
-#[async_test]12
-async fn test_timeline_survives_room_cache_reseed() {13
- let server = MatrixMockServer::new().await;14
- let client = server.client_builder().build().await;15
- client.send_queue().set_enabled(false).await;16
-17
- server.mock_room_state_encryption().plain().mount().await;18
-19
- let room_id = room_id!("!a98sd12bjh:example.org");20
- let room = server.sync_joined_room(&client, room_id).await;21
- let timeline = room.timeline().await.unwrap();22
- let (_, mut timeline_stream) = timeline.subscribe().await;23
- let event_factory = EventFactory::new().room(room_id);24
-25
- let old_event_id = event_id!("$before_reseed");26
- server27
- .sync_room(28
- &client,29
- JoinedRoomBuilder::new(room_id).add_timeline_event(30
- event_factory.text_msg("old history").sender(&ALICE).event_id(old_event_id),31
- ),32
- )33
- .await;34
-35
- timeout(Duration::from_secs(5), async {36
- loop {37
- let items = timeline.items().await;38
- if items.iter().any(|item| {39
- item.as_event().is_some_and(|event| event.event_id() == Some(old_event_id))40
- }) {41
- break;42
- }43
- assert!(timeline_stream.next().await.is_some());44
- }45
- })46
- .await47
- .unwrap();48
-49
- timeline.send(RoomMessageEventContent::text_plain("pending local echo").into()).await.unwrap();50
-51
- timeout(Duration::from_secs(5), async {52
- loop {53
- let items = timeline.items().await;54
- if items.iter().any(|item| {55
- item.as_event().is_some_and(|event| {56
- event57
- .content()58
- .as_message()59
- .is_some_and(|message| message.body() == "pending local echo")60
- })61
- }) {62
- break;63
- }64
- assert!(timeline_stream.next().await.is_some());65
- }66
- })67
- .await68
- .unwrap();69
-70
- let new_event_id = event_id!("$after_reseed");71
- let new_event =72
- event_factory.text_msg("recent history").sender(&ALICE).event_id(new_event_id).into_event();73
- let (room_event_cache, _drop_handles) = room.event_cache().await.unwrap();74
- room_event_cache75
- .reseed(vec![new_event], Some("older".into()))76
- .await77
- .unwrap();78
-79
- timeout(Duration::from_secs(5), async {80
- loop {81
- let items = timeline.items().await;82
- if items.iter().any(|item| {83
- item.as_event().is_some_and(|event| event.event_id() == Some(new_event_id))84
- }) {85
- break;86
- }87
- assert!(timeline_stream.next().await.is_some());88
- }89
- })90
- .await91
- .unwrap();92
-93
- let items = timeline.items().await;94
- assert!(!items.iter().any(|item| {95
- item.as_event().is_some_and(|event| event.event_id() == Some(old_event_id))96
- }));97
-98
- let local_echo = items99
- .iter()100
- .filter_map(|item| item.as_event())101
- .find(|event| {102
- event103
- .content()104
- .as_message()105
- .is_some_and(|message| message.body() == "pending local echo")106
- })107
- .unwrap();108
- assert_matches!(local_echo.send_state(), Some(EventSendState::NotSentYet { .. }));109
-}@@ -2,7 +2,7 @@ use std::time::Duration;
22
33
use futures_util::StreamExt;44
use matrix_sdk::test_utils::mocks::{MatrixMockServer, RoomMessagesResponseTemplate};5
-use matrix_sdk_test::{ALICE, async_test, event_factory::EventFactory};5
+use matrix_sdk_test::{ALICE, JoinedRoomBuilder, async_test, event_factory::EventFactory};66
use matrix_sdk_ui::timeline::{RoomExt, VirtualTimelineItem};77
use ruma::{event_id, events::room::message::RoomMessageEventContent, room_id};88
use tokio::time::timeout;@@ -22,13 +22,14 @@ async fn test_timeline_fills_segmented_history_without_replacing_retained_items(
2222
let old_event_id = event_id!("$old");2323
let hidden_event_id = event_id!("$hidden");2424
let new_event_id = event_id!("$new");25
- cache26
- .reseed(27
- vec![event_factory.text_msg("old").event_id(old_event_id).into_event()],28
- Some("old-cursor".into()),25
+ server26
+ .sync_room(27
+ &client,28
+ JoinedRoomBuilder::new(room_id)29
+ .add_timeline_event(event_factory.text_msg("old").event_id(old_event_id))30
+ .set_timeline_prev_batch("old-cursor"),2931
)30
- .await31
- .unwrap();32
+ .await;3233
let gap = cache3334
.append_history_section(3435
vec![@@ -210,7 +210,7 @@ name = "response_commit"
210210
required-features = ["testing", "e2e-encryption"]211211
212212
[[test]]213
-name = "history_reseed"213
+name = "room_clear"214214
required-features = ["testing"]215215
216216
[[test]]@@ -384,41 +384,6 @@ impl RoomEventCache {
384384
Ok(())385385
}386386
387
- #[doc = include_str!("reseed.md")]388
- pub async fn reseed(&self, events: Vec<Event>, prev_batch: Option<String>) -> Result<()> {389
- let cache = self.clone();390
- matrix_sdk_common::executor::spawn(async move {391
- let mut state = cache.inner.state.write().await?;392
- state.reseed(events, prev_batch.clone()).await?;393
- let events =394
- state.room_linked_chunk().events().map(|(_, event)| event.clone()).collect();395
- let result = async {396
- state.clear_loaded_views().await?;397
- state398
- .post_process_new_events(events, PostProcessingOrigin::Backpagination, None)399
- .await400
- }401
- .await;402
- let items =403
- state.room_linked_chunk().events().map(|(_, event)| event.clone()).collect();404
- let _ = state.room_linked_chunk_mut().updates_as_vector_diffs();405
- cache.inner.update_sender.send(406
- RoomEventCacheUpdate::UpdateTimelineEvents(TimelineVectorDiffs {407
- diffs: vec![eyeball_im::VectorDiff::Reset { values: items }],408
- history_gaps: Some(history::gap_positions(state.room_linked_chunk())),409
- origin: EventsOrigin::Cache,410
- }),411
- Some(RoomEventCacheGenericUpdate { room_id: cache.inner.room_id.clone() }),412
- );413
- if prev_batch.is_some() {414
- cache.inner.pagination_batch_token_notifier.notify_waiters();415
- }416
- result417
- })418
- .await419
- .map_err(|error| EventCacheError::TaskInterrupted(error.to_string()))?420
- }421
-422387
/// Return a reference to the state.423388
pub(in super::super) fn state(&self) -> &LockedRoomEventCacheState {424389
&self.inner.state@@ -1,25 +0,0 @@
1
-Replace this room's timeline with independently fetched history and its older2
-pagination cursor. The replacement is persisted in one linked chunk update3
-transaction before a timeline reset is published.4
-5
-Pass events in topological order, oldest first. For a backwards6
-[`Room::messages`](crate::Room::messages) response, reverse its `chunk` and pass7
-its `end` as `prev_batch`. Decryption should happen through the normal room8
-messages API before reseeding. Duplicate event IDs within the supplied window9
-are retained once. `None` means the start of accessible history has been reached,10
-including for an empty window. An empty window with a cursor remains pageable.11
-12
-This replaces cached history and invalidates thread, pinned, and event-focused13
-timelines, including persisted views that have not been loaded. It does14
-not establish continuity with discarded history. Account credentials, encryption15
-state, and pending outgoing messages are not cleared.16
-17
-Once started, the replacement finishes even if the caller drops its future.18
-Failure to persist the replacement leaves the prior room timeline intact.19
-Post-processing can fail after the replacement is installed, in which case the20
-new timeline is still published and the error is returned. Completion means the21
-cache update has been emitted, not that an application mirror has consumed it.22
-23
-Callers must coordinate live sync and requests that fetched this window. Sync24
-updates are serialized against replacement but may append normally afterward.25
-In-flight older pagination is fenced against timeline replacement.@@ -781,28 +781,6 @@ impl<'a> RoomEventCacheStateLockWriteGuard<'a> {
781781
Ok(diff_updates)782782
}783783
784
- pub async fn reseed(785
- &mut self,786
- mut events: Vec<Event>,787
- prev_batch: Option<String>,788
- ) -> Result<(), EventCacheError> {789
- let mut event_ids = HashSet::new();790
- events.retain(|event| event.event_id().is_none_or(|id| event_ids.insert(id)));791
- let mut replacement = EventLinkedChunk::new();792
- replacement.reset();793
- replacement.push_live_events(794
- prev_batch.clone().map(|token| Gap { token, history: None }),795
- &events,796
- );797
- let updates = replacement.store_updates().take();798
- self.send_updates_to_store(updates).await?;799
- let _ = replacement.updates_as_vector_diffs();800
- self.state.room_linked_chunk = replacement;801
- self.state.waited_for_initial_prev_token = true;802
- self.reset_pagination_status(prev_batch.is_none());803
- Ok(())804
- }805
-806784
pub(super) async fn history_candidate(&self) -> Result<EventLinkedChunk, EventCacheError> {807785
let linked_chunk_id = LinkedChunkId::Room(&self.state.room_id);808786
let (last, generator) = self.store.load_last_chunk(linked_chunk_id).await?;@@ -1,598 +0,0 @@
1
-use std::{2
- sync::{Arc, atomic::Ordering},3
- time::Duration,4
-};5
-6
-use eyeball_im::VectorDiff;7
-use matrix_sdk::{8
- ThreadingSupport,9
- config::StoreConfig,10
- event_cache::{EventFocusThreadMode, PaginationStatus, RoomEventCacheUpdate},11
- room::MessagesOptions,12
- ruma::{event_id, room_id},13
- test_utils::mocks::{14
- MatrixMockServer, RoomContextResponseTemplate, RoomMessagesResponseTemplate,15
- RoomRelationsResponseTemplate,16
- },17
-};18
-use matrix_sdk_base::{event_cache::store::EventCacheStore, linked_chunk::LinkedChunkId};19
-use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;20
-use matrix_sdk_test::{ALICE, JoinedRoomBuilder, event_factory::EventFactory};21
-22
-#[path = "history_reseed/failing_store.rs"]23
-mod failing_store;24
-25
-#[tokio::test]26
-async fn reseed_publishes_one_reset_and_preserves_the_fetched_cursor() {27
- let server = MatrixMockServer::new().await;28
- let client = server.client_builder().build().await;29
- let room_id = room_id!("!reseed:localhost");30
- client.event_cache().subscribe().unwrap();31
- server.sync_joined_room(&client, room_id).await;32
- let room = client.get_room(room_id).unwrap();33
- let (cache, _handles) = room.event_cache().await.unwrap();34
- let (_, mut updates) = cache.subscribe().await.unwrap();35
- let factory = EventFactory::new().room(room_id).sender(&ALICE);36
- cache37
- .reseed(vec![factory.text_msg("old").into_event()], Some("old-cursor".into()))38
- .await39
- .unwrap();40
- updates.recv().await.unwrap();41
- let recent_id = event_id!("$recent:localhost");42
- let older_id = event_id!("$older:localhost");43
- server44
- .mock_room_messages()45
- .ok(RoomMessagesResponseTemplate::default()46
- .events(vec![47
- factory.text_msg("recent").event_id(recent_id),48
- factory.text_msg("older").event_id(older_id),49
- factory.text_msg("older").event_id(older_id),50
- ])51
- .end_token("fetched-older-cursor"))52
- .mock_once()53
- .mount()54
- .await;55
- let mut history = room.messages(MessagesOptions::backward()).await.unwrap();56
- history.chunk.reverse();57
- cache.reseed(history.chunk, history.end).await.unwrap();58
- let update =59
- tokio::time::timeout(Duration::from_secs(2), updates.recv()).await.unwrap().unwrap();60
- let RoomEventCacheUpdate::UpdateTimelineEvents(update) = update else {61
- panic!("expected reset")62
- };63
- assert_eq!(update.diffs.len(), 1);64
- let VectorDiff::Reset { values } = &update.diffs[0] else { panic!("expected reset") };65
- assert_eq!(values.len(), 2);66
- assert_eq!(values[0].event_id().as_deref(), Some(older_id));67
- assert_eq!(values[1].event_id().as_deref(), Some(recent_id));68
- assert!(updates.is_empty());69
- assert_eq!(cache.events().await.unwrap().len(), 2);70
- server71
- .mock_room_messages()72
- .match_from("fetched-older-cursor")73
- .ok(RoomMessagesResponseTemplate::default())74
- .mock_once()75
- .mount()76
- .await;77
- assert!(cache.pagination().run_backwards_once(10).await.unwrap().reached_start);78
-}79
-80
-#[tokio::test]81
-async fn reseed_empty_history_distinguishes_a_cursor_from_the_start_of_history() {82
- let server = MatrixMockServer::new().await;83
- let client = server.client_builder().build().await;84
- let room_id = room_id!("!reseed:localhost");85
- client.event_cache().subscribe().unwrap();86
- server.sync_joined_room(&client, room_id).await;87
- let (cache, _handles) = client.get_room(room_id).unwrap().event_cache().await.unwrap();88
- cache.reseed(Vec::new(), None).await.unwrap();89
- assert_eq!(90
- cache.pagination().status().get(),91
- PaginationStatus::Idle { hit_timeline_start: true }92
- );93
- assert!(cache.pagination().run_backwards_once(10).await.unwrap().reached_start);94
- cache.reseed(Vec::new(), Some("empty-page-cursor".into())).await.unwrap();95
- assert_eq!(96
- cache.pagination().status().get(),97
- PaginationStatus::Idle { hit_timeline_start: false }98
- );99
- server100
- .mock_room_messages()101
- .match_from("empty-page-cursor")102
- .ok(RoomMessagesResponseTemplate::default())103
- .mock_once()104
- .mount()105
- .await;106
- assert!(cache.pagination().run_backwards_once(10).await.unwrap().reached_start);107
-}108
-109
-#[tokio::test]110
-async fn reseed_fences_inflight_tokenless_pagination() {111
- let server = MatrixMockServer::new().await;112
- let client = server.client_builder().build().await;113
- let room_id = room_id!("!reseed:localhost");114
- client.event_cache().subscribe().unwrap();115
- server.sync_joined_room(&client, room_id).await;116
- let (cache, _handles) = client.get_room(room_id).unwrap().event_cache().await.unwrap();117
- let factory = EventFactory::new().room(room_id).sender(&ALICE);118
- let stale = factory.text_msg("stale request");119
- server120
- .mock_room_messages()121
- .ok(RoomMessagesResponseTemplate::default()122
- .events(vec![stale])123
- .with_delay(Duration::from_millis(300)))124
- .mock_once()125
- .mount()126
- .await;127
- let pagination = tokio::spawn({128
- let cache = cache.clone();129
- async move { cache.pagination().run_backwards_once(10).await }130
- });131
- tokio::time::timeout(Duration::from_secs(8), async {132
- loop {133
- if server134
- .received_requests()135
- .await136
- .unwrap()137
- .iter()138
- .any(|r| r.url.path().ends_with("/messages"))139
- {140
- break;141
- }142
- tokio::task::yield_now().await;143
- }144
- })145
- .await146
- .unwrap();147
- let recent_id = event_id!("$fresh:localhost");148
- cache149
- .reseed(vec![factory.text_msg("fresh").event_id(recent_id).into_event()], None)150
- .await151
- .unwrap();152
- tokio::time::timeout(Duration::from_secs(3), pagination).await.unwrap().unwrap().unwrap();153
- let events = cache.events().await.unwrap();154
- assert_eq!(events.len(), 1);155
- assert_eq!(events[0].event_id().as_deref(), Some(recent_id));156
- assert_eq!(157
- cache.pagination().status().get(),158
- PaginationStatus::Idle { hit_timeline_start: true }159
- );160
-}161
-162
-#[tokio::test]163
-async fn reseed_preserves_timeline_when_store_write_fails() {164
- let server = MatrixMockServer::new().await;165
- let store = Arc::new(failing_store::FailingStore::default());166
- let client = server167
- .client_builder()168
- .on_builder(|builder| {169
- builder.store_config(170
- StoreConfig::new(CrossProcessLockConfig::SingleProcess)171
- .event_cache_store(store.clone()),172
- )173
- })174
- .build()175
- .await;176
- let room_id = room_id!("!reseed:localhost");177
- client.event_cache().subscribe().unwrap();178
- server.sync_joined_room(&client, room_id).await;179
- let (cache, _handles) = client.get_room(room_id).unwrap().event_cache().await.unwrap();180
- let factory = EventFactory::new().room(room_id).sender(&ALICE);181
- let original_id = event_id!("$original:localhost");182
- cache183
- .reseed(184
- vec![factory.text_msg("original").event_id(original_id).into_event()],185
- Some("original-cursor".into()),186
- )187
- .await188
- .unwrap();189
- let (original_events, updates) = cache.subscribe().await.unwrap();190
- assert_eq!(original_events.len(), 1);191
- assert_eq!(original_events[0].event_id().as_deref(), Some(original_id));192
- let original_chunks = store.inner.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap();193
-194
- store.fail_writes.store(true, Ordering::SeqCst);195
- let result = cache196
- .reseed(197
- vec![198
- factory199
- .text_msg("replacement")200
- .event_id(event_id!("$replacement:localhost"))201
- .into_event(),202
- ],203
- Some("replacement-cursor".into()),204
- )205
- .await;206
- store.fail_writes.store(false, Ordering::SeqCst);207
-208
- assert!(result.is_err());209
- assert!(updates.is_empty());210
- let current_events = cache.events().await.unwrap();211
- assert_eq!(current_events.len(), 1);212
- assert_eq!(current_events[0].event_id().as_deref(), Some(original_id));213
- let current_chunks = store.inner.load_all_chunks(LinkedChunkId::Room(room_id)).await.unwrap();214
- assert_eq!(format!("{current_chunks:?}"), format!("{original_chunks:?}"));215
-}216
-217
-#[tokio::test]218
-async fn reseed_redaction_does_not_leak_stale_diff_into_next_sync() {219
- let server = MatrixMockServer::new().await;220
- let client = server.client_builder().build().await;221
- let room_id = room_id!("!reseed:localhost");222
- client.event_cache().subscribe().unwrap();223
- server.sync_joined_room(&client, room_id).await;224
- let room = client.get_room(room_id).unwrap();225
- let (cache, _handles) = room.event_cache().await.unwrap();226
- let (_, mut updates) = cache.subscribe().await.unwrap();227
- let factory = EventFactory::new().room(room_id).sender(&ALICE);228
- let redacted_id = event_id!("$redacted:localhost");229
- let redaction_id = event_id!("$redaction:localhost");230
-231
- cache232
- .reseed(233
- vec![234
- factory.text_msg("secret").event_id(redacted_id).into_event(),235
- factory.redaction(&redacted_id).event_id(redaction_id).into_event(),236
- ],237
- None,238
- )239
- .await240
- .unwrap();241
- let reset =242
- tokio::time::timeout(Duration::from_secs(2), updates.recv()).await.unwrap().unwrap();243
- let RoomEventCacheUpdate::UpdateTimelineEvents(reset) = reset else {244
- panic!("expected timeline reset")245
- };246
- let VectorDiff::Reset { values } = &reset.diffs[0] else { panic!("expected reset diff") };247
- assert_eq!(values.len(), 2);248
- assert!(values[0].raw().deserialize().unwrap().is_redacted());249
-250
- let new_event_id = event_id!("$new-live:localhost");251
- server252
- .sync_room(253
- &client,254
- JoinedRoomBuilder::new(room_id).add_timeline_event(255
- factory.text_msg("live").event_id(new_event_id).into_raw_sync(),256
- ),257
- )258
- .await;259
- let live_update =260
- tokio::time::timeout(Duration::from_secs(2), updates.recv()).await.unwrap().unwrap();261
- let RoomEventCacheUpdate::UpdateTimelineEvents(live_update) = live_update else {262
- panic!("expected timeline update")263
- };264
- assert!(live_update.diffs.iter().all(|diff| !matches!(diff, VectorDiff::Set { .. })));265
- assert!(live_update.diffs.iter().all(|diff| match diff {266
- VectorDiff::Append { values } => {267
- values.iter().all(|event| event.event_id().as_deref() == Some(new_event_id))268
- }269
- VectorDiff::PushBack { value } => value.event_id().as_deref() == Some(new_event_id),270
- _ => true,271
- }));272
- assert_eq!(cache.events().await.unwrap().len(), 3);273
-}274
-275
-#[tokio::test]276
-async fn reseed_retries_thread_pagination_without_stale_response() {277
- use wiremock::ResponseTemplate;278
-279
- let server = MatrixMockServer::new().await;280
- let client = server281
- .client_builder()282
- .on_builder(|builder| {283
- builder.with_threading_support(ThreadingSupport::Enabled { with_subscriptions: false })284
- })285
- .build()286
- .await;287
- let room_id = room_id!("!reseed:localhost");288
- client.event_cache().subscribe().unwrap();289
- let thread_root_id = event_id!("$thread-root:localhost");290
- let initial_reply_id = event_id!("$initial-reply:localhost");291
- let factory = EventFactory::new().room(room_id).sender(&ALICE);292
- let room = server293
- .sync_room(294
- &client,295
- JoinedRoomBuilder::new(room_id)296
- .set_timeline_prev_batch("initial-thread-cursor")297
- .add_timeline_event(298
- factory299
- .text_msg("initial reply")300
- .in_thread(thread_root_id, thread_root_id)301
- .event_id(initial_reply_id),302
- ),303
- )304
- .await;305
- let (cache, _handles) = room.event_cache().await.unwrap();306
- let (initial_events, _thread_updates) =307
- cache.subscribe_to_thread(thread_root_id.to_owned()).await.unwrap();308
- assert_eq!(initial_events.len(), 1);309
- assert_eq!(initial_events[0].event_id().as_deref(), Some(initial_reply_id));310
-311
- let stale_reply_id = event_id!("$stale-reply:localhost");312
- let fresh_reply_id = event_id!("$fresh-reply:localhost");313
- server314
- .mock_room_relations()315
- .match_target_event(thread_root_id.to_owned())316
- .ok(RoomRelationsResponseTemplate::default().events(vec![317
- factory318
- .text_msg("stale reply")319
- .in_thread(thread_root_id, thread_root_id)320
- .event_id(stale_reply_id),321
- ]))322
- .mock_once()323
- .mount()324
- .await;325
- let root_event = factory.text_msg("thread root").event_id(thread_root_id).into_event();326
- server327
- .mock_room_event()328
- .room(room_id)329
- .ok_with_template(330
- ResponseTemplate::new(200)331
- .set_body_json(root_event.into_raw().json())332
- .set_delay(Duration::from_millis(300)),333
- )334
- .mock_once()335
- .mount()336
- .await;337
-338
- let pagination = tokio::spawn({339
- let cache = cache.clone();340
- let thread_root_id = thread_root_id.to_owned();341
- async move {342
- cache.thread_pagination(thread_root_id).await.unwrap().run_backwards_once(10).await343
- }344
- });345
- tokio::time::timeout(Duration::from_secs(8), async {346
- loop {347
- if server348
- .received_requests()349
- .await350
- .unwrap()351
- .iter()352
- .any(|request| request.url.path().contains("/event/"))353
- {354
- break;355
- }356
- tokio::task::yield_now().await;357
- }358
- })359
- .await360
- .unwrap();361
- server362
- .mock_room_relations()363
- .match_target_event(thread_root_id.to_owned())364
- .ok(RoomRelationsResponseTemplate::default()365
- .events(vec![366
- factory367
- .text_msg("fresh reply")368
- .in_thread(thread_root_id, thread_root_id)369
- .event_id(fresh_reply_id),370
- ])371
- .next_batch("fresh-cursor"))372
- .mock_once()373
- .mount()374
- .await;375
- cache.reseed(Vec::new(), None).await.unwrap();376
-377
- let outcome =378
- tokio::time::timeout(Duration::from_secs(8), pagination).await.unwrap().unwrap().unwrap();379
- assert!(outcome.events.iter().all(|event| event.event_id().as_deref() != Some(stale_reply_id)));380
- assert!(outcome.events.iter().any(|event| event.event_id().as_deref() == Some(fresh_reply_id)));381
- let (thread_events, _thread_updates) =382
- cache.subscribe_to_thread(thread_root_id.to_owned()).await.unwrap();383
- assert!(thread_events.iter().all(|event| event.event_id().as_deref() != Some(stale_reply_id)));384
- assert!(thread_events.iter().any(|event| event.event_id().as_deref() == Some(fresh_reply_id)));385
-}386
-387
-#[tokio::test]388
-async fn clear_invalidates_focused_cache_clones_and_inflight_pagination() {389
- let server = MatrixMockServer::new().await;390
- let client = server.client_builder().build().await;391
- let room_id = room_id!("!reseed:localhost");392
- client.event_cache().subscribe().unwrap();393
- server.sync_joined_room(&client, room_id).await;394
- let room = client.get_room(room_id).unwrap();395
- let (cache, _handles) = room.event_cache().await.unwrap();396
- let factory = EventFactory::new().room(room_id).sender(&ALICE);397
- let focused_id = event_id!("$focused:localhost");398
- server399
- .mock_room_event_context()400
- .ok(RoomContextResponseTemplate::new(401
- factory.text_msg("focused").event_id(focused_id).into_event(),402
- )403
- .start("old-focused-cursor"))404
- .mock_once()405
- .mount()406
- .await;407
- let focused = cache408
- .get_or_create_event_focused_cache(409
- focused_id.to_owned(),410
- 10,411
- EventFocusThreadMode::Automatic,412
- )413
- .await414
- .unwrap();415
- let focused_clone = focused.clone();416
- let (initial_events, mut updates) = focused.subscribe().await;417
- assert_eq!(initial_events.len(), 1);418
- assert_eq!(initial_events[0].event_id().as_deref(), Some(focused_id));419
-420
- let stale_event_id = event_id!("$stale-focused:localhost");421
- server422
- .mock_room_messages()423
- .match_from("old-focused-cursor")424
- .ok(RoomMessagesResponseTemplate::default()425
- .events(vec![factory.text_msg("stale").event_id(stale_event_id)])426
- .end_token("stale-next-cursor")427
- .with_delay(Duration::from_millis(300)))428
- .mock_once()429
- .mount()430
- .await;431
- let pagination = tokio::spawn({432
- let focused = focused.clone();433
- async move { focused.paginate_backwards(10).await }434
- });435
- tokio::time::timeout(Duration::from_secs(8), async {436
- loop {437
- if server438
- .received_requests()439
- .await440
- .unwrap()441
- .iter()442
- .any(|request| request.url.path().ends_with("/messages"))443
- {444
- break;445
- }446
- tokio::task::yield_now().await;447
- }448
- })449
- .await450
- .unwrap();451
-452
- tokio::time::timeout(Duration::from_secs(2), cache.clear()).await.unwrap().unwrap();453
- let clear =454
- tokio::time::timeout(Duration::from_secs(2), updates.recv()).await.unwrap().unwrap();455
- assert!(matches!(clear.diffs.as_slice(), [VectorDiff::Clear]));456
- tokio::time::timeout(Duration::from_secs(3), pagination).await.unwrap().unwrap().unwrap();457
- let (cloned_events, _cloned_updates) = focused_clone.subscribe().await;458
- assert!(cloned_events.is_empty());459
- let result = focused_clone.paginate_backwards(10).await.unwrap();460
- assert!(result.hit_end_of_timeline);461
- let (events_after_pagination, _updates_after_pagination) = focused_clone.subscribe().await;462
- assert!(events_after_pagination.is_empty());463
-}464
-465
-#[tokio::test]466
-async fn clear_empties_subscribed_thread_cache() {467
- let server = MatrixMockServer::new().await;468
- let client = server469
- .client_builder()470
- .on_builder(|builder| {471
- builder.with_threading_support(ThreadingSupport::Enabled { with_subscriptions: false })472
- })473
- .build()474
- .await;475
- let room_id = room_id!("!reseed:localhost");476
- client.event_cache().subscribe().unwrap();477
- let thread_root_id = event_id!("$thread-root:localhost");478
- let thread_reply_id = event_id!("$thread-reply:localhost");479
- let factory = EventFactory::new().room(room_id).sender(&ALICE);480
- let room = server481
- .sync_room(482
- &client,483
- JoinedRoomBuilder::new(room_id).add_timeline_event(484
- factory485
- .text_msg("thread reply")486
- .in_thread(thread_root_id, thread_root_id)487
- .event_id(thread_reply_id),488
- ),489
- )490
- .await;491
- let (cache, _handles) = room.event_cache().await.unwrap();492
- let (initial_events, mut updates) =493
- cache.subscribe_to_thread(thread_root_id.to_owned()).await.unwrap();494
- assert_eq!(initial_events.len(), 1);495
- assert_eq!(initial_events[0].event_id().as_deref(), Some(thread_reply_id));496
-497
- cache.clear().await.unwrap();498
- let clear =499
- tokio::time::timeout(Duration::from_secs(2), updates.recv()).await.unwrap().unwrap();500
- assert!(matches!(clear.diffs.as_slice(), [VectorDiff::Clear]));501
- let (events_after_clear, _updates_after_clear) =502
- cache.subscribe_to_thread(thread_root_id.to_owned()).await.unwrap();503
- assert!(events_after_clear.is_empty());504
-}505
-506
-#[tokio::test]507
-async fn clear_empties_loaded_pinned_cache() {508
- let server = MatrixMockServer::new().await;509
- let room_id = room_id!("!reseed:localhost");510
- let pinned_event_id = event_id!("$pinned:localhost");511
- let factory = EventFactory::new().room(room_id).sender(&ALICE);512
- let pinned_event = factory.text_msg("pinned").event_id(pinned_event_id).into_event();513
- server.mock_room_event().match_event_id().ok(pinned_event).mock_once().mount().await;514
- let client = server.client_builder().build().await;515
- client.event_cache().subscribe().unwrap();516
- let room = server517
- .sync_room(518
- &client,519
- JoinedRoomBuilder::new(room_id).add_state_bulk(vec![520
- factory.room_pinned_events(vec![pinned_event_id.to_owned()]).into(),521
- ]),522
- )523
- .await;524
- let (cache, _handles) = room.event_cache().await.unwrap();525
- let (initial_events, mut updates) = cache.subscribe_to_pinned_events().await.unwrap();526
- let mut visible: imbl::Vector<_> = initial_events.into();527
- tokio::time::timeout(Duration::from_secs(3), async {528
- while visible.is_empty() {529
- for diff in updates.recv().await.unwrap().diffs {530
- diff.apply(&mut visible);531
- }532
- }533
- })534
- .await535
- .unwrap();536
- assert_eq!(visible.len(), 1);537
- assert_eq!(visible[0].event_id().as_deref(), Some(pinned_event_id));538
-539
- cache.clear().await.unwrap();540
- let clear =541
- tokio::time::timeout(Duration::from_secs(2), updates.recv()).await.unwrap().unwrap();542
- assert!(matches!(clear.diffs.as_slice(), [VectorDiff::Clear]));543
- for diff in clear.diffs {544
- diff.apply(&mut visible);545
- }546
- assert!(visible.is_empty());547
-}548
-549
-#[cfg(feature = "sqlite")]550
-#[tokio::test]551
-async fn reseed_persists_the_window_and_cursor_across_client_recreation() {552
- let server = MatrixMockServer::new().await;553
- let directory = tempfile::tempdir().unwrap();554
- let room_id = room_id!("!reseed:localhost");555
- let recent_id = event_id!("$recent:localhost");556
- {557
- let client = server558
- .client_builder()559
- .on_builder(|b| b.sqlite_store(directory.path(), None))560
- .build()561
- .await;562
- client.event_cache().subscribe().unwrap();563
- server.sync_joined_room(&client, room_id).await;564
- let (cache, _handles) = client.get_room(room_id).unwrap().event_cache().await.unwrap();565
- cache566
- .reseed(567
- vec![568
- EventFactory::new()569
- .room(room_id)570
- .sender(&ALICE)571
- .text_msg("recent")572
- .event_id(recent_id)573
- .into_event(),574
- ],575
- Some("persisted-cursor".into()),576
- )577
- .await578
- .unwrap();579
- }580
- let client = server581
- .client_builder()582
- .on_builder(|b| b.sqlite_store(directory.path(), None))583
- .build()584
- .await;585
- client.event_cache().subscribe().unwrap();586
- let (cache, _handles) = client.get_room(room_id).unwrap().event_cache().await.unwrap();587
- let (events, _subscriber) = cache.subscribe().await.unwrap();588
- assert_eq!(events.len(), 1);589
- assert_eq!(events[0].event_id().as_deref(), Some(recent_id));590
- server591
- .mock_room_messages()592
- .match_from("persisted-cursor")593
- .ok(RoomMessagesResponseTemplate::default())594
- .mock_once()595
- .mount()596
- .await;597
- assert!(cache.pagination().run_backwards_once(10).await.unwrap().reached_start);598
-}@@ -1,141 +0,0 @@
1
-use std::sync::atomic::{AtomicBool, Ordering};2
-3
-use async_trait::async_trait;4
-use matrix_sdk_base::{5
- event_cache::{6
- Event, Gap,7
- store::{EventCacheStore, EventCacheStoreError, MemoryStore},8
- },9
- linked_chunk::{10
- ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId, Position,11
- RawChunk, Update,12
- },13
-};14
-use matrix_sdk_common::cross_process_lock::CrossProcessLockGeneration;15
-use ruma::{EventId, OwnedEventId, RoomId, events::relation::RelationType};16
-17
-#[derive(Debug, Default)]18
-pub struct FailingStore {19
- pub inner: MemoryStore,20
- pub fail_writes: AtomicBool,21
-}22
-23
-#[cfg_attr(target_family = "wasm", async_trait(?Send))]24
-#[cfg_attr(not(target_family = "wasm"), async_trait)]25
-impl EventCacheStore for FailingStore {26
- type Error = EventCacheStoreError;27
-28
- async fn try_take_leased_lock(29
- &self,30
- lease_duration_ms: u32,31
- key: &str,32
- holder: &str,33
- ) -> Result<Option<CrossProcessLockGeneration>, Self::Error> {34
- self.inner.try_take_leased_lock(lease_duration_ms, key, holder).await35
- }36
-37
- async fn handle_linked_chunk_updates(38
- &self,39
- linked_chunk_id: LinkedChunkId<'_>,40
- updates: Vec<Update<Event, Gap>>,41
- ) -> Result<(), Self::Error> {42
- if self.fail_writes.load(Ordering::SeqCst) {43
- return Err(EventCacheStoreError::InvalidData {44
- details: "injected write failure".into(),45
- });46
- }47
-48
- self.inner.handle_linked_chunk_updates(linked_chunk_id, updates).await49
- }50
-51
- async fn remove_room(&self, room_id: &RoomId) -> Result<(), Self::Error> {52
- self.inner.remove_room(room_id).await53
- }54
-55
- async fn load_all_chunks(56
- &self,57
- linked_chunk_id: LinkedChunkId<'_>,58
- ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error> {59
- self.inner.load_all_chunks(linked_chunk_id).await60
- }61
-62
- async fn load_all_chunks_metadata(63
- &self,64
- linked_chunk_id: LinkedChunkId<'_>,65
- ) -> Result<Vec<ChunkMetadata>, Self::Error> {66
- self.inner.load_all_chunks_metadata(linked_chunk_id).await67
- }68
-69
- async fn load_last_chunk(70
- &self,71
- linked_chunk_id: LinkedChunkId<'_>,72
- ) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error> {73
- self.inner.load_last_chunk(linked_chunk_id).await74
- }75
-76
- async fn load_previous_chunk(77
- &self,78
- linked_chunk_id: LinkedChunkId<'_>,79
- before_chunk_identifier: ChunkIdentifier,80
- ) -> Result<Option<RawChunk<Event, Gap>>, Self::Error> {81
- self.inner.load_previous_chunk(linked_chunk_id, before_chunk_identifier).await82
- }83
-84
- async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error> {85
- self.inner.clear_all_linked_chunks().await86
- }87
-88
- async fn filter_duplicated_events(89
- &self,90
- linked_chunk_id: LinkedChunkId<'_>,91
- events: Vec<OwnedEventId>,92
- ) -> Result<Vec<(OwnedEventId, Position)>, Self::Error> {93
- self.inner.filter_duplicated_events(linked_chunk_id, events).await94
- }95
-96
- async fn find_event(97
- &self,98
- room_id: &RoomId,99
- event_id: &EventId,100
- ) -> Result<Option<Event>, Self::Error> {101
- self.inner.find_event(room_id, event_id).await102
- }103
-104
- async fn find_event_relations(105
- &self,106
- room_id: &RoomId,107
- event_id: &EventId,108
- filter: Option<&[RelationType]>,109
- ) -> Result<Vec<(Event, Option<Position>)>, Self::Error> {110
- self.inner.find_event_relations(room_id, event_id, filter).await111
- }112
-113
- async fn get_room_events(114
- &self,115
- room_id: &RoomId,116
- event_type: Option<&str>,117
- session_id: Option<&str>,118
- ) -> Result<Vec<Event>, Self::Error> {119
- self.inner.get_room_events(room_id, event_type, session_id).await120
- }121
-122
- async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error> {123
- self.inner.save_event(room_id, event).await124
- }125
-126
- async fn close(&self) -> Result<(), Self::Error> {127
- self.inner.close().await128
- }129
-130
- async fn reopen(&self) -> Result<(), Self::Error> {131
- self.inner.reopen().await132
- }133
-134
- async fn optimize(&self) -> Result<(), Self::Error> {135
- self.inner.optimize().await136
- }137
-138
- async fn get_size(&self) -> Result<Option<usize>, Self::Error> {139
- self.inner.get_size().await140
- }141
-}@@ -54,10 +54,13 @@ async fn test_recent_history_overlapping_a_limited_sync_drops_its_stale_gap() {
5454
let room = server.sync_joined_room(&client, room_id).await;5555
let (cache, _handles) = room.event_cache().await.unwrap();5656
let f = event_factory(room_id);57
- cache58
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)59
- .await60
- .unwrap();57
+ server58
+ .sync_room(59
+ &client,60
+ JoinedRoomBuilder::new(room_id)61
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),62
+ )63
+ .await;6164
server6265
.mock_room_messages()6366
.match_limit(50)@@ -91,10 +94,13 @@ async fn test_recent_history_ending_before_a_limited_sync_keeps_its_gap_after_th
9194
let room = server.sync_joined_room(&client, room_id).await;9295
let (cache, _handles) = room.event_cache().await.unwrap();9396
let f = event_factory(room_id);94
- cache95
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)96
- .await97
- .unwrap();97
+ server98
+ .sync_room(99
+ &client,100
+ JoinedRoomBuilder::new(room_id)101
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),102
+ )103
+ .await;98104
server99105
.mock_room_messages()100106
.match_limit(50)@@ -129,10 +135,13 @@ async fn test_recent_history_is_bounded_and_keeps_the_retained_window() {
129135
let room = server.sync_joined_room(&client, room_id).await;130136
let (cache, _handles) = room.event_cache().await.unwrap();131137
let f = event_factory(room_id);132
- cache133
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)134
- .await135
- .unwrap();138
+ server139
+ .sync_room(140
+ &client,141
+ JoinedRoomBuilder::new(room_id)142
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),143
+ )144
+ .await;136145
137146
server138147
.mock_room_messages()@@ -202,10 +211,13 @@ async fn test_recent_history_joins_at_the_retained_boundary() {
202211
let room = server.sync_joined_room(&client, room_id).await;203212
let (cache, _handles) = room.event_cache().await.unwrap();204213
let f = event_factory(room_id);205
- cache206
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)207
- .await208
- .unwrap();214
+ server215
+ .sync_room(216
+ &client,217
+ JoinedRoomBuilder::new(room_id)218
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),219
+ )220
+ .await;209221
server210222
.mock_room_messages()211223
.match_limit(50)@@ -242,10 +254,13 @@ async fn test_recent_history_keeps_sync_arrivals_during_a_fetch() {
242254
let room = server.sync_joined_room(&client, room_id).await;243255
let (cache, _handles) = room.event_cache().await.unwrap();244256
let f = event_factory(room_id);245
- cache246
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)247
- .await248
- .unwrap();257
+ server258
+ .sync_room(259
+ &client,260
+ JoinedRoomBuilder::new(room_id)261
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),262
+ )263
+ .await;249264
server250265
.mock_room_messages()251266
.match_limit(50)@@ -288,10 +303,13 @@ async fn test_recent_history_continues_empty_pages_and_pauses_on_a_cursor_cycle(
288303
let room = server.sync_joined_room(&client, room_id).await;289304
let (cache, _handles) = room.event_cache().await.unwrap();290305
let f = event_factory(room_id);291
- cache292
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)293
- .await294
- .unwrap();306
+ server307
+ .sync_room(308
+ &client,309
+ JoinedRoomBuilder::new(room_id)310
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),311
+ )312
+ .await;295313
server296314
.mock_room_messages()297315
.match_limit(50)@@ -343,10 +361,13 @@ async fn test_concurrent_recent_history_calls_share_the_request_and_gap() {
343361
let room = server.sync_joined_room(&client, room_id).await;344362
let (cache, _handles) = room.event_cache().await.unwrap();345363
let f = event_factory(room_id);346
- cache347
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)348
- .await349
- .unwrap();364
+ server365
+ .sync_room(366
+ &client,367
+ JoinedRoomBuilder::new(room_id)368
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),369
+ )370
+ .await;350371
server351372
.mock_room_messages()352373
.match_limit(50)@@ -377,10 +398,13 @@ async fn test_cancelling_one_recent_history_waiter_keeps_the_shared_request_aliv
377398
let room = server.sync_joined_room(&client, room_id).await;378399
let (cache, _handles) = room.event_cache().await.unwrap();379400
let f = event_factory(room_id);380
- cache381
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)382
- .await383
- .unwrap();401
+ server402
+ .sync_room(403
+ &client,404
+ JoinedRoomBuilder::new(room_id)405
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),406
+ )407
+ .await;384408
server385409
.mock_room_messages()386410
.match_limit(50)@@ -426,10 +450,13 @@ async fn test_cancelling_all_recent_history_waiters_allows_a_fresh_retry() {
426450
let room = server.sync_joined_room(&client, room_id).await;427451
let (cache, _handles) = room.event_cache().await.unwrap();428452
let f = event_factory(room_id);429
- cache430
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)431
- .await432
- .unwrap();453
+ server454
+ .sync_room(455
+ &client,456
+ JoinedRoomBuilder::new(room_id)457
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),458
+ )459
+ .await;433460
server434461
.mock_room_messages()435462
.match_limit(50)@@ -0,0 +1,174 @@
1
+use std::time::Duration;2
+3
+use eyeball_im::VectorDiff;4
+use matrix_sdk::{5
+ ThreadingSupport,6
+ event_cache::EventFocusThreadMode,7
+ ruma::{event_id, room_id},8
+ test_utils::mocks::{9
+ MatrixMockServer, RoomContextResponseTemplate, RoomMessagesResponseTemplate,10
+ },11
+};12
+use matrix_sdk_test::{ALICE, JoinedRoomBuilder, event_factory::EventFactory};13
+14
+#[tokio::test]15
+async fn clear_invalidates_focused_cache_clones_and_inflight_pagination() {16
+ let server = MatrixMockServer::new().await;17
+ let client = server.client_builder().build().await;18
+ let room_id = room_id!("!clear:localhost");19
+ client.event_cache().subscribe().unwrap();20
+ server.sync_joined_room(&client, room_id).await;21
+ let room = client.get_room(room_id).unwrap();22
+ let (cache, _handles) = room.event_cache().await.unwrap();23
+ let factory = EventFactory::new().room(room_id).sender(&ALICE);24
+ let focused_id = event_id!("$focused:localhost");25
+ server26
+ .mock_room_event_context()27
+ .ok(RoomContextResponseTemplate::new(28
+ factory.text_msg("focused").event_id(focused_id).into_event(),29
+ )30
+ .start("old-focused-cursor"))31
+ .mock_once()32
+ .mount()33
+ .await;34
+ let focused = cache35
+ .get_or_create_event_focused_cache(36
+ focused_id.to_owned(),37
+ 10,38
+ EventFocusThreadMode::Automatic,39
+ )40
+ .await41
+ .unwrap();42
+ let focused_clone = focused.clone();43
+ let (initial_events, mut updates) = focused.subscribe().await;44
+ assert_eq!(initial_events.len(), 1);45
+ assert_eq!(initial_events[0].event_id().as_deref(), Some(focused_id));46
+47
+ let stale_event_id = event_id!("$stale-focused:localhost");48
+ server49
+ .mock_room_messages()50
+ .match_from("old-focused-cursor")51
+ .ok(RoomMessagesResponseTemplate::default()52
+ .events(vec![factory.text_msg("stale").event_id(stale_event_id)])53
+ .end_token("stale-next-cursor")54
+ .with_delay(Duration::from_millis(300)))55
+ .mock_once()56
+ .mount()57
+ .await;58
+ let pagination = tokio::spawn({59
+ let focused = focused.clone();60
+ async move { focused.paginate_backwards(10).await }61
+ });62
+ tokio::time::timeout(Duration::from_secs(8), async {63
+ loop {64
+ if server65
+ .received_requests()66
+ .await67
+ .unwrap()68
+ .iter()69
+ .any(|request| request.url.path().ends_with("/messages"))70
+ {71
+ break;72
+ }73
+ tokio::task::yield_now().await;74
+ }75
+ })76
+ .await77
+ .unwrap();78
+79
+ tokio::time::timeout(Duration::from_secs(2), cache.clear()).await.unwrap().unwrap();80
+ let clear =81
+ tokio::time::timeout(Duration::from_secs(2), updates.recv()).await.unwrap().unwrap();82
+ assert!(matches!(clear.diffs.as_slice(), [VectorDiff::Clear]));83
+ tokio::time::timeout(Duration::from_secs(3), pagination).await.unwrap().unwrap().unwrap();84
+ let (cloned_events, _cloned_updates) = focused_clone.subscribe().await;85
+ assert!(cloned_events.is_empty());86
+ let result = focused_clone.paginate_backwards(10).await.unwrap();87
+ assert!(result.hit_end_of_timeline);88
+ let (events_after_pagination, _updates_after_pagination) = focused_clone.subscribe().await;89
+ assert!(events_after_pagination.is_empty());90
+}91
+92
+#[tokio::test]93
+async fn clear_empties_subscribed_thread_cache() {94
+ let server = MatrixMockServer::new().await;95
+ let client = server96
+ .client_builder()97
+ .on_builder(|builder| {98
+ builder.with_threading_support(ThreadingSupport::Enabled { with_subscriptions: false })99
+ })100
+ .build()101
+ .await;102
+ let room_id = room_id!("!clear:localhost");103
+ client.event_cache().subscribe().unwrap();104
+ let thread_root_id = event_id!("$thread-root:localhost");105
+ let thread_reply_id = event_id!("$thread-reply:localhost");106
+ let factory = EventFactory::new().room(room_id).sender(&ALICE);107
+ let room = server108
+ .sync_room(109
+ &client,110
+ JoinedRoomBuilder::new(room_id).add_timeline_event(111
+ factory112
+ .text_msg("thread reply")113
+ .in_thread(thread_root_id, thread_root_id)114
+ .event_id(thread_reply_id),115
+ ),116
+ )117
+ .await;118
+ let (cache, _handles) = room.event_cache().await.unwrap();119
+ let (initial_events, mut updates) =120
+ cache.subscribe_to_thread(thread_root_id.to_owned()).await.unwrap();121
+ assert_eq!(initial_events.len(), 1);122
+ assert_eq!(initial_events[0].event_id().as_deref(), Some(thread_reply_id));123
+124
+ cache.clear().await.unwrap();125
+ let clear =126
+ tokio::time::timeout(Duration::from_secs(2), updates.recv()).await.unwrap().unwrap();127
+ assert!(matches!(clear.diffs.as_slice(), [VectorDiff::Clear]));128
+ let (events_after_clear, _updates_after_clear) =129
+ cache.subscribe_to_thread(thread_root_id.to_owned()).await.unwrap();130
+ assert!(events_after_clear.is_empty());131
+}132
+133
+#[tokio::test]134
+async fn clear_empties_loaded_pinned_cache() {135
+ let server = MatrixMockServer::new().await;136
+ let room_id = room_id!("!clear:localhost");137
+ let pinned_event_id = event_id!("$pinned:localhost");138
+ let factory = EventFactory::new().room(room_id).sender(&ALICE);139
+ let pinned_event = factory.text_msg("pinned").event_id(pinned_event_id).into_event();140
+ server.mock_room_event().match_event_id().ok(pinned_event).mock_once().mount().await;141
+ let client = server.client_builder().build().await;142
+ client.event_cache().subscribe().unwrap();143
+ let room = server144
+ .sync_room(145
+ &client,146
+ JoinedRoomBuilder::new(room_id).add_state_bulk(vec![147
+ factory.room_pinned_events(vec![pinned_event_id.to_owned()]).into(),148
+ ]),149
+ )150
+ .await;151
+ let (cache, _handles) = room.event_cache().await.unwrap();152
+ let (initial_events, mut updates) = cache.subscribe_to_pinned_events().await.unwrap();153
+ let mut visible: imbl::Vector<_> = initial_events.into();154
+ tokio::time::timeout(Duration::from_secs(3), async {155
+ while visible.is_empty() {156
+ for diff in updates.recv().await.unwrap().diffs {157
+ diff.apply(&mut visible);158
+ }159
+ }160
+ })161
+ .await162
+ .unwrap();163
+ assert_eq!(visible.len(), 1);164
+ assert_eq!(visible[0].event_id().as_deref(), Some(pinned_event_id));165
+166
+ cache.clear().await.unwrap();167
+ let clear =168
+ tokio::time::timeout(Duration::from_secs(2), updates.recv()).await.unwrap().unwrap();169
+ assert!(matches!(clear.diffs.as_slice(), [VectorDiff::Clear]));170
+ for diff in clear.diffs {171
+ diff.apply(&mut visible);172
+ }173
+ assert!(visible.is_empty());174
+}@@ -13,7 +13,7 @@ use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
1313
use matrix_sdk_test::{JoinedRoomBuilder, async_test, event_factory::EventFactory};1414
use ruma::{event_id, room_id, user_id};1515
16
-#[path = "history_reseed/failing_store.rs"]16
+#[path = "support/failing_store.rs"]1717
mod failing_store;1818
1919
#[async_test]@@ -34,13 +34,14 @@ async fn test_segmented_history_resumes_after_restart_and_joins_only_at_boundary
3434
client.event_cache().subscribe().unwrap();3535
let room = server.sync_joined_room(&client, room_id).await;3636
let (cache, _handles) = room.event_cache().await.unwrap();37
- cache38
- .reseed(39
- vec![f.text_msg("retained").event_id(event_id!("$old")).into_event()],40
- Some("old-edge".into()),37
+ server38
+ .sync_room(39
+ &client,40
+ JoinedRoomBuilder::new(room_id)41
+ .add_timeline_event(f.text_msg("retained").event_id(event_id!("$old")))42
+ .set_timeline_prev_batch("old-edge"),4143
)42
- .await43
- .unwrap();44
+ .await;4445
let gap = cache4546
.append_history_section(4647
vec![f.text_msg("recent").event_id(event_id!("$new")).into_event()],@@ -129,10 +130,13 @@ async fn test_subscribe_with_history_reports_atomic_gap_snapshot_and_updates() {
129130
let room = server.sync_joined_room(&client, room_id).await;130131
let (cache, _handles) = room.event_cache().await.unwrap();131132
let f = EventFactory::new().room(room_id).sender(user_id!("@alice:example.org"));132
- cache133
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)134
- .await135
- .unwrap();133
+ server134
+ .sync_room(135
+ &client,136
+ JoinedRoomBuilder::new(room_id)137
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),138
+ )139
+ .await;136140
cache137141
.append_history_section(138142
vec![f.reaction(event_id!("$old"), "👍").event_id(event_id!("$hidden")).into_event()],@@ -195,13 +199,14 @@ async fn test_segmented_history_keeps_unavailable_intervals_and_independent_curs
195199
let room = server.sync_joined_room(&client, room_id).await;196200
let (cache, _handles) = room.event_cache().await.unwrap();197201
let f = EventFactory::new().room(room_id).sender(user_id!("@alice:example.org"));198
- cache199
- .reseed(200
- vec![f.text_msg("old").event_id(event_id!("$old")).into_event()],201
- Some("old-edge".into()),202
+ server203
+ .sync_room(204
+ &client,205
+ JoinedRoomBuilder::new(room_id)206
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old")))207
+ .set_timeline_prev_batch("old-edge"),202208
)203
- .await204
- .unwrap();209
+ .await;205210
let first = cache206211
.append_history_section(207212
vec![f.text_msg("middle").event_id(event_id!("$middle")).into_event()],@@ -261,13 +266,14 @@ async fn test_segmented_history_failed_commit_keeps_events_and_cursor_retryable(
261266
let room = server.sync_joined_room(&client, room_id).await;262267
let (cache, _handles) = room.event_cache().await.unwrap();263268
let f = EventFactory::new().room(room_id).sender(user_id!("@alice:example.org"));264
- cache265
- .reseed(266
- vec![f.text_msg("old").event_id(event_id!("$old")).into_event()],267
- Some("old-edge".into()),269
+ server270
+ .sync_room(271
+ &client,272
+ JoinedRoomBuilder::new(room_id)273
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old")))274
+ .set_timeline_prev_batch("old-edge"),268275
)269
- .await270
- .unwrap();276
+ .await;271277
store.fail_writes.store(true, Ordering::SeqCst);272278
assert!(273279
cache@@ -321,10 +327,13 @@ async fn test_segmented_history_request_failure_preserves_existing_progress() {
321327
let room = server.sync_joined_room(&client, room_id).await;322328
let (cache, _handles) = room.event_cache().await.unwrap();323329
let f = EventFactory::new().room(room_id).sender(user_id!("@alice:example.org"));324
- cache325
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)326
- .await327
- .unwrap();330
+ server331
+ .sync_room(332
+ &client,333
+ JoinedRoomBuilder::new(room_id)334
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),335
+ )336
+ .await;328337
let gap = cache329338
.append_history_section(330339
vec![f.text_msg("new").event_id(event_id!("$new")).into_event()],@@ -365,10 +374,13 @@ async fn test_segmented_history_cancellation_keeps_the_saved_cursor() {
365374
let room = server.sync_joined_room(&client, room_id).await;366375
let (cache, _handles) = room.event_cache().await.unwrap();367376
let f = EventFactory::new().room(room_id).sender(user_id!("@alice:example.org"));368
- cache369
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)370
- .await371
- .unwrap();377
+ server378
+ .sync_room(379
+ &client,380
+ JoinedRoomBuilder::new(room_id)381
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),382
+ )383
+ .await;372384
let gap = cache373385
.append_history_section(374386
vec![f.text_msg("new").event_id(event_id!("$new")).into_event()],@@ -423,10 +435,13 @@ async fn test_segmented_history_preserves_sync_arrivals_during_a_fill() {
423435
let room = server.sync_joined_room(&client, room_id).await;424436
let (cache, _handles) = room.event_cache().await.unwrap();425437
let f = EventFactory::new().room(room_id).sender(user_id!("@alice:example.org"));426
- cache427
- .reseed(vec![f.text_msg("old").event_id(event_id!("$old")).into_event()], None)428
- .await429
- .unwrap();438
+ server439
+ .sync_room(440
+ &client,441
+ JoinedRoomBuilder::new(room_id)442
+ .add_timeline_event(f.text_msg("old").event_id(event_id!("$old"))),443
+ )444
+ .await;430445
let gap = cache431446
.append_history_section(432447
vec![f.text_msg("new").event_id(event_id!("$new")).into_event()],@@ -0,0 +1,141 @@
1
+use std::sync::atomic::{AtomicBool, Ordering};2
+3
+use async_trait::async_trait;4
+use matrix_sdk_base::{5
+ event_cache::{6
+ Event, Gap,7
+ store::{EventCacheStore, EventCacheStoreError, MemoryStore},8
+ },9
+ linked_chunk::{10
+ ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId, Position,11
+ RawChunk, Update,12
+ },13
+};14
+use matrix_sdk_common::cross_process_lock::CrossProcessLockGeneration;15
+use ruma::{EventId, OwnedEventId, RoomId, events::relation::RelationType};16
+17
+#[derive(Debug, Default)]18
+pub struct FailingStore {19
+ pub inner: MemoryStore,20
+ pub fail_writes: AtomicBool,21
+}22
+23
+#[cfg_attr(target_family = "wasm", async_trait(?Send))]24
+#[cfg_attr(not(target_family = "wasm"), async_trait)]25
+impl EventCacheStore for FailingStore {26
+ type Error = EventCacheStoreError;27
+28
+ async fn try_take_leased_lock(29
+ &self,30
+ lease_duration_ms: u32,31
+ key: &str,32
+ holder: &str,33
+ ) -> Result<Option<CrossProcessLockGeneration>, Self::Error> {34
+ self.inner.try_take_leased_lock(lease_duration_ms, key, holder).await35
+ }36
+37
+ async fn handle_linked_chunk_updates(38
+ &self,39
+ linked_chunk_id: LinkedChunkId<'_>,40
+ updates: Vec<Update<Event, Gap>>,41
+ ) -> Result<(), Self::Error> {42
+ if self.fail_writes.load(Ordering::SeqCst) {43
+ return Err(EventCacheStoreError::InvalidData {44
+ details: "injected write failure".into(),45
+ });46
+ }47
+48
+ self.inner.handle_linked_chunk_updates(linked_chunk_id, updates).await49
+ }50
+51
+ async fn remove_room(&self, room_id: &RoomId) -> Result<(), Self::Error> {52
+ self.inner.remove_room(room_id).await53
+ }54
+55
+ async fn load_all_chunks(56
+ &self,57
+ linked_chunk_id: LinkedChunkId<'_>,58
+ ) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error> {59
+ self.inner.load_all_chunks(linked_chunk_id).await60
+ }61
+62
+ async fn load_all_chunks_metadata(63
+ &self,64
+ linked_chunk_id: LinkedChunkId<'_>,65
+ ) -> Result<Vec<ChunkMetadata>, Self::Error> {66
+ self.inner.load_all_chunks_metadata(linked_chunk_id).await67
+ }68
+69
+ async fn load_last_chunk(70
+ &self,71
+ linked_chunk_id: LinkedChunkId<'_>,72
+ ) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error> {73
+ self.inner.load_last_chunk(linked_chunk_id).await74
+ }75
+76
+ async fn load_previous_chunk(77
+ &self,78
+ linked_chunk_id: LinkedChunkId<'_>,79
+ before_chunk_identifier: ChunkIdentifier,80
+ ) -> Result<Option<RawChunk<Event, Gap>>, Self::Error> {81
+ self.inner.load_previous_chunk(linked_chunk_id, before_chunk_identifier).await82
+ }83
+84
+ async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error> {85
+ self.inner.clear_all_linked_chunks().await86
+ }87
+88
+ async fn filter_duplicated_events(89
+ &self,90
+ linked_chunk_id: LinkedChunkId<'_>,91
+ events: Vec<OwnedEventId>,92
+ ) -> Result<Vec<(OwnedEventId, Position)>, Self::Error> {93
+ self.inner.filter_duplicated_events(linked_chunk_id, events).await94
+ }95
+96
+ async fn find_event(97
+ &self,98
+ room_id: &RoomId,99
+ event_id: &EventId,100
+ ) -> Result<Option<Event>, Self::Error> {101
+ self.inner.find_event(room_id, event_id).await102
+ }103
+104
+ async fn find_event_relations(105
+ &self,106
+ room_id: &RoomId,107
+ event_id: &EventId,108
+ filter: Option<&[RelationType]>,109
+ ) -> Result<Vec<(Event, Option<Position>)>, Self::Error> {110
+ self.inner.find_event_relations(room_id, event_id, filter).await111
+ }112
+113
+ async fn get_room_events(114
+ &self,115
+ room_id: &RoomId,116
+ event_type: Option<&str>,117
+ session_id: Option<&str>,118
+ ) -> Result<Vec<Event>, Self::Error> {119
+ self.inner.get_room_events(room_id, event_type, session_id).await120
+ }121
+122
+ async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error> {123
+ self.inner.save_event(room_id, event).await124
+ }125
+126
+ async fn close(&self) -> Result<(), Self::Error> {127
+ self.inner.close().await128
+ }129
+130
+ async fn reopen(&self) -> Result<(), Self::Error> {131
+ self.inner.reopen().await132
+ }133
+134
+ async fn optimize(&self) -> Result<(), Self::Error> {135
+ self.inner.optimize().await136
+ }137
+138
+ async fn get_size(&self) -> Result<Option<usize>, Self::Error> {139
+ self.inner.get_size().await140
+ }141
+}