Commit 3c85630430e1
tarball6 files changed+141-52
@@ -56,18 +56,19 @@ pub(in crate::timeline) async fn pinned_events_task(
5656
// The updates might have lagged, but the room event cache might have5757
// events, so retrieve them and add them back again to the timeline,5858
// after clearing it.59
- let (initial_events, _) = match room_event_cache.subscribe_to_pinned_events().await60
- {61
- Ok(initial_events) => initial_events,62
- Err(err) => {63
- error!(64
- ?err,65
- "Failed to replace the initial remote events in the event cache"66
- );67
- break;68
- }69
- };59
+ let (initial_events, recv) =60
+ match room_event_cache.subscribe_to_pinned_events().await {61
+ Ok(initial_events) => initial_events,62
+ Err(err) => {63
+ error!(64
+ ?err,65
+ "Failed to replace the initial remote events in the event cache"66
+ );67
+ break;68
+ }69
+ };7070
71
+ pinned_events_recv = recv;7172
timeline_controller7273
.replace_with_initial_remote_events(initial_events, RemoteEventOrigin::Cache)7374
.await;@@ -130,7 +131,8 @@ pub(in crate::timeline) async fn event_focused_task(
130131
}131132
};132133
133
- let (initial_events, _) = cache.subscribe().await;134
+ let (initial_events, recv) = cache.subscribe().await;135
+ event_focused_events_recv = recv;134136
135137
timeline_controller136138
.replace_with_initial_remote_events(initial_events, RemoteEventOrigin::Cache)@@ -46,7 +46,7 @@ use tokio::sync::{
4646
RwLock,4747
broadcast::{Receiver, Sender},4848
};49
-use tracing::{instrument, trace};49
+use tracing::{instrument, trace, warn};5050
5151
#[cfg(feature = "e2e-encryption")]5252
use crate::event_cache::redecryptor::ResolvedUtd;@@ -108,6 +108,9 @@ struct EventFocusedCacheInner {
108108
/// The pagination mode (room or thread).109109
pagination_mode: EventFocusedPaginationMode,110110
111
+ /// How the cache was started, so a clear can start it again.112
+ context: Option<(u16, EventFocusThreadMode)>,113
+111114
/// The linked chunk for this event-focused cache.112115
chunk: EventLinkedChunk,113116
@@ -145,14 +148,28 @@ impl EventFocusedCacheInner {
145148
room: Room,146149
num_context_events: u16,147150
thread_mode: EventFocusThreadMode,148
- ) -> Result<StartFromResult> {151
+ ) -> Result<Option<StartFromResult>> {149152
trace!(num_context_events, "fetching event with context via /context");150153
154
+ let invalidation_lock = self.invalidation_lock.clone();155
+ let generation = {156
+ let _guard = invalidation_lock.lock().unwrap();157
+ self.synchronize_generation();158
+ self.loaded_generation159
+ };160
+151161
let paginator = Paginator::new(room);152162
153163
let result =154164
paginator.start_from(&self.focused_event_id, UInt::from(num_context_events)).await?;155165
166
+ let _invalidation_guard = invalidation_lock.lock().unwrap();167
+ if generation != self.generation.load(Ordering::SeqCst) {168
+ self.synchronize_generation();169
+ return Ok(None);170
+ }171
+ self.context = Some((num_context_events, thread_mode));172
+156173
// Detect if the focused event is part of a thread.157174
let thread_root = match thread_mode {158175
EventFocusThreadMode::ForceThread => {@@ -253,16 +270,11 @@ impl EventFocusedCacheInner {
253270
254271
self.propagate_changes();255272
256
- // Empty the updates_as_vector_diffs(), since it's impossible for an observer to257
- // have subscribed to this cache yet, since this code is part of the constructor258
- // flow.259
- //260
- // If we didn't empty those, such initial updates would be duplicated, since the261
- // subscriber would get the full initial list of events as diffs and as a set of262
- // initial events.263
- let _ = self.chunk.updates_as_vector_diffs();273
+ // Draining the diffs keeps a later subscriber from seeing the initial events274
+ // twice, and refills subscribers that saw a clear.275
+ self.notify_subscribers(EventsOrigin::Cache);264276
265
- Ok(result)277
+ Ok(Some(result))266278
}267279
268280
/// Add initial events to the chunk, with gaps for pagination tokens.@@ -357,7 +369,7 @@ impl EventFocusedCacheInner {
357369
let _invalidation_guard = invalidation_lock.lock().unwrap();358370
if generation != self.generation.load(Ordering::SeqCst) {359371
self.synchronize_generation();360
- return Ok(PaginationResult { events: Vec::new(), hit_end_of_timeline: true });372
+ return Ok(PaginationResult { events: Vec::new(), hit_end_of_timeline: false });361373
}362374
363375
let (mut events, new_token) = pagination?;@@ -484,7 +496,7 @@ impl EventFocusedCacheInner {
484496
let _invalidation_guard = invalidation_lock.lock().unwrap();485497
if generation != self.generation.load(Ordering::SeqCst) {486498
self.synchronize_generation();487
- return Ok(PaginationResult { events: Vec::new(), hit_end_of_timeline: true });499
+ return Ok(PaginationResult { events: Vec::new(), hit_end_of_timeline: false });488500
}489501
490502
let (events, new_token) = pagination?;@@ -599,6 +611,7 @@ impl EventFocusedCache {
599611
room,600612
focused_event_id,601613
pagination_mode: EventFocusedPaginationMode::Room { hide_thread_events: false },614
+ context: None,602615
chunk: EventLinkedChunk::new(),603616
sender: sender.clone(),604617
linked_chunk_update_sender: linked_chunk_update_sender.clone(),@@ -611,7 +624,26 @@ impl EventFocusedCache {
611624
}612625
}613626
627
+ /// Empty this cache, keeping its subscribers, and start it again from its628
+ /// focused event in the background.614629
pub(super) fn clear(&self) {630
+ self.invalidate();631
+ let cache = self.clone();632
+ matrix_sdk_common::executor::spawn(async move {633
+ let mut inner = cache.inner.write().await;634
+ let Some((num_context_events, thread_mode)) = inner.context else {635
+ return;636
+ };637
+ let Some(room) = inner.room.get() else {638
+ return;639
+ };640
+ if let Err(error) = inner.start_from(room, num_context_events, thread_mode).await {641
+ warn!(%error, "failed to restart the event-focused cache after a clear");642
+ }643
+ });644
+ }645
+646
+ pub(super) fn invalidate(&self) {615647
let _guard = self.invalidation_lock.lock().unwrap();616648
self.generation.fetch_add(1, Ordering::SeqCst);617649
let _ = self.sender.send(TimelineVectorDiffs {@@ -660,7 +692,7 @@ impl EventFocusedCache {
660692
room: Room,661693
num_context_events: u16,662694
thread_mode: EventFocusThreadMode,663
- ) -> Result<StartFromResult> {695
+ ) -> Result<Option<StartFromResult>> {664696
self.inner.write().await.start_from(room, num_context_events, thread_mode).await665697
}666698
@@ -12,7 +12,11 @@
1212
// See the License for the specific language governing permissions and1313
// limitations under the License.1414
15
-use std::{cmp::Ordering, collections::BTreeSet, sync::Arc};15
+use std::{16
+ cmp::Ordering,17
+ collections::BTreeSet,18
+ sync::{Arc, Mutex},19
+};1620
1721
use futures_util::{StreamExt as _, stream};1822
use matrix_sdk_base::{@@ -209,9 +213,11 @@ impl PinnedEventCacheState {
209213
pub struct PinnedEventCache {210214
state: Arc<PinnedEventCacheStateLock>,211215
216
+ room: WeakRoom,217
+212218
/// The task handling the refreshing of pinned events for this specific213219
/// room.214
- _task: Arc<BackgroundTaskHandle>,220
+ task: Arc<Mutex<BackgroundTaskHandle>>,215221
}216222
217223
impl PinnedEventCache {@@ -236,18 +242,21 @@ impl PinnedEventCache {
236242
store,237243
};238244
let state = Arc::new(PinnedEventCacheStateLock::new_inner(state));245
+ let weak_room =246
+ WeakRoom::new(WeakClient::from_client(&room.client()), room.room_id().to_owned());247
+ let task = Arc::new(Mutex::new(Self::spawn_listener(room, state.clone())));239248
240
- let task = Arc::new(241
- room.client()242
- .task_monitor()243
- .spawn_infinite_task(244
- "pinned_event_listener_task",245
- Self::pinned_event_listener_task(room, state.clone()),246
- )247
- .abort_on_drop(),248
- );249
-250
- Self { state, _task: task }249
+ Self { state, room: weak_room, task }250
+ }251
+252
+ fn spawn_listener(room: Room, state: Arc<PinnedEventCacheStateLock>) -> BackgroundTaskHandle {253
+ room.client()254
+ .task_monitor()255
+ .spawn_infinite_task(256
+ "pinned_event_listener_task",257
+ Self::pinned_event_listener_task(room, state),258
+ )259
+ .abort_on_drop()251260
}252261
253262
/// Subscribe to live events from this room's pinned events cache.@@ -260,13 +269,20 @@ impl PinnedEventCache {
260269
Ok((events, recv))261270
}262271
272
+ /// Empty this cache, keeping its subscribers, and reload the pinned events273
+ /// as a fresh listener would.263274
pub(super) async fn clear(&self) -> Result<()> {264
- self._task.abort();275
+ self.task.lock().unwrap().abort();265276
let mut guard = self.state.write().await?;266277
guard.state.generation += 1;267278
guard.state.chunk.reset();268279
guard.propagate_changes().await?;269280
guard.notify_subscribers(EventsOrigin::Cache);281
+ drop(guard);282
+283
+ if let Some(room) = self.room.get() {284
+ *self.task.lock().unwrap() = Self::spawn_listener(room, self.state.clone());285
+ }270286
271287
Ok(())272288
}@@ -231,7 +231,7 @@ impl RoomEventCache {
231231
232232
let mut guard = self.inner.state.write().await?;233233
if self.inner.generation.load(Ordering::SeqCst) != generation {234
- cache.clear();234
+ cache.invalidate();235235
continue;236236
}237237
@@ -862,10 +862,10 @@ impl<'a> RoomEventCacheStateLockWriteGuard<'a> {
862862
}863863
864864
pub async fn clear_loaded_views(&mut self) -> Result<(), EventCacheError> {865
- for (_, focused) in self.state.event_focused_caches.drain() {865
+ for focused in self.state.event_focused_caches.values() {866866
focused.clear();867867
}868
- if let Some(pinned) = self.state.pinned_event_cache.take() {868
+ if let Some(pinned) = self.state.pinned_event_cache.get() {869869
pinned.clear().await?;870870
}871871
for thread in self.state.threads.values_mut() {@@ -12,7 +12,7 @@ use matrix_sdk::{
1212
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, event_factory::EventFactory};1313
1414
#[tokio::test]15
-async fn clear_invalidates_focused_cache_clones_and_inflight_pagination() {15
+async fn clear_restarts_focused_cache_and_fences_inflight_pagination() {1616
let server = MatrixMockServer::new().await;1717
let client = server.client_builder().build().await;1818
let room_id = room_id!("!clear:localhost");@@ -76,17 +76,45 @@ async fn clear_invalidates_focused_cache_clones_and_inflight_pagination() {
7676
.await7777
.unwrap();7878
79
+ server80
+ .mock_room_event_context()81
+ .ok(RoomContextResponseTemplate::new(82
+ factory.text_msg("focused").event_id(focused_id).into_event(),83
+ )84
+ .start("fresh-focused-cursor"))85
+ .mock_once()86
+ .mount()87
+ .await;7988
tokio::time::timeout(Duration::from_secs(2), cache.clear()).await.unwrap().unwrap();8089
let clear =8190
tokio::time::timeout(Duration::from_secs(2), updates.recv()).await.unwrap().unwrap();8291
assert!(matches!(clear.diffs.as_slice(), [VectorDiff::Clear]));83
- tokio::time::timeout(Duration::from_secs(3), pagination).await.unwrap().unwrap().unwrap();92
+ let interrupted =93
+ tokio::time::timeout(Duration::from_secs(3), pagination).await.unwrap().unwrap().unwrap();94
+ assert!(interrupted.events.is_empty());95
+ assert!(!interrupted.hit_end_of_timeline);96
+97
+ let mut visible = imbl::Vector::new();98
+ tokio::time::timeout(Duration::from_secs(3), async {99
+ while visible.is_empty() {100
+ for diff in updates.recv().await.unwrap().diffs {101
+ diff.apply(&mut visible);102
+ }103
+ }104
+ })105
+ .await106
+ .unwrap();107
+ let ids: Vec<_> = visible.iter().filter_map(|event| event.event_id()).collect();108
+ assert_eq!(ids, vec![focused_id]);84109
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());110
+ assert_eq!(cloned_events.len(), 1);111
+ assert!(112
+ cache113
+ .get_event_focused_cache(focused_id.to_owned(), EventFocusThreadMode::Automatic)114
+ .await115
+ .unwrap()116
+ .is_some()117
+ );90118
}91119
92120
#[tokio::test]@@ -131,13 +159,13 @@ async fn clear_empties_subscribed_thread_cache() {
131159
}132160
133161
#[tokio::test]134
-async fn clear_empties_loaded_pinned_cache() {162
+async fn clear_reloads_loaded_pinned_cache() {135163
let server = MatrixMockServer::new().await;136164
let room_id = room_id!("!clear:localhost");137165
let pinned_event_id = event_id!("$pinned:localhost");138166
let factory = EventFactory::new().room(room_id).sender(&ALICE);139167
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;168
+ server.mock_room_event().match_event_id().ok(pinned_event).mount().await;141169
let client = server.client_builder().build().await;142170
client.event_cache().subscribe().unwrap();143171
let room = server@@ -171,4 +199,15 @@ async fn clear_empties_loaded_pinned_cache() {
171199
diff.apply(&mut visible);172200
}173201
assert!(visible.is_empty());202
+ tokio::time::timeout(Duration::from_secs(3), async {203
+ while visible.is_empty() {204
+ for diff in updates.recv().await.unwrap().diffs {205
+ diff.apply(&mut visible);206
+ }207
+ }208
+ })209
+ .await210
+ .unwrap();211
+ assert_eq!(visible.len(), 1);212
+ assert_eq!(visible[0].event_id().as_deref(), Some(pinned_event_id));174213
}