@@ -42,6 +42,7 @@ use matrix_sdk_base::{
42
42
store:: { EventCacheStoreError , EventCacheStoreLock } ,
43
43
Gap ,
44
44
} ,
45
+ executor:: AbortOnDrop ,
45
46
linked_chunk:: { self , lazy_loader:: LazyLoaderError , OwnedLinkedChunkId } ,
46
47
store_locks:: LockStoreError ,
47
48
sync:: RoomUpdates ,
@@ -173,6 +174,13 @@ impl EventCache {
173
174
let ( room_event_cache_generic_update_sender, _) = channel ( 32 ) ;
174
175
let ( linked_chunk_update_sender, _) = channel ( 32 ) ;
175
176
177
+ let ( thread_subscriber_sender, thread_subscriber_receiver) = channel ( 32 ) ;
178
+ let thread_subscriber_task = AbortOnDrop :: new ( spawn ( Self :: thread_subscriber_task (
179
+ client. clone ( ) ,
180
+ linked_chunk_update_sender. clone ( ) ,
181
+ thread_subscriber_sender. clone ( ) ,
182
+ ) ) ) ;
183
+
176
184
Self {
177
185
inner : Arc :: new ( EventCacheInner {
178
186
client,
@@ -183,10 +191,20 @@ impl EventCache {
183
191
auto_shrink_sender : Default :: default ( ) ,
184
192
room_event_cache_generic_update_sender,
185
193
linked_chunk_update_sender,
194
+ _thread_subscriber_task : thread_subscriber_task,
195
+ thread_subscriber_receiver,
186
196
} ) ,
187
197
}
188
198
}
189
199
200
+ /// Subscribes to updates that a thread subscription has been sent.
201
+ ///
202
+ /// For testing purposes only.
203
+ #[ doc( hidden) ]
204
+ pub fn subscribe_thread_subscriber_updates ( & self ) -> Receiver < ( ) > {
205
+ self . inner . thread_subscriber_receiver . resubscribe ( )
206
+ }
207
+
190
208
/// Starts subscribing the [`EventCache`] to sync responses, if not done
191
209
/// before.
192
210
///
@@ -398,6 +416,115 @@ impl EventCache {
398
416
pub fn subscribe_to_room_generic_updates ( & self ) -> Receiver < RoomEventCacheGenericUpdate > {
399
417
self . inner . room_event_cache_generic_update_sender . subscribe ( )
400
418
}
419
+
420
+ #[ instrument( skip_all) ]
421
+ async fn thread_subscriber_task (
422
+ client : WeakClient ,
423
+ linked_chunk_update_sender : Sender < RoomEventCacheLinkedChunkUpdate > ,
424
+ thread_subscriber_sender : Sender < ( ) > ,
425
+ ) {
426
+ if client. get ( ) . map_or ( false , |client| !client. enabled_thread_subscriptions ( ) ) {
427
+ trace ! ( "Not spawning the thread subscriber task, because the client is shutting down or is not interested in those" ) ;
428
+ return ;
429
+ }
430
+
431
+ let mut rx = linked_chunk_update_sender. subscribe ( ) ;
432
+
433
+ loop {
434
+ match rx. recv ( ) . await {
435
+ Ok ( up) => {
436
+ let Some ( client) = client. get ( ) else {
437
+ // Client shutting down.
438
+ debug ! ( "Client is shutting down, exiting thread subscriber task" ) ;
439
+ break ;
440
+ } ;
441
+
442
+ let OwnedLinkedChunkId :: Thread ( room_id, thread_root) = & up. linked_chunk else {
443
+ trace ! ( "received an update for a non-thread linked chunk, ignoring" ) ;
444
+ continue ;
445
+ } ;
446
+
447
+ let Some ( room) = client. get_room ( & room_id) else {
448
+ warn ! ( %room_id, "unknown room" ) ;
449
+ continue ;
450
+ } ;
451
+
452
+ let thread_root = thread_root. clone ( ) ;
453
+
454
+ let new_events = up. events ( ) ;
455
+ if new_events. is_empty ( ) {
456
+ // No new events, nothing to do.
457
+ continue ;
458
+ }
459
+
460
+ // This `PushContext` is going to be used to compute whether an in-thread event
461
+ // would trigger a mention.
462
+ //
463
+ // Of course, we're not interested in an in-thread event causing a mention,
464
+ // because it's part of a thread we've subscribed to. So the
465
+ // `PushContext` must not include the check for thread subscriptions (otherwise
466
+ // it would be impossible to subscribe to new threads).
467
+
468
+ let with_thread_subscriptions = false ;
469
+
470
+ let Some ( push_context) = room
471
+ . push_context_internal ( with_thread_subscriptions)
472
+ . await
473
+ . inspect_err ( |err| {
474
+ warn ! ( "Failed to get push context for threads: {err}" ) ;
475
+ } )
476
+ . ok ( )
477
+ . flatten ( )
478
+ else {
479
+ warn ! ( "Missing push context for thread subscriptions." ) ;
480
+ continue ;
481
+ } ;
482
+
483
+ let mut subscribe_up_to = None ;
484
+
485
+ // Find if there's an event that would trigger a mention for the current
486
+ // user, iterating from the end of the new events towards the oldest,
487
+ for ev in new_events. into_iter ( ) . rev ( ) {
488
+ if push_context
489
+ . for_event ( ev. raw ( ) )
490
+ . await
491
+ . into_iter ( )
492
+ . any ( |action| action. should_notify ( ) )
493
+ {
494
+ let Some ( event_id) = ev. event_id ( ) else {
495
+ // Shouldn't happen.
496
+ continue ;
497
+ } ;
498
+ subscribe_up_to = Some ( event_id. to_owned ( ) ) ;
499
+ break ;
500
+ }
501
+ }
502
+
503
+ // And if we've found such a mention, subscribe to the thread up to this
504
+ // event.
505
+ if let Some ( event_id) = subscribe_up_to {
506
+ trace ! ( thread = %thread_root, up_to = %event_id, "found a new thread to subscribe to" ) ;
507
+ if let Err ( err) =
508
+ room. subscribe_thread_if_needed ( & thread_root, Some ( event_id) ) . await
509
+ {
510
+ warn ! ( %err, "Failed to subscribe to thread" ) ;
511
+ } else {
512
+ let _ = thread_subscriber_sender. send ( ( ) ) ;
513
+ }
514
+ }
515
+ }
516
+
517
+ Err ( RecvError :: Closed ) => {
518
+ debug ! ( "Linked chunk update channel has been closed, exiting thread subscriber task" ) ;
519
+ break ;
520
+ }
521
+
522
+ Err ( RecvError :: Lagged ( num_skipped) ) => {
523
+ warn ! ( num_skipped, "Lagged behind linked chunk updates" ) ;
524
+ }
525
+ }
526
+ }
527
+ }
401
528
}
402
529
403
530
struct EventCacheInner {
@@ -443,6 +570,21 @@ struct EventCacheInner {
443
570
///
444
571
/// See doc comment of [`RoomEventCacheLinkedChunkUpdate`].
445
572
linked_chunk_update_sender : Sender < RoomEventCacheLinkedChunkUpdate > ,
573
+
574
+ /// A background task listening to room and send queue updates, and
575
+ /// automatically subscribing the user to threads when needed, based on
576
+ /// the semantics of MSC4306.
577
+ ///
578
+ /// One important constraint is that there is only one such task per
579
+ /// [`EventCache`], so it does listen to *all* rooms at the same time.
580
+ _thread_subscriber_task : AbortOnDrop < ( ) > ,
581
+
582
+ /// A test helper receiver that will be emitted every time the thread
583
+ /// subscriber task subscribed to a new thread.
584
+ ///
585
+ /// This is helpful for tests to coordinate that a new thread subscription
586
+ /// has been sent or not.
587
+ thread_subscriber_receiver : Receiver < ( ) > ,
446
588
}
447
589
448
590
type AutoShrinkChannelPayload = OwnedRoomId ;
0 commit comments