1use std::{
16 collections::HashMap,
17 ops::ControlFlow,
18 sync::{Arc, Weak},
19};
20
21use eyeball::Subscriber;
22use matrix_sdk_base::{
23 linked_chunk::OwnedLinkedChunkId, serde_helpers::extract_thread_root_from_content,
24 sync::RoomUpdates,
25};
26use ruma::{OwnedEventId, OwnedTransactionId, RoomId};
27use tokio::{
28 select,
29 sync::{
30 OwnedRwLockReadGuard,
31 broadcast::{Receiver, Sender, error::RecvError},
32 mpsc,
33 },
34};
35use tracing::{Instrument as _, Span, debug, error, info, info_span, instrument, trace, warn};
36
37use super::{
38 AutoShrinkMessage, Caches, CachesByRoom, EventCacheError, EventCacheInner,
39 RoomEventCacheLinkedChunkUpdate,
40};
41use crate::{
42 client::WeakClient,
43 send_queue::{LocalEchoContent, RoomSendQueueUpdate, SendQueueUpdate},
44};
45
46#[instrument(skip_all)]
48pub(super) async fn room_updates_task(
49 inner: Arc<EventCacheInner>,
50 mut room_updates_feed: Receiver<RoomUpdates>,
51) {
52 trace!("Spawning the listen task");
53 loop {
54 match room_updates_feed.recv().await {
55 Ok(updates) => {
56 trace!("Receiving `RoomUpdates`");
57
58 if let Err(err) = inner.handle_room_updates(updates).await {
59 match err {
60 EventCacheError::ClientDropped => {
61 info!(
63 "Closing the event cache global listen task because client dropped"
64 );
65 break;
66 }
67 err => {
68 error!("Error when handling room updates: {err}");
69 }
70 }
71 }
72 }
73
74 Err(RecvError::Lagged(num_skipped)) => {
75 warn!(num_skipped, "Lagged behind room updates, clearing all rooms");
79 if let Err(err) = inner.clear_all_rooms().await {
80 error!("when clearing storage after lag in listen_task: {err}");
81 }
82 }
83
84 Err(RecvError::Closed) => {
85 info!("Closing the event cache global listen task because receiver closed");
87 break;
88 }
89 }
90 }
91}
92
93#[instrument(skip_all)]
96pub(super) async fn ignore_user_list_update_task(
97 inner: Arc<EventCacheInner>,
98 mut ignore_user_list_stream: Subscriber<Vec<String>>,
99) {
100 let span = info_span!(parent: Span::none(), "ignore_user_list_update_task");
101 span.follows_from(Span::current());
102
103 async move {
104 while ignore_user_list_stream.next().await.is_some() {
105 info!("Received an ignore user list change");
106
107 if let Err(err) = inner.clear_all_rooms().await {
108 error!("when clearing room storage after ignore user list change: {err}");
109 }
110 }
111
112 info!("Ignore user list stream has closed");
113 }
114 .instrument(span)
115 .await;
116}
117
118#[instrument(skip_all)]
135pub(super) async fn auto_shrink_linked_chunk_task(
136 inner: Weak<EventCacheInner>,
137 mut auto_shrink_receiver: mpsc::Receiver<AutoShrinkMessage>,
138) {
139 while let Some(message) = auto_shrink_receiver.recv().await {
140 trace!(?message, "received notification to shrink");
141
142 let Some(inner) = inner.upgrade() else {
143 return;
144 };
145
146 let maybe_diffs = match message {
147 AutoShrinkMessage::Room { room_id } => {
148 let ControlFlow::Continue(caches) = all_caches(inner.as_ref(), &room_id).await
149 else {
150 continue;
151 };
152
153 match caches.room().state().write().await {
154 Ok(mut state) => state.auto_shrink_if_no_subscribers().await,
155 Err(err) => {
156 warn!(%room_id, ?err, "Failed to get the `RoomEventCacheStateLock`");
157 continue;
158 }
159 }
160 }
161
162 AutoShrinkMessage::Thread { room_id, thread_id } => {
163 let ControlFlow::Continue(caches) = all_caches(inner.as_ref(), &room_id).await
164 else {
165 continue;
166 };
167
168 let Ok(cache) = caches.thread(thread_id.clone()).await else {
169 warn!(%room_id, %thread_id, "Failed to get the `ThreadEventCache`");
170 continue;
171 };
172
173 match cache.state().write().await {
174 Ok(mut state) => state.auto_shrink_if_no_subscribers().await,
175 Err(err) => {
176 warn!(%room_id, ?err, "Failed to get the `ThreadEventCacheStateLock`");
177 continue;
178 }
179 }
180 }
181 };
182
183 match maybe_diffs {
184 Ok(_diffs) => {
185 }
197
198 Err(err) => {
199 warn!(?err, "error when attempting to shrink linked chunk");
201 }
202 }
203 }
204
205 info!("Auto-shrink linked chunk task has been closed, exiting");
206
207 async fn all_caches(
208 inner: &EventCacheInner,
209 room_id: &RoomId,
210 ) -> ControlFlow<(), OwnedRwLockReadGuard<CachesByRoom, Caches>> {
211 match inner.all_caches_for_room(room_id).await {
212 Ok(caches) => ControlFlow::Continue(caches),
213 Err(err) => {
214 warn!(?err, "Failed to get the `Caches`");
215 ControlFlow::Break(())
216 }
217 }
218 }
219}
220
221#[instrument(skip_all)]
224pub(super) async fn thread_subscriber_task(
225 client: WeakClient,
226 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
227 thread_subscriber_sender: Sender<()>,
228) {
229 let mut send_q_rx = if let Some(client) = client.get() {
230 match client.enabled_thread_subscriptions().await {
231 Ok(enabled) => {
232 if !enabled {
233 trace!(
234 "Thread subscriptions are not enabled, not spawning thread subscriber task"
235 );
236 return;
237 }
238 }
239
240 Err(err) => {
241 warn!(%err, "Failed to get whether thread subscriptions are enabled, not spawning thread subscriber task");
242 return;
243 }
244 }
245
246 client.send_queue().subscribe()
247 } else {
248 trace!("Client is shutting down, not spawning thread subscriber task");
249 return;
250 };
251
252 let mut linked_chunk_rx = linked_chunk_update_sender.subscribe();
253
254 let mut events_being_sent = HashMap::new();
259
260 loop {
261 select! {
262 res = send_q_rx.recv() => {
263 match res {
264 Ok(up) => {
265 if !handle_thread_subscriber_send_queue_update(&client, &thread_subscriber_sender, &mut events_being_sent, up).await {
266 break;
267 }
268 }
269 Err(RecvError::Closed) => {
270 debug!("Linked chunk update channel has been closed, exiting thread subscriber task");
271 break;
272 }
273 Err(RecvError::Lagged(num_skipped)) => {
274 warn!(num_skipped, "Lagged behind linked chunk updates");
275 }
276 }
277 }
278
279 res = linked_chunk_rx.recv() => {
280 match res {
281 Ok(up) => {
282 if !handle_thread_subscriber_linked_chunk_update(&client, &thread_subscriber_sender, up).await {
283 break;
284 }
285 }
286 Err(RecvError::Closed) => {
287 debug!("Linked chunk update channel has been closed, exiting thread subscriber task");
288 break;
289 }
290 Err(RecvError::Lagged(num_skipped)) => {
291 warn!(num_skipped, "Lagged behind linked chunk updates");
292 }
293 }
294 }
295 }
296 }
297}
298
299#[instrument(skip(client, thread_subscriber_sender))]
306async fn handle_thread_subscriber_send_queue_update(
307 client: &WeakClient,
308 thread_subscriber_sender: &Sender<()>,
309 events_being_sent: &mut HashMap<OwnedTransactionId, OwnedEventId>,
310 up: SendQueueUpdate,
311) -> bool {
312 let Some(client) = client.get() else {
313 debug!("Client is shutting down, exiting thread subscriber task");
315 return false;
316 };
317
318 let room_id = up.room_id;
319 let Some(room) = client.get_room(&room_id) else {
320 warn!(%room_id, "unknown room");
321 return true;
322 };
323
324 let (thread_root, subscribe_up_to) = match up.update {
325 RoomSendQueueUpdate::NewLocalEvent(local_echo) => {
326 match local_echo.content {
327 LocalEchoContent::Event { serialized_event, .. } => {
328 if let Some(thread_root) =
329 extract_thread_root_from_content(serialized_event.into_raw().0)
330 {
331 events_being_sent.insert(local_echo.transaction_id, thread_root);
332 }
333 }
334 LocalEchoContent::React { .. } => {
335 }
338
339 LocalEchoContent::Redaction { .. } => {
340 }
343 }
344 return true;
345 }
346
347 RoomSendQueueUpdate::CancelledLocalEvent { transaction_id } => {
348 events_being_sent.remove(&transaction_id);
349 return true;
350 }
351
352 RoomSendQueueUpdate::ReplacedLocalEvent { transaction_id, new_content } => {
353 if let Some(thread_root) = extract_thread_root_from_content(new_content.into_raw().0) {
354 events_being_sent.insert(transaction_id, thread_root);
355 } else {
356 events_being_sent.remove(&transaction_id);
359 }
360 return true;
361 }
362
363 RoomSendQueueUpdate::SentEvent { transaction_id, event_id } => {
364 if let Some(thread_root) = events_being_sent.remove(&transaction_id) {
365 (thread_root, event_id)
366 } else {
367 trace!(%transaction_id, "received a sent event that we didn't know about, ignoring");
369 return true;
370 }
371 }
372
373 RoomSendQueueUpdate::SendError { .. }
374 | RoomSendQueueUpdate::RetryEvent { .. }
375 | RoomSendQueueUpdate::MediaUpload { .. } => {
376 return true;
378 }
379 };
380
381 trace!(thread = %thread_root, up_to = %subscribe_up_to, "found a new thread to subscribe to");
383
384 if let Err(err) = room.subscribe_thread_if_needed(&thread_root, Some(subscribe_up_to)).await {
385 warn!(%err, "Failed to subscribe to thread");
386 } else {
387 let _ = thread_subscriber_sender.send(());
388 }
389
390 true
391}
392
393#[instrument(skip(client, thread_subscriber_sender))]
400async fn handle_thread_subscriber_linked_chunk_update(
401 client: &WeakClient,
402 thread_subscriber_sender: &Sender<()>,
403 up: RoomEventCacheLinkedChunkUpdate,
404) -> bool {
405 let Some(client) = client.get() else {
406 debug!("Client is shutting down, exiting thread subscriber task");
408 return false;
409 };
410
411 let OwnedLinkedChunkId::Thread(room_id, thread_root) = &up.linked_chunk_id else {
412 trace!("received an update for a non-thread linked chunk, ignoring");
413 return true;
414 };
415
416 let Some(room) = client.get_room(room_id) else {
417 warn!(%room_id, "unknown room");
418 return true;
419 };
420
421 let thread_root = thread_root.clone();
422
423 let mut new_events = up.events().peekable();
424
425 if new_events.peek().is_none() {
426 return true;
428 }
429
430 let with_thread_subscriptions = false;
439
440 let Some(push_context) = room
441 .push_context_internal(with_thread_subscriptions)
442 .await
443 .inspect_err(|err| {
444 warn!("Failed to get push context for threads: {err}");
445 })
446 .ok()
447 .flatten()
448 else {
449 warn!("Missing push context for thread subscriptions.");
450 return true;
451 };
452
453 let mut subscribe_up_to = None;
454
455 for ev in new_events.rev() {
459 if push_context.for_event(ev.raw()).await.into_iter().any(|action| action.should_notify()) {
460 let Some(event_id) = ev.event_id() else {
461 continue;
463 };
464 subscribe_up_to = Some(event_id.to_owned());
465 break;
466 }
467 }
468
469 if let Some(event_id) = subscribe_up_to {
472 trace!(thread = %thread_root, up_to = %event_id, "found a new thread to subscribe to");
473 if let Err(err) = room.subscribe_thread_if_needed(&thread_root, Some(event_id)).await {
474 warn!(%err, "Failed to subscribe to thread");
475 } else {
476 let _ = thread_subscriber_sender.send(());
477 }
478 }
479
480 true
481}
482
483#[cfg(feature = "experimental-search")]
490#[instrument(skip_all)]
491pub(super) async fn search_indexing_task(
492 client: WeakClient,
493 linked_chunk_update_sender: Sender<RoomEventCacheLinkedChunkUpdate>,
494) {
495 let mut linked_chunk_update_receiver = linked_chunk_update_sender.subscribe();
496
497 loop {
498 match linked_chunk_update_receiver.recv().await {
499 Ok(room_ec_lc_update) => {
500 let OwnedLinkedChunkId::Room(room_id) = room_ec_lc_update.linked_chunk_id.clone()
501 else {
502 trace!("Received non-room updates, ignoring.");
503 continue;
504 };
505
506 let mut timeline_events = room_ec_lc_update.events().peekable();
507
508 if timeline_events.peek().is_none() {
509 continue;
510 }
511
512 let Some(client) = client.get() else {
513 trace!("Client is shutting down, exiting search task");
514 return;
515 };
516
517 let maybe_room_cache = client.event_cache().room(&room_id).await;
518 let Ok((room_cache, _drop_handles)) = maybe_room_cache else {
519 warn!(for_room = %room_id, "Failed to get RoomEventCache: {maybe_room_cache:?}");
520 continue;
521 };
522
523 let maybe_room = client.get_room(&room_id);
524 let Some(room) = maybe_room else {
525 warn!(get_room = %room_id, "Failed to get room while indexing: {maybe_room:?}");
526 continue;
527 };
528 let redaction_rules = room.clone_info().room_version_rules_or_default().redaction;
529
530 let mut search_index_guard = client.search_index().lock().await;
531
532 if let Err(err) = search_index_guard
533 .bulk_handle_timeline_event(
534 timeline_events,
535 &room_cache,
536 &room_id,
537 &redaction_rules,
538 )
539 .await
540 {
541 error!("Failed to handle events for indexing: {err}")
542 }
543 }
544 Err(RecvError::Closed) => {
545 debug!(
546 "Linked chunk update channel has been closed, exiting thread subscriber task"
547 );
548 break;
549 }
550 Err(RecvError::Lagged(num_skipped)) => {
551 warn!(num_skipped, "Lagged behind linked chunk updates");
552 }
553 }
554 }
555}