Skip to main content

matrix_sdk/send_queue/
mod.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A send queue facility to serializing queuing and sending of messages.
16//!
17//! # [`Room`] send queue
18//!
19//! Each room gets its own [`RoomSendQueue`], that's available by calling
20//! [`Room::send_queue()`]. The first time this method is called, it will spawn
21//! a background task that's used to actually send events, in the order they
22//! were passed from calls to [`RoomSendQueue::send()`].
23//!
24//! This queue tries to simplify error management around sending events, using
25//! [`RoomSendQueue::send`] or [`RoomSendQueue::send_raw`]: by default, it will retry to send the
26//! same event a few times, before automatically disabling itself, and emitting
27//! a notification that can be listened to with the global send queue (see
28//! paragraph below) or using [`RoomSendQueue::subscribe()`].
29//!
30//! Requests are sent in the order they were queued. A request that failed with
31//! an unrecoverable error is marked as "wedged", and blocks all the requests
32//! queued after it (in the same room) from being sent, so events are never sent
33//! out of order; the queue resumes when the wedged request is retried (with
34//! [`SendHandle::unwedge`]) or removed (with [`SendHandle::abort`]).
35//!
36//! It is possible to control whether a single room is enabled using
37//! [`RoomSendQueue::set_enabled()`].
38//!
39//! # Global [`SendQueue`] object
40//!
41//! The [`Client::send_queue()`] method returns an API object allowing to
42//! control all the room send queues:
43//!
44//! - enable/disable them all at once with [`SendQueue::set_enabled()`].
45//! - get notifications about send errors with [`SendQueue::subscribe_errors`].
46//! - reload all unsent events that had been persisted in storage using
47//!   [`SendQueue::respawn_tasks_for_rooms_with_unsent_requests()`]. It is
48//!   recommended to call this method during initialization of a client,
49//!   otherwise persisted unsent events will only be re-sent after the send
50//!   queue for the given room has been reopened for the first time.
51//!
52//! # Send handle
53//!
54//! Just after queuing a request to send something, a [`SendHandle`] is
55//! returned, allowing manipulating the inflight request.
56//!
57//! For a send handle for an event, it's possible to edit the event / abort
58//! sending it. If it was still in the queue (i.e. not sent yet, or not being
59//! sent), then such an action would happen locally (i.e. in the database).
60//! Otherwise, it is "too late": the background task may be sending
61//! the event already, or has sent it; in that case, the edit/aborting must
62//! happen as an actual event materializing this, on the server. To accomplish
63//! this, the send queue may send such an event, using the dependency system
64//! described below.
65//!
66//! # Dependency system
67//!
68//! The send queue includes a simple dependency system, where a
69//! [`QueuedRequest`] can have zero or more dependents in the form of
70//! [`DependentQueuedRequest`]. A dependent queued request can have at most one
71//! depended-upon (parent) queued request.
72//!
73//! This allows implementing deferred edits/redacts, as hinted to in the
74//! previous section.
75//!
76//! ## Media upload
77//!
78//! This dependency system also allows uploading medias, since the media's
79//! *content* must be uploaded before we send the media *event* that describes
80//! it.
81//!
82//! In the simplest case, that is, a media file and its event must be sent (i.e.
83//! no thumbnails):
84//!
85//! - The file's content is immediately cached in the
86//!   [`matrix_sdk_base::event_cache::store::EventCacheStore`], using an MXC ID
87//!   that is temporary and designates a local URI without any possible doubt.
88//! - An initial media event is created and uses this temporary MXC ID, and
89//!   propagated as a local echo for an event.
90//! - A [`QueuedRequest`] is pushed to upload the file's media
91//!   ([`QueuedRequestKind::MediaUpload`]).
92//! - A [`DependentQueuedRequest`] is pushed to finish the upload
93//!   ([`DependentQueuedRequestKind::FinishUpload`]).
94//!
95//! What is expected to happen, if all goes well, is the following:
96//!
97//! - the media is uploaded to the media homeserver, which returns the final MXC
98//!   ID.
99//! - when marking the upload request as sent, the MXC ID is injected (as a
100//!   [`matrix_sdk_base::store::SentRequestKey`]) into the dependent request
101//!   [`DependentQueuedRequestKind::FinishUpload`] created in the last step
102//!   above.
103//! - next time the send queue handles dependent queries, it'll see this one is
104//!   ready to be sent, and it will transform it into an event queued request
105//!   ([`QueuedRequestKind::Event`]), with the event created in the local echo
106//!   before, updated with the MXC ID returned from the server.
107//! - this updated local echo is also propagated as an edit of the local echo to
108//!   observers, who get the final version with the final MXC IDs at this point
109//!   too.
110//! - then the event is sent normally, as any event sent with the send queue.
111//!
112//! When there is a thumbnail, things behave similarly, with some tweaks:
113//!
114//! - the thumbnail's content is also stored into the cache store immediately,
115//! - the thumbnail is sent first as an [`QueuedRequestKind::MediaUpload`]
116//!   request,
117//! - the file upload is pushed as a dependent request of kind
118//!   [`DependentQueuedRequestKind::UploadFileOrThumbnail`] (this variant keeps
119//!   the file's key used to look it up in the cache store).
120//! - the media event is then sent as a dependent request as described in the
121//!   previous section.
122//!
123//! What's expected to happen is thus the following:
124//!
125//! - After the thumbnail has been uploaded, the dependent query will retrieve
126//!   the final MXC ID returned by the homeserver for the thumbnail, and store
127//!   it into the [`QueuedRequestKind::MediaUpload`]'s `thumbnail_source` field,
128//!   allowing to remember the thumbnail MXC ID when it's time to finish the
129//!   upload later.
130//! - The dependent request is morphed into another
131//!   [`QueuedRequestKind::MediaUpload`], for the file itself.
132//!
133//! The rest of the process is then similar to that of uploading a file without
134//! a thumbnail. The only difference is that there's a thumbnail source (MXC ID)
135//! remembered and fixed up into the media event, just before sending it.
136
137use std::{
138    collections::{BTreeMap, HashMap},
139    future::IntoFuture,
140    ops::Not,
141    str::FromStr as _,
142    sync::{
143        Arc, RwLock,
144        atomic::{AtomicBool, Ordering},
145    },
146    time::Duration,
147};
148
149use eyeball::SharedObservable;
150#[cfg(feature = "e2e-encryption")]
151use matrix_sdk_base::crypto::{OlmError, SessionRecipientCollectionError};
152#[cfg(feature = "unstable-msc4274")]
153use matrix_sdk_base::store::FinishGalleryItemInfo;
154use matrix_sdk_base::{
155    RoomState, StoreError,
156    cross_process_lock::CrossProcessLockError,
157    deserialized_responses::{EncryptionInfo, TimelineEvent},
158    event_cache::store::EventCacheStoreError,
159    media::{MediaRequestParameters, store::MediaStoreError},
160    store::{
161        ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind, DynStateStore,
162        FinishUploadThumbnailInfo, QueueWedgeError, QueuedRequest, QueuedRequestKind,
163        SentMediaInfo, SentRequestKey, SerializableEventContent,
164    },
165    task_monitor::BackgroundTaskHandle,
166};
167use matrix_sdk_common::{boxed_into_future, locks::Mutex as SyncMutex};
168use mime::Mime;
169#[cfg(feature = "unstable-msc4354")]
170use ruma::events::sticky::StickyDurationMs;
171use ruma::{
172    MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedTransactionId, RoomId,
173    TransactionId,
174    events::{
175        AnyMessageLikeEventContent, Mentions, MessageLikeEventContent as _, TimelineEventType,
176        reaction::ReactionEventContent,
177        relation::Annotation,
178        room::{
179            MediaSource,
180            message::{FormattedBody, RoomMessageEventContent},
181        },
182    },
183    serde::Raw,
184};
185use tokio::sync::{Mutex, Notify, OwnedMutexGuard, broadcast, oneshot};
186use tracing::{debug, error, info, instrument, trace, warn};
187
188#[cfg(feature = "unstable-msc4354")]
189use crate::utils::sticky_duration_ms;
190use crate::{
191    Client, Media, Room, TransmissionProgress,
192    client::WeakClient,
193    config::RequestConfig,
194    error::RetryKind,
195    room::{WeakRoom, edit::EditedContent},
196};
197
198mod progress;
199mod upload;
200
201pub use progress::AbstractProgress;
202
203/// How long to wait before retrying, when loading the next request to send
204/// failed against the state store.
205const STORE_ERROR_BACKOFF: Duration = Duration::from_millis(250);
206
207/// A client-wide send queue, for all the rooms known by a client.
208pub struct SendQueue {
209    client: Client,
210}
211
212#[cfg(not(tarpaulin_include))]
213impl std::fmt::Debug for SendQueue {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        f.debug_struct("SendQueue").finish_non_exhaustive()
216    }
217}
218
219impl SendQueue {
220    pub(super) fn new(client: Client) -> Self {
221        Self { client }
222    }
223
224    /// Reload all the rooms which had unsent requests, and respawn tasks for
225    /// those rooms.
226    pub async fn respawn_tasks_for_rooms_with_unsent_requests(&self) {
227        if !self.is_enabled() {
228            return;
229        }
230
231        let room_ids =
232            self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
233                |err| {
234                    warn!("error when loading rooms with unsent requests: {err}");
235                    Vec::new()
236                },
237            );
238
239        // Getting the [`RoomSendQueue`] is sufficient to spawn the task if
240        // needs be.
241        for room_id in room_ids {
242            if let Some(room) = self.client.get_room(&room_id) {
243                let _ = self.for_room(room);
244            }
245        }
246    }
247
248    /// Tiny helper to get the send queue's global context from the [`Client`].
249    #[inline(always)]
250    fn data(&self) -> &SendQueueData {
251        &self.client.inner.send_queue_data
252    }
253
254    /// Get or create a new send queue for a given room, and insert it into our
255    /// memoized rooms mapping.
256    pub(crate) fn for_room(&self, room: Room) -> RoomSendQueue {
257        let data = self.data();
258
259        let mut map = data.rooms.write().unwrap();
260
261        let room_id = room.room_id();
262        if let Some(room_q) = map.get(room_id).cloned() {
263            return room_q;
264        }
265
266        let owned_room_id = room_id.to_owned();
267        let room_q = RoomSendQueue::new(
268            self.is_enabled(),
269            data.global_update_sender.clone(),
270            data.error_sender.clone(),
271            data.is_dropping.clone(),
272            &self.client,
273            owned_room_id.clone(),
274            data.report_media_upload_progress.clone(),
275        );
276
277        map.insert(owned_room_id, room_q.clone());
278
279        room_q
280    }
281
282    /// Enable or disable the send queue for the entire client, i.e. all rooms.
283    ///
284    /// If we're disabling the queue, and requests were being sent, they're not
285    /// aborted, and will continue until a status resolves (error responses will
286    /// keep the events in the buffer of events to send later). The disablement
287    /// will happen before the next request is sent.
288    ///
289    /// This may wake up background tasks and resume sending of requests in the
290    /// background.
291    pub async fn set_enabled(&self, enabled: bool) {
292        debug!(?enabled, "setting global send queue enablement");
293
294        self.data().globally_enabled.store(enabled, Ordering::SeqCst);
295
296        // Wake up individual rooms we already know about.
297        for room in self.data().rooms.read().unwrap().values() {
298            room.set_enabled(enabled);
299        }
300
301        // Reload some extra rooms that might not have been awaken yet, but
302        // could have requests from previous sessions.
303        self.respawn_tasks_for_rooms_with_unsent_requests().await;
304    }
305
306    /// Returns whether the send queue is enabled, at a client-wide granularity.
307    pub fn is_enabled(&self) -> bool {
308        self.data().globally_enabled.load(Ordering::SeqCst)
309    }
310
311    /// Enable or disable progress reporting for media uploads.
312    pub fn enable_upload_progress(&self, enabled: bool) {
313        self.data().report_media_upload_progress.store(enabled, Ordering::SeqCst);
314    }
315
316    /// Subscribe to all updates for all rooms.
317    ///
318    /// Use [`RoomSendQueue::subscribe`] to subscribe to update for a
319    /// _specific room_.
320    pub fn subscribe(&self) -> broadcast::Receiver<SendQueueUpdate> {
321        self.data().global_update_sender.subscribe()
322    }
323
324    /// Get local echoes from all room send queues.
325    pub async fn local_echoes(
326        &self,
327    ) -> Result<BTreeMap<OwnedRoomId, Vec<LocalEcho>>, RoomSendQueueError> {
328        let room_ids =
329            self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
330                |err| {
331                    warn!("error when loading rooms with unsent requests: {err}");
332                    Vec::new()
333                },
334            );
335
336        let mut local_echoes: BTreeMap<OwnedRoomId, Vec<LocalEcho>> = BTreeMap::new();
337
338        for room_id in room_ids {
339            if let Some(room) = self.client.get_room(&room_id) {
340                let queue = self.for_room(room);
341                local_echoes
342                    .insert(room_id.to_owned(), queue.inner.queue.local_echoes(&queue).await?);
343            }
344        }
345
346        Ok(local_echoes)
347    }
348
349    /// A subscriber to the enablement status (enabled or disabled) of the send
350    /// queue, along with useful errors.
351    pub fn subscribe_errors(&self) -> broadcast::Receiver<SendQueueRoomError> {
352        self.data().error_sender.subscribe()
353    }
354}
355
356/// Metadata about a thumbnail needed when pushing media uploads to the send
357/// queue.
358#[derive(Clone, Debug)]
359struct QueueThumbnailInfo {
360    /// Metadata about the thumbnail needed when finishing a media upload.
361    finish_upload_thumbnail_info: FinishUploadThumbnailInfo,
362
363    /// The parameters for the request to retrieve the thumbnail data.
364    media_request_parameters: MediaRequestParameters,
365
366    /// The thumbnail's mime type.
367    content_type: Mime,
368
369    /// The thumbnail's file size in bytes.
370    file_size: usize,
371}
372
373/// A specific room's send queue ran into an error.
374#[derive(Clone, Debug)]
375pub struct SendQueueRoomError {
376    /// For which room is the send queue failing?
377    pub room_id: OwnedRoomId,
378
379    /// The error the room has ran into, when trying to send a request.
380    pub error: Arc<crate::Error>,
381
382    /// Whether the error is considered recoverable or not.
383    ///
384    /// An error that's recoverable will disable the room's send queue, while an
385    /// unrecoverable error will be parked, until it's retried or aborted.
386    pub is_recoverable: bool,
387}
388
389impl Client {
390    /// Returns a [`SendQueue`] that handles sending, retrying and not
391    /// forgetting about requests that are to be sent.
392    pub fn send_queue(&self) -> SendQueue {
393        SendQueue::new(self.clone())
394    }
395}
396
397pub(super) struct SendQueueData {
398    /// Mapping of room to their unique send queue.
399    rooms: RwLock<BTreeMap<OwnedRoomId, RoomSendQueue>>,
400
401    /// Is the whole mechanism enabled or disabled?
402    ///
403    /// This is only kept in memory to initialize new room queues with an
404    /// initial enablement state.
405    globally_enabled: AtomicBool,
406
407    /// Global sender to send [`SendQueueUpdate`].
408    ///
409    /// See [`SendQueue::subscribe`].
410    global_update_sender: broadcast::Sender<SendQueueUpdate>,
411
412    /// Global error updates for the send queue.
413    error_sender: broadcast::Sender<SendQueueRoomError>,
414
415    /// Are we currently dropping the Client?
416    is_dropping: Arc<AtomicBool>,
417
418    /// Will media upload progress be reported via send queue updates?
419    report_media_upload_progress: Arc<AtomicBool>,
420}
421
422impl SendQueueData {
423    /// Create the data for a send queue, in the given enabled state.
424    pub fn new(globally_enabled: bool) -> Self {
425        let (global_update_sender, _) = broadcast::channel(32);
426        let (error_sender, _) = broadcast::channel(32);
427
428        Self {
429            rooms: Default::default(),
430            globally_enabled: AtomicBool::new(globally_enabled),
431            global_update_sender,
432            error_sender,
433            is_dropping: Arc::new(false.into()),
434            report_media_upload_progress: Arc::new(false.into()),
435        }
436    }
437}
438
439impl Drop for SendQueueData {
440    fn drop(&mut self) {
441        // Mark the whole send queue as shutting down, then wake up all the room
442        // queues so they're stopped too.
443        debug!("globally dropping the send queue");
444        self.is_dropping.store(true, Ordering::SeqCst);
445
446        let rooms = self.rooms.read().unwrap();
447        for room in rooms.values() {
448            room.inner.notifier.notify_one();
449        }
450    }
451}
452
453impl Room {
454    /// Returns the [`RoomSendQueue`] for this specific room.
455    pub fn send_queue(&self) -> RoomSendQueue {
456        self.client.send_queue().for_room(self.clone())
457    }
458}
459
460/// A per-room send queue.
461///
462/// This is cheap to clone.
463#[derive(Clone)]
464pub struct RoomSendQueue {
465    inner: Arc<RoomSendQueueInner>,
466}
467
468#[cfg(not(tarpaulin_include))]
469impl std::fmt::Debug for RoomSendQueue {
470    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471        f.debug_struct("RoomSendQueue").finish_non_exhaustive()
472    }
473}
474
475impl RoomSendQueue {
476    fn new(
477        globally_enabled: bool,
478        global_update_sender: broadcast::Sender<SendQueueUpdate>,
479        global_error_sender: broadcast::Sender<SendQueueRoomError>,
480        is_dropping: Arc<AtomicBool>,
481        client: &Client,
482        room_id: OwnedRoomId,
483        report_media_upload_progress: Arc<AtomicBool>,
484    ) -> Self {
485        let (update_sender, _) = broadcast::channel(32);
486
487        let queue = QueueStorage::new(WeakClient::from_client(client), room_id.clone());
488        let notifier = Arc::new(Notify::new());
489
490        let weak_room = WeakRoom::new(WeakClient::from_client(client), room_id);
491        let locally_enabled = Arc::new(AtomicBool::new(globally_enabled));
492
493        let task = client.task_monitor().spawn_infinite_task(
494            "send_queue",
495            Self::sending_task(
496                weak_room.clone(),
497                queue.clone(),
498                notifier.clone(),
499                global_update_sender.clone(),
500                update_sender.clone(),
501                locally_enabled.clone(),
502                global_error_sender,
503                is_dropping,
504                report_media_upload_progress,
505            ),
506        );
507
508        Self {
509            inner: Arc::new(RoomSendQueueInner {
510                room: weak_room,
511                global_update_sender,
512                update_sender,
513                _task: task,
514                queue,
515                notifier,
516                locally_enabled,
517            }),
518        }
519    }
520
521    /// Queues a raw event for sending it to this room.
522    ///
523    /// This immediately returns, and will push the event to be sent into a
524    /// queue, handled in the background.
525    ///
526    /// Callers are expected to consume [`RoomSendQueueUpdate`] via calling the
527    /// [`Self::subscribe()`] method to get updates about the sending of that
528    /// event.
529    ///
530    /// By default, if sending failed on the first attempt, it will be retried a
531    /// few times. If sending failed after those retries, the entire client's
532    /// sending queue will be disabled, and it will need to be manually
533    /// re-enabled by the caller (e.g. after network is back, or when something
534    /// has been done about the faulty requests).
535    pub fn send_raw(
536        &self,
537        content: Raw<AnyMessageLikeEventContent>,
538        event_type: String,
539    ) -> SendRawEvent<'_> {
540        SendRawEvent {
541            queue: self,
542            content: SerializableEventContent::from_raw(content, event_type),
543            #[cfg(feature = "unstable-msc4354")]
544            sticky_duration: None,
545        }
546    }
547
548    /// Queues an already serialized event for sending it to this room.
549    async fn send_serialized(
550        &self,
551        content: SerializableEventContent,
552        #[cfg(feature = "unstable-msc4354")] sticky_duration: Option<StickyDurationMs>,
553    ) -> Result<SendHandle, RoomSendQueueError> {
554        let Some(room) = self.inner.room.get() else {
555            return Err(RoomSendQueueError::RoomDisappeared);
556        };
557        if room.state() != RoomState::Joined {
558            return Err(RoomSendQueueError::RoomNotJoined);
559        }
560
561        let request = QueuedRequestKind::Event {
562            content: content.clone(),
563            #[cfg(feature = "unstable-msc4354")]
564            sticky_duration,
565        };
566
567        let created_at = MilliSecondsSinceUnixEpoch::now();
568        let transaction_id = self.inner.queue.push(request, created_at).await?;
569        trace!(%transaction_id, "manager sends a raw event to the background task");
570
571        self.inner.notifier.notify_one();
572
573        let send_handle = SendHandle {
574            room: self.clone(),
575            transaction_id: transaction_id.clone(),
576            media_handles: vec![],
577            created_at,
578        };
579
580        self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
581            transaction_id,
582            content: LocalEchoContent::Event {
583                serialized_event: content,
584                send_handle: send_handle.clone(),
585                send_error: None,
586            },
587        }));
588
589        Ok(send_handle)
590    }
591
592    /// Queues an event for sending it to this room.
593    ///
594    /// This immediately returns, and will push the event to be sent into a
595    /// queue, handled in the background.
596    ///
597    /// Callers are expected to consume [`RoomSendQueueUpdate`] via calling the
598    /// [`Self::subscribe()`] method to get updates about the sending of that
599    /// event.
600    ///
601    /// By default, if sending failed on the first attempt, it will be retried a
602    /// few times. If sending failed after those retries, the entire client's
603    /// sending queue will be disabled, and it will need to be manually
604    /// re-enabled by the caller (e.g. after network is back, or when something
605    /// has been done about the faulty requests).
606    pub fn send(&self, content: AnyMessageLikeEventContent) -> SendEvent<'_> {
607        SendEvent {
608            queue: self,
609            content,
610            extra_content: None,
611            #[cfg(feature = "unstable-msc4354")]
612            sticky_duration: None,
613        }
614    }
615
616    /// Queues a redaction of another event for sending it to this room.
617    ///
618    /// This immediately returns, and will push the redaction to be sent into a
619    /// queue, handled in the background.
620    ///
621    /// Callers are expected to consume [`RoomSendQueueUpdate`] via calling the
622    /// [`Self::subscribe()`] method to get updates about the sending of that
623    /// redaction.
624    ///
625    /// By default, if sending failed on the first attempt, it will be retried a
626    /// few times. If sending failed after those retries, the entire client's
627    /// sending queue will be disabled, and it will need to be manually
628    /// re-enabled by the caller (e.g. after network is back, or when something
629    /// has been done about the faulty requests).
630    pub async fn redact(
631        &self,
632        redacts: OwnedEventId,
633        reason: Option<&str>,
634    ) -> Result<SendHandle, RoomSendQueueError> {
635        let Some(room) = self.inner.room.get() else {
636            return Err(RoomSendQueueError::RoomDisappeared);
637        };
638        if room.state() != RoomState::Joined {
639            return Err(RoomSendQueueError::RoomNotJoined);
640        }
641
642        let request = QueuedRequestKind::Redaction {
643            redacts: redacts.clone(),
644            reason: reason.map(str::to_owned),
645        };
646
647        let created_at = MilliSecondsSinceUnixEpoch::now();
648        let transaction_id = self.inner.queue.push(request, created_at).await?;
649        trace!(%transaction_id, "manager sends a redaction event to the background task");
650
651        self.inner.notifier.notify_one();
652
653        let send_handle = SendHandle {
654            room: self.clone(),
655            transaction_id: transaction_id.clone(),
656            media_handles: vec![],
657            created_at,
658        };
659
660        self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
661            transaction_id,
662            content: LocalEchoContent::Redaction {
663                redacts,
664                reason: reason.map(str::to_owned),
665                send_handle: send_handle.clone(),
666                send_error: None,
667            },
668        }));
669
670        Ok(send_handle)
671    }
672
673    /// Returns the current local requests as well as a receiver to listen to
674    /// the send queue updates, as defined in [`RoomSendQueueUpdate`].
675    ///
676    /// Use [`SendQueue::subscribe`] to subscribe to update for _all rooms_ with
677    /// a single receiver.
678    pub async fn subscribe(
679        &self,
680    ) -> Result<(Vec<LocalEcho>, broadcast::Receiver<RoomSendQueueUpdate>), RoomSendQueueError>
681    {
682        let local_echoes = self.inner.queue.local_echoes(self).await?;
683
684        Ok((local_echoes, self.inner.update_sender.subscribe()))
685    }
686
687    /// A task that must be spawned in the async runtime, running in the
688    /// background for each room that has a send queue.
689    ///
690    /// It only progresses forward: nothing can be cancelled at any point, which
691    /// makes the implementation not overly complicated to follow.
692    #[allow(clippy::too_many_arguments)]
693    #[instrument(skip_all, fields(room_id = %room.room_id()))]
694    async fn sending_task(
695        room: WeakRoom,
696        queue: QueueStorage,
697        notifier: Arc<Notify>,
698        global_update_sender: broadcast::Sender<SendQueueUpdate>,
699        update_sender: broadcast::Sender<RoomSendQueueUpdate>,
700        locally_enabled: Arc<AtomicBool>,
701        global_error_sender: broadcast::Sender<SendQueueRoomError>,
702        is_dropping: Arc<AtomicBool>,
703        report_media_upload_progress: Arc<AtomicBool>,
704    ) {
705        trace!("spawned the sending task");
706
707        let room_id = room.room_id();
708
709        loop {
710            // A request to shut down should be preferred above everything else.
711            if is_dropping.load(Ordering::SeqCst) {
712                trace!("shutting down!");
713                break;
714            }
715
716            // Try to apply dependent requests now; those applying to previously
717            // failed attempts (local echoes) would succeed now.
718            let mut new_updates = Vec::new();
719            if let Err(err) = queue.apply_dependent_requests(&mut new_updates).await {
720                warn!("errors when applying dependent requests: {err}");
721            }
722
723            for up in new_updates {
724                send_update(&global_update_sender, &update_sender, room_id, up);
725            }
726
727            if !locally_enabled.load(Ordering::SeqCst) {
728                trace!("not enabled, sleeping");
729                // Wait for an explicit wakeup.
730                notifier.notified().await;
731                continue;
732            }
733
734            let (queued_request, cancel_upload_rx) = match queue.peek_next_to_send().await {
735                Ok(Some(request)) => request,
736
737                Ok(None) => {
738                    trace!("queue is empty or blocked on a wedged request, sleeping");
739                    // Wait for an explicit wakeup.
740                    notifier.notified().await;
741                    continue;
742                }
743
744                Err(err) => {
745                    warn!("error when loading next request to send: {err}");
746                    // Don't hammer a failing store; back off a bit before
747                    // retrying.
748                    matrix_sdk_common::sleep::sleep(STORE_ERROR_BACKOFF).await;
749                    continue;
750                }
751            };
752
753            let txn_id = queued_request.transaction_id.clone();
754            trace!(txn_id = %txn_id, "received a request to send!");
755
756            let Some(room) = room.get() else {
757                if is_dropping.load(Ordering::SeqCst) {
758                    break;
759                }
760                error!("the weak room couldn't be upgraded but we're not shutting down?");
761                continue;
762            };
763
764            // If this is a media/gallery upload, prepare the following:
765            //
766            // - transaction id for the related media event request,
767            // - progress metadata to feed the final media upload progress
768            // - an observable to watch the media upload progress.
769            let (related_txn_id, media_upload_progress_info, http_progress) =
770                if let QueuedRequestKind::MediaUpload {
771                    cache_key,
772                    thumbnail_source,
773                    #[cfg(feature = "unstable-msc4274")]
774                    accumulated,
775                    related_to,
776                    ..
777                } = &queued_request.kind
778                {
779                    // Prepare to watch and communicate the request's progress
780                    // for media uploads, if it has been requested.
781                    let (media_upload_progress_info, http_progress) =
782                        if report_media_upload_progress.load(Ordering::SeqCst) {
783                            let media_upload_progress_info =
784                                RoomSendQueue::create_media_upload_progress_info(
785                                    &queued_request.transaction_id,
786                                    related_to,
787                                    cache_key,
788                                    thumbnail_source.as_ref(),
789                                    #[cfg(feature = "unstable-msc4274")]
790                                    accumulated,
791                                    &room,
792                                    &queue,
793                                )
794                                .await;
795
796                            let progress = RoomSendQueue::create_media_upload_progress_observable(
797                                &media_upload_progress_info,
798                                related_to,
799                                &update_sender,
800                            );
801
802                            (Some(media_upload_progress_info), Some(progress))
803                        } else {
804                            Default::default()
805                        };
806
807                    (Some(related_to.clone()), media_upload_progress_info, http_progress)
808                } else {
809                    Default::default()
810                };
811
812            match Self::handle_request(&room, queued_request, cancel_upload_rx, http_progress).await
813            {
814                Ok((Some(parent_key), encryption_info)) => match queue
815                    .mark_as_sent(&txn_id, parent_key.clone())
816                    .await
817                {
818                    Ok(()) => match parent_key {
819                        SentRequestKey::Event { event_id, event, event_type } => {
820                            send_update(
821                                &global_update_sender,
822                                &update_sender,
823                                room_id,
824                                RoomSendQueueUpdate::SentEvent {
825                                    transaction_id: txn_id,
826                                    event_id: event_id.clone(),
827                                },
828                            );
829
830                            // The event has been sent to the server and the
831                            // server has received it. Yepee! Now, we usually
832                            // wait on the server to give us back the event via
833                            // the sync.
834                            //
835                            // Problem: sometimes the network lags, can be down,
836                            // or the server may be slow; well, anything can
837                            // happen.
838                            //
839                            // It results in a weird situation where the user
840                            // sees its event being sent, then disappears before
841                            // it's received again from the server.
842                            //
843                            // To avoid this situation, we eagerly save the
844                            // event in the Event Cache. It's similar to what
845                            // would happen if the event was echoed back from
846                            // the server via the sync, but we avoid any network
847                            // issues. The Event Cache is smart enough to
848                            // deduplicate events based on the event ID, so it's
849                            // safe to do that.
850                            //
851                            // If this little feature fails, it MUST NOT stop
852                            // the Send Queue. Any errors are logged, but the
853                            // Send Queue will continue as if everything
854                            // happened successfully. This feature is not
855                            // considered “crucial”.
856                            if let Ok((room_event_cache, _drop_handles)) = room.event_cache().await
857                            {
858                                let timeline_event = match Raw::from_json_string(
859                                    // Create a compact string: remove all useless spaces.
860                                    format!(
861                                        "{{\
862                                            \"event_id\":\"{event_id}\",\
863                                            \"origin_server_ts\":{ts},\
864                                            \"sender\":\"{sender}\",\
865                                            \"type\":\"{type}\",\
866                                            \"content\":{content}\
867                                        }}",
868                                        event_id = event_id,
869                                        ts = MilliSecondsSinceUnixEpoch::now().get(),
870                                        sender = room.client().user_id().expect("Client must be logged-in"),
871                                        type = event_type,
872                                        content = event.into_json(),
873                                    ),
874                                ) {
875                                    Ok(event) => match encryption_info {
876                                        #[cfg(feature = "e2e-encryption")]
877                                        Some(encryption_info) => {
878                                            use matrix_sdk_base::deserialized_responses::DecryptedRoomEvent;
879                                            let decrypted_event = DecryptedRoomEvent {
880                                                event: event.cast_unchecked(),
881                                                encryption_info: Arc::new(encryption_info),
882                                                unsigned_encryption_info: None,
883                                            };
884                                            Some(TimelineEvent::from_decrypted(
885                                                decrypted_event,
886                                                None,
887                                            ))
888                                        }
889                                        _ => Some(TimelineEvent::from_plaintext(event)),
890                                    },
891                                    Err(err) => {
892                                        error!(
893                                            ?err,
894                                            "Failed to build the (sync) event before the saving in the Event Cache"
895                                        );
896                                        None
897                                    }
898                                };
899
900                                // In case of an error, just log the error but
901                                // don't stop the Send Queue. This feature is
902                                // not crucial.
903                                if let Some(timeline_event) = timeline_event
904                                    && let Err(err) = room_event_cache
905                                        .insert_sent_event_from_send_queue(timeline_event)
906                                        .await
907                                {
908                                    error!(
909                                        ?err,
910                                        "Failed to save the sent event in the Event Cache"
911                                    );
912                                }
913                            } else {
914                                info!(
915                                    "Cannot insert the sent event in the Event Cache because \
916                                    either the room no longer exists, or the Room Event Cache cannot be retrieved"
917                                );
918                            }
919                        }
920
921                        SentRequestKey::Media(sent_media_info) => {
922                            // Generate some final progress information, even if
923                            // incremental progress wasn't requested.
924                            let index =
925                                media_upload_progress_info.as_ref().map_or(0, |info| info.index);
926                            let progress = media_upload_progress_info
927                                .as_ref()
928                                .map(|info| {
929                                    AbstractProgress { current: info.bytes, total: info.bytes }
930                                        + info.offsets
931                                })
932                                .unwrap_or(AbstractProgress { current: 1, total: 1 });
933
934                            // Purposefully don't use `send_update` here,
935                            // because we don't want to notify the global
936                            // listeners about an upload progress update.
937                            let _ = update_sender.send(RoomSendQueueUpdate::MediaUpload {
938                                related_to: related_txn_id.as_ref().unwrap_or(&txn_id).clone(),
939                                file: Some(sent_media_info.file),
940                                index,
941                                progress,
942                            });
943                        }
944
945                        SentRequestKey::Redaction { event_id, redacts, reason } => {
946                            send_update(
947                                &global_update_sender,
948                                &update_sender,
949                                room_id,
950                                RoomSendQueueUpdate::SentEvent {
951                                    transaction_id: txn_id,
952                                    event_id: event_id.clone(),
953                                },
954                            );
955
956                            // The redaction event has been sent to the server
957                            // and the server has received it. It's safe to
958                            // cache the event now to avoid any inconsistencies
959                            // until the server sends down the remote echo via
960                            // the sync.
961                            if let Ok((room_event_cache, _drop_handles)) = room.event_cache().await
962                            {
963                                let content_field_redacts = room.version().is_some_and(|id| {
964                                    id.rules()
965                                        .is_some_and(|rules| rules.redaction.content_field_redacts)
966                                });
967                                let redacts = if content_field_redacts.not() {
968                                    format!("\"redacts\":\"{redacts}\",")
969                                } else {
970                                    "".to_owned()
971                                };
972                                let reason = reason.map_or_else(
973                                    || "".to_owned(),
974                                    |r| format!("\"reason\": \"{r}\""),
975                                );
976                                let content = if content_field_redacts {
977                                    format!("\"redacts\":\"{redacts}\",{reason}")
978                                } else {
979                                    reason
980                                };
981
982                                let timeline_event = match Raw::from_json_string(
983                                    // Create a compact string: remove all useless spaces.
984                                    format!(
985                                        "{{\
986                                            {redacts}\
987                                            \"event_id\":\"{event_id}\",\
988                                            \"origin_server_ts\":{ts},\
989                                            \"sender\":\"{sender}\",\
990                                            \"type\":\"{type}\",\
991                                            \"content\":{{{content}}}\
992                                        }}",
993                                        redacts = redacts,
994                                        event_id = event_id,
995                                        ts = MilliSecondsSinceUnixEpoch::now().get(),
996                                        sender = room.client().user_id().expect("Client must be logged-in"),
997                                        type = TimelineEventType::RoomRedaction,
998                                        content = content
999                                    ),
1000                                ) {
1001                                    Ok(event) => Some(TimelineEvent::from_plaintext(event)),
1002                                    Err(err) => {
1003                                        error!(
1004                                            ?err,
1005                                            "Failed to build the (sync) redaction event before the saving in the Event Cache"
1006                                        );
1007                                        None
1008                                    }
1009                                };
1010
1011                                // In case of an error, just log the error but
1012                                // don't stop the Send Queue. This feature is
1013                                // not crucial.
1014                                if let Some(timeline_event) = timeline_event
1015                                    && let Err(err) = room_event_cache
1016                                        .insert_sent_event_from_send_queue(timeline_event)
1017                                        .await
1018                                {
1019                                    error!(
1020                                        ?err,
1021                                        "Failed to save the sent redaction event in the Event Cache"
1022                                    );
1023                                }
1024                            } else {
1025                                info!(
1026                                    "Cannot insert the sent redaction event in the Event Cache because \
1027                                    either the room no longer exists, or the Room Event Cache cannot be retrieved"
1028                                );
1029                            }
1030                        }
1031                    },
1032
1033                    Err(err) => {
1034                        warn!("unable to mark queued request as sent: {err}");
1035                    }
1036                },
1037
1038                Ok((None, _)) => {
1039                    debug!("Request has been aborted while running, continuing.");
1040                }
1041
1042                Err(err) => {
1043                    let is_recoverable = match err {
1044                        crate::Error::Http(ref http_err) => {
1045                            // All transient errors are recoverable.
1046                            matches!(
1047                                http_err.retry_kind(),
1048                                RetryKind::Transient { .. } | RetryKind::NetworkFailure
1049                            )
1050                        }
1051
1052                        // `ConcurrentRequestFailed` typically happens because
1053                        // of an HTTP failure; since we don't get the underlying
1054                        // error, be lax and consider it recoverable, and let
1055                        // observers decide to retry it or not. At some point
1056                        // we'll get the actual underlying error.
1057                        crate::Error::ConcurrentRequestFailed => true,
1058
1059                        // As of 2024-06-27, all other error types are considered unrecoverable.
1060                        _ => false,
1061                    };
1062
1063                    if is_recoverable {
1064                        // Disable the queue for this room; there's nothing else
1065                        // blocking it, and whatever caused the failure is
1066                        // likely to affect the next requests too.
1067                        locally_enabled.store(false, Ordering::SeqCst);
1068
1069                        warn!(txn_id = %txn_id, error = ?err, "Recoverable error when sending request: {err}, disabling send queue");
1070
1071                        // In this case, we intentionally keep the request in
1072                        // the queue, but mark it as not being sent anymore.
1073                        queue.mark_as_not_being_sent(&txn_id).await;
1074
1075                        // Let observers know about a failure _after_ we've
1076                        // marked the item as not being sent anymore. Otherwise,
1077                        // there's a possible race where a caller might try to
1078                        // remove an item, while it's still marked as being
1079                        // sent, resulting in a cancellation failure.
1080                    } else {
1081                        warn!(txn_id = %txn_id, error = ?err, "Unrecoverable error when sending request: {err}");
1082
1083                        // Mark the request as wedged, so it's not picked at any
1084                        // future point; it will also block subsequent requests
1085                        // in the same room from being sent, until it's unwedged
1086                        // or removed, so as to preserve ordering.
1087                        if let Err(storage_error) =
1088                            queue.mark_as_wedged(&txn_id, QueueWedgeError::from(&err)).await
1089                        {
1090                            // Nothing recorded the wedge, so the request would
1091                            // be picked up and sent again right away, over and
1092                            // over; disabling the queue is the only brake left.
1093                            error!(
1094                                "unable to mark request as wedged, disabling the queue: {storage_error}"
1095                            );
1096                            locally_enabled.store(false, Ordering::SeqCst);
1097                        }
1098                    }
1099
1100                    let error = Arc::new(err);
1101
1102                    let _ = global_error_sender.send(SendQueueRoomError {
1103                        room_id: room_id.to_owned(),
1104                        error: error.clone(),
1105                        is_recoverable,
1106                    });
1107
1108                    send_update(
1109                        &global_update_sender,
1110                        &update_sender,
1111                        room_id,
1112                        RoomSendQueueUpdate::SendError {
1113                            transaction_id: related_txn_id.unwrap_or(txn_id),
1114                            error,
1115                            is_recoverable,
1116                        },
1117                    );
1118                }
1119            }
1120        }
1121
1122        info!("exited sending task");
1123    }
1124
1125    /// Handles a single request and returns the [`SentRequestKey`] on success
1126    /// (unless the request was cancelled, in which case it'll return `None`).
1127    async fn handle_request(
1128        room: &Room,
1129        request: QueuedRequest,
1130        cancel_upload_rx: Option<oneshot::Receiver<()>>,
1131        progress: Option<SharedObservable<TransmissionProgress>>,
1132    ) -> Result<(Option<SentRequestKey>, Option<EncryptionInfo>), crate::Error> {
1133        match request.kind {
1134            QueuedRequestKind::Event {
1135                content,
1136                #[cfg(feature = "unstable-msc4354")]
1137                sticky_duration,
1138            } => {
1139                let (event, event_type) = content.into_raw();
1140
1141                let future = room
1142                    .send_raw(&event_type, &event)
1143                    .with_transaction_id(&request.transaction_id)
1144                    .with_request_config(RequestConfig::short_retry());
1145
1146                #[cfg(feature = "unstable-msc4354")]
1147                let future = match sticky_duration {
1148                    Some(duration) => {
1149                        future.with_sticky_duration(Duration::from_millis(duration.get().into()))
1150                    }
1151                    None => future,
1152                };
1153
1154                let result = future.await?;
1155
1156                trace!(txn_id = %request.transaction_id, event_id = %result.response.event_id, "event successfully sent");
1157
1158                Ok((
1159                    Some(SentRequestKey::Event {
1160                        event_id: result.response.event_id,
1161                        event,
1162                        event_type,
1163                    }),
1164                    result.encryption_info,
1165                ))
1166            }
1167
1168            QueuedRequestKind::MediaUpload {
1169                content_type,
1170                cache_key,
1171                thumbnail_source,
1172                related_to: relates_to,
1173                #[cfg(feature = "unstable-msc4274")]
1174                accumulated,
1175            } => {
1176                trace!(%relates_to, "uploading media related to event");
1177
1178                let fut = async move {
1179                    let data = room
1180                        .client()
1181                        .media_store()
1182                        .lock()
1183                        .await?
1184                        .get_media_content(&cache_key)
1185                        .await?
1186                        .ok_or(crate::Error::SendQueueWedgeError(Box::new(
1187                            QueueWedgeError::MissingMediaContent,
1188                        )))?;
1189
1190                    let mime = Mime::from_str(&content_type).map_err(|_| {
1191                        crate::Error::SendQueueWedgeError(Box::new(
1192                            QueueWedgeError::InvalidMimeType { mime_type: content_type.clone() },
1193                        ))
1194                    })?;
1195
1196                    #[cfg(feature = "e2e-encryption")]
1197                    let media_source = if room.latest_encryption_state().await?.is_encrypted() {
1198                        trace!("upload will be encrypted (encrypted room)");
1199
1200                        let mut cursor = std::io::Cursor::new(data);
1201                        let mut req = room
1202                            .client
1203                            .upload_encrypted_file(&mut cursor)
1204                            .with_request_config(RequestConfig::short_retry());
1205                        if let Some(progress) = progress {
1206                            req = req.with_send_progress_observable(progress);
1207                        }
1208                        let encrypted_file = req.await?;
1209
1210                        MediaSource::Encrypted(Box::new(encrypted_file))
1211                    } else {
1212                        trace!("upload will be in clear text (room without encryption)");
1213
1214                        let request_config = RequestConfig::short_retry()
1215                            .timeout(Media::reasonable_upload_timeout(&data));
1216                        let mut req =
1217                            room.client().media().upload(&mime, data, Some(request_config));
1218                        if let Some(progress) = progress {
1219                            req = req.with_send_progress_observable(progress);
1220                        }
1221                        let res = req.await?;
1222
1223                        MediaSource::Plain(res.content_uri)
1224                    };
1225
1226                    #[cfg(not(feature = "e2e-encryption"))]
1227                    let media_source = {
1228                        let request_config = RequestConfig::short_retry()
1229                            .timeout(Media::reasonable_upload_timeout(&data));
1230                        let mut req =
1231                            room.client().media().upload(&mime, data, Some(request_config));
1232                        if let Some(progress) = progress {
1233                            req = req.with_send_progress_observable(progress);
1234                        }
1235                        let res = req.await?;
1236                        MediaSource::Plain(res.content_uri)
1237                    };
1238
1239                    let uri = match &media_source {
1240                        MediaSource::Plain(uri) => uri,
1241                        MediaSource::Encrypted(encrypted_file) => &encrypted_file.url,
1242                    };
1243                    trace!(%relates_to, mxc_uri = %uri, "media successfully uploaded");
1244
1245                    Ok((
1246                        Some(SentRequestKey::Media(SentMediaInfo {
1247                            file: media_source,
1248                            thumbnail: thumbnail_source,
1249                            #[cfg(feature = "unstable-msc4274")]
1250                            accumulated,
1251                        })),
1252                        None,
1253                    ))
1254                };
1255
1256                let wait_for_cancel = async move {
1257                    if let Some(rx) = cancel_upload_rx {
1258                        rx.await
1259                    } else {
1260                        std::future::pending().await
1261                    }
1262                };
1263
1264                tokio::select! {
1265                    biased;
1266
1267                    _ = wait_for_cancel => {
1268                        Ok((None, None))
1269                    }
1270
1271                    res = fut => {
1272                        res
1273                    }
1274                }
1275            }
1276
1277            QueuedRequestKind::Redaction { redacts, reason } => {
1278                let result = room
1279                    .redact(&redacts, reason.as_deref(), Some(request.transaction_id.clone()))
1280                    .await?;
1281
1282                trace!(txn_id = %request.transaction_id, event_id = %result.event_id, "redaction successfully sent");
1283
1284                Ok((
1285                    Some(SentRequestKey::Redaction { event_id: result.event_id, redacts, reason }),
1286                    None,
1287                ))
1288            }
1289        }
1290    }
1291
1292    /// Returns whether the room is enabled, at the room level.
1293    pub fn is_enabled(&self) -> bool {
1294        self.inner.locally_enabled.load(Ordering::SeqCst)
1295    }
1296
1297    /// Set the locally enabled flag for this room queue.
1298    pub fn set_enabled(&self, enabled: bool) {
1299        self.inner.locally_enabled.store(enabled, Ordering::SeqCst);
1300
1301        // No need to wake a task to tell it it's been disabled, so only notify
1302        // if we're re-enabling the queue.
1303        if enabled {
1304            self.inner.notifier.notify_one();
1305        }
1306    }
1307
1308    /// Send an update on the room send queue channel, and on the global send
1309    /// queue channel, i.e. it sends a [`RoomSendQueueUpdate`] and a
1310    /// [`SendQueueUpdate`].
1311    fn send_update(&self, update: RoomSendQueueUpdate) {
1312        let _ = self.inner.update_sender.send(update.clone());
1313        let _ = self
1314            .inner
1315            .global_update_sender
1316            .send(SendQueueUpdate { room_id: self.inner.room.room_id().to_owned(), update });
1317    }
1318
1319    /// Clear a request's wedged status and wake the queue up so it's tried
1320    /// again.
1321    async fn unwedge_request(
1322        &self,
1323        transaction_id: &TransactionId,
1324    ) -> Result<(), RoomSendQueueError> {
1325        self.inner
1326            .queue
1327            .mark_as_unwedged(transaction_id)
1328            .await
1329            .map_err(RoomSendQueueError::StorageError)?;
1330
1331        // Wake up the queue, in case the room was asleep before unwedging the
1332        // request.
1333        self.inner.notifier.notify_one();
1334
1335        self.send_update(RoomSendQueueUpdate::RetryEvent {
1336            transaction_id: transaction_id.to_owned(),
1337        });
1338
1339        Ok(())
1340    }
1341}
1342
1343fn send_update(
1344    global_update_sender: &broadcast::Sender<SendQueueUpdate>,
1345    update_sender: &broadcast::Sender<RoomSendQueueUpdate>,
1346    room_id: &RoomId,
1347    update: RoomSendQueueUpdate,
1348) {
1349    let _ = update_sender.send(update.clone());
1350    let _ = global_update_sender.send(SendQueueUpdate { room_id: room_id.to_owned(), update });
1351}
1352
1353impl From<&crate::Error> for QueueWedgeError {
1354    fn from(value: &crate::Error) -> Self {
1355        match value {
1356            #[cfg(feature = "e2e-encryption")]
1357            crate::Error::OlmError(error) => match &**error {
1358                OlmError::SessionRecipientCollectionError(error) => match error {
1359                    SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(user_map) => {
1360                        QueueWedgeError::InsecureDevices { user_device_map: user_map.clone() }
1361                    }
1362
1363                    SessionRecipientCollectionError::VerifiedUserChangedIdentity(users) => {
1364                        QueueWedgeError::IdentityViolations { users: users.clone() }
1365                    }
1366
1367                    SessionRecipientCollectionError::CrossSigningNotSetup
1368                    | SessionRecipientCollectionError::SendingFromUnverifiedDevice => {
1369                        QueueWedgeError::CrossVerificationRequired
1370                    }
1371                },
1372                _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1373            },
1374
1375            // Flatten errors of `Self` type.
1376            crate::Error::SendQueueWedgeError(error) => *error.clone(),
1377
1378            _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1379        }
1380    }
1381}
1382
1383struct RoomSendQueueInner {
1384    /// The room which this send queue relates to.
1385    room: WeakRoom,
1386
1387    /// Global sender to send [`SendQueueUpdate`].
1388    ///
1389    /// See [`SendQueue::subscribe`].
1390    global_update_sender: broadcast::Sender<SendQueueUpdate>,
1391
1392    /// Broadcaster for notifications about the statuses of requests to be sent.
1393    ///
1394    /// Can be subscribed to from the outside.
1395    ///
1396    /// See [`RoomSendQueue::subscribe`].
1397    update_sender: broadcast::Sender<RoomSendQueueUpdate>,
1398
1399    /// Queue of requests that are either to be sent, or being sent.
1400    ///
1401    /// When a request has been sent to the server, it is removed from that
1402    /// queue _after_ being sent. That way, we will retry sending upon failure,
1403    /// in the same order requests have been inserted in the first place.
1404    queue: QueueStorage,
1405
1406    /// A notifier that's updated any time common data is touched (stopped or
1407    /// enabled statuses), or the associated room [`QueueStorage`].
1408    notifier: Arc<Notify>,
1409
1410    /// Should the room process new requests or not (because e.g. it might be
1411    /// running off the network)?
1412    locally_enabled: Arc<AtomicBool>,
1413
1414    /// Handle to the actual sending task. Unused, but kept alive along this
1415    /// data structure.
1416    _task: BackgroundTaskHandle,
1417}
1418
1419/// Information about a request being sent right this moment.
1420struct BeingSentInfo {
1421    /// Transaction id of the thing being sent.
1422    transaction_id: OwnedTransactionId,
1423
1424    /// For an upload request, a trigger to cancel the upload before it
1425    /// completes.
1426    cancel_upload: Option<oneshot::Sender<()>>,
1427}
1428
1429impl BeingSentInfo {
1430    /// Aborts the upload, if a trigger is available.
1431    ///
1432    /// Consumes the object because the sender is a oneshot and will be consumed
1433    /// upon sending.
1434    fn cancel_upload(self) -> bool {
1435        if let Some(cancel_upload) = self.cancel_upload {
1436            let _ = cancel_upload.send(());
1437            true
1438        } else {
1439            false
1440        }
1441    }
1442}
1443
1444/// A specialized lock that guards both against the state store and the
1445/// [`Self::being_sent`] data.
1446#[derive(Clone)]
1447struct StoreLock {
1448    /// Reference to the client, to get access to the underlying store.
1449    client: WeakClient,
1450
1451    /// The one queued request that is being sent at the moment, along with
1452    /// associated data that can be useful to act upon it.
1453    ///
1454    /// Also used as the lock to access the state store.
1455    being_sent: Arc<Mutex<Option<BeingSentInfo>>>,
1456}
1457
1458impl StoreLock {
1459    /// Gets a hold of the locked store and [`Self::being_sent`] pair.
1460    async fn lock(&self) -> StoreLockGuard {
1461        StoreLockGuard {
1462            client: self.client.clone(),
1463            being_sent: self.being_sent.clone().lock_owned().await,
1464        }
1465    }
1466}
1467
1468/// A lock guard obtained through locking with [`StoreLock`]. `being_sent` data.
1469struct StoreLockGuard {
1470    /// Reference to the client, to get access to the underlying store.
1471    client: WeakClient,
1472
1473    /// The one queued request that is being sent at the moment, along with
1474    /// associated data that can be useful to act upon it.
1475    being_sent: OwnedMutexGuard<Option<BeingSentInfo>>,
1476}
1477
1478impl StoreLockGuard {
1479    /// Get a client from the locked state, useful to get a handle on a store.
1480    fn client(&self) -> Result<Client, RoomSendQueueStorageError> {
1481        self.client.get().ok_or(RoomSendQueueStorageError::ClientShuttingDown)
1482    }
1483}
1484
1485#[derive(Clone)]
1486struct QueueStorage {
1487    /// A lock to make sure the state store is only accessed once at a time, to
1488    /// make some store operations atomic.
1489    store: StoreLock,
1490
1491    /// To which room is this storage related.
1492    room_id: OwnedRoomId,
1493
1494    /// In-memory mapping of media transaction IDs to thumbnail sizes for the
1495    /// purpose of progress reporting.
1496    ///
1497    /// The keys are the transaction IDs for sending the media or gallery event
1498    /// after all uploads have finished. This allows us to easily clean up the
1499    /// cache after the event was sent.
1500    ///
1501    /// For media uploads, the value vector will always have a single element.
1502    ///
1503    /// For galleries, some gallery items might not have a thumbnail while
1504    /// others do. Since we access the thumbnails by their index within the
1505    /// gallery, the vector needs to hold optional usize's.
1506    thumbnail_file_sizes: Arc<SyncMutex<HashMap<OwnedTransactionId, Vec<Option<usize>>>>>,
1507}
1508
1509impl QueueStorage {
1510    /// Default priority for a queued request.
1511    const LOW_PRIORITY: usize = 0;
1512
1513    /// High priority for a queued request that must be handled before others.
1514    const HIGH_PRIORITY: usize = 10;
1515
1516    /// Create a new queue for queuing requests to be sent later.
1517    fn new(client: WeakClient, room: OwnedRoomId) -> Self {
1518        Self {
1519            room_id: room,
1520            store: StoreLock { client, being_sent: Default::default() },
1521            thumbnail_file_sizes: Default::default(),
1522        }
1523    }
1524
1525    /// Push a new event to be sent in the queue, with a default priority of 0.
1526    ///
1527    /// Returns the transaction id chosen to identify the request.
1528    async fn push(
1529        &self,
1530        request: QueuedRequestKind,
1531        created_at: MilliSecondsSinceUnixEpoch,
1532    ) -> Result<OwnedTransactionId, RoomSendQueueStorageError> {
1533        let transaction_id = TransactionId::new();
1534
1535        self.store
1536            .lock()
1537            .await
1538            .client()?
1539            .state_store()
1540            .save_send_queue_request(
1541                &self.room_id,
1542                transaction_id.clone(),
1543                created_at,
1544                request,
1545                Self::LOW_PRIORITY,
1546            )
1547            .await?;
1548
1549        Ok(transaction_id)
1550    }
1551
1552    /// Peeks the next request to be sent, marking it as being sent.
1553    ///
1554    /// It is required to call [`Self::mark_as_sent`] after it's been
1555    /// effectively sent.
1556    async fn peek_next_to_send(
1557        &self,
1558    ) -> Result<Option<(QueuedRequest, Option<oneshot::Receiver<()>>)>, RoomSendQueueStorageError>
1559    {
1560        let mut guard = self.store.lock().await;
1561        let queued_requests =
1562            guard.client()?.state_store().load_send_queue_requests(&self.room_id).await?;
1563
1564        // Only ever consider the head of the queue: requests must be sent in
1565        // the order they were queued, so a wedged request (which failed to be
1566        // sent with an unrecoverable error) blocks all the requests queued
1567        // after it. Otherwise, messages would be sent out of order, until the
1568        // wedged request is either manually unwedged or removed (both of which
1569        // will wake up the sending task).
1570        if let Some(request) = queued_requests.first().filter(|queued| !queued.is_wedged()) {
1571            let (cancel_upload_tx, cancel_upload_rx) =
1572                if matches!(request.kind, QueuedRequestKind::MediaUpload { .. }) {
1573                    let (tx, rx) = oneshot::channel();
1574                    (Some(tx), Some(rx))
1575                } else {
1576                    Default::default()
1577                };
1578
1579            let prev = guard.being_sent.replace(BeingSentInfo {
1580                transaction_id: request.transaction_id.clone(),
1581                cancel_upload: cancel_upload_tx,
1582            });
1583
1584            if let Some(prev) = prev {
1585                error!(
1586                    prev_txn = ?prev.transaction_id,
1587                    "a previous request was still active while picking a new one"
1588                );
1589            }
1590
1591            Ok(Some((request.clone(), cancel_upload_rx)))
1592        } else {
1593            Ok(None)
1594        }
1595    }
1596
1597    /// Marks a request popped with [`Self::peek_next_to_send`] and identified
1598    /// with the given transaction id as not being sent anymore, so it can be
1599    /// removed from the queue later.
1600    async fn mark_as_not_being_sent(&self, transaction_id: &TransactionId) {
1601        let was_being_sent = self.store.lock().await.being_sent.take();
1602
1603        let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1604        if prev_txn != Some(transaction_id) {
1605            error!(prev_txn = ?prev_txn, "previous active request didn't match that we expect (after transient error)");
1606        }
1607    }
1608
1609    /// Marks a request popped with [`Self::peek_next_to_send`] and identified
1610    /// with the given transaction id as being wedged (and not being sent
1611    /// anymore), so it can be removed from the queue later.
1612    async fn mark_as_wedged(
1613        &self,
1614        transaction_id: &TransactionId,
1615        reason: QueueWedgeError,
1616    ) -> Result<(), RoomSendQueueStorageError> {
1617        // Keep the lock until we're done touching the storage.
1618        let mut guard = self.store.lock().await;
1619        let was_being_sent = guard.being_sent.take();
1620
1621        let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1622        if prev_txn != Some(transaction_id) {
1623            error!(
1624                ?prev_txn,
1625                "previous active request didn't match that we expect (after permanent error)",
1626            );
1627        }
1628
1629        Ok(guard
1630            .client()?
1631            .state_store()
1632            .update_send_queue_request_status(&self.room_id, transaction_id, Some(reason))
1633            .await?)
1634    }
1635
1636    /// Marks a request identified with the given transaction id as being now
1637    /// unwedged and adds it back to the queue.
1638    async fn mark_as_unwedged(
1639        &self,
1640        transaction_id: &TransactionId,
1641    ) -> Result<(), RoomSendQueueStorageError> {
1642        Ok(self
1643            .store
1644            .lock()
1645            .await
1646            .client()?
1647            .state_store()
1648            .update_send_queue_request_status(&self.room_id, transaction_id, None)
1649            .await?)
1650    }
1651
1652    /// Marks a request pushed with [`Self::push`] and identified with the given
1653    /// transaction id as sent, by removing it from the local queue.
1654    async fn mark_as_sent(
1655        &self,
1656        transaction_id: &TransactionId,
1657        parent_key: SentRequestKey,
1658    ) -> Result<(), RoomSendQueueStorageError> {
1659        // Keep the lock until we're done touching the storage.
1660        let mut guard = self.store.lock().await;
1661        let was_being_sent = guard.being_sent.take();
1662
1663        let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1664        if prev_txn != Some(transaction_id) {
1665            error!(
1666                ?prev_txn,
1667                "previous active request didn't match that we expect (after successful send)",
1668            );
1669        }
1670
1671        let client = guard.client()?;
1672        let store = client.state_store();
1673
1674        // Update all dependent requests.
1675        store
1676            .mark_dependent_queued_requests_as_ready(&self.room_id, transaction_id, parent_key)
1677            .await?;
1678
1679        let removed = store.remove_send_queue_request(&self.room_id, transaction_id).await?;
1680
1681        if !removed {
1682            warn!(txn_id = %transaction_id, "request marked as sent was missing from storage");
1683        }
1684
1685        self.thumbnail_file_sizes.lock().remove(transaction_id);
1686
1687        Ok(())
1688    }
1689
1690    /// Cancel a sending command for an event that has been sent with
1691    /// [`Self::push`] with the given transaction id.
1692    ///
1693    /// Returns whether the given transaction has been effectively removed. If
1694    /// false, this either means that the transaction id was unrelated to this
1695    /// queue, or that the request was sent before we cancelled it.
1696    async fn cancel_event(
1697        &self,
1698        transaction_id: &TransactionId,
1699        reason: Option<String>,
1700    ) -> Result<bool, RoomSendQueueStorageError> {
1701        let guard = self.store.lock().await;
1702
1703        if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1704            == Some(transaction_id)
1705        {
1706            // Save the intent to redact the event.
1707            guard
1708                .client()?
1709                .state_store()
1710                .save_dependent_queued_request(
1711                    &self.room_id,
1712                    transaction_id,
1713                    ChildTransactionId::new(),
1714                    MilliSecondsSinceUnixEpoch::now(),
1715                    DependentQueuedRequestKind::RedactEventWithReason { reason },
1716                )
1717                .await?;
1718
1719            return Ok(true);
1720        }
1721
1722        let removed = guard
1723            .client()?
1724            .state_store()
1725            .remove_send_queue_request(&self.room_id, transaction_id)
1726            .await?;
1727
1728        self.thumbnail_file_sizes.lock().remove(transaction_id);
1729
1730        Ok(removed)
1731    }
1732
1733    /// Replace an event that has been sent with [`Self::push`] with the given
1734    /// transaction id, before it's been actually sent.
1735    ///
1736    /// Returns whether the given transaction has been effectively edited. If
1737    /// false, this either means that the transaction id was unrelated to this
1738    /// queue, or that the request was sent before we edited it.
1739    async fn replace_event(
1740        &self,
1741        transaction_id: &TransactionId,
1742        serializable: SerializableEventContent,
1743    ) -> Result<bool, RoomSendQueueStorageError> {
1744        let guard = self.store.lock().await;
1745        let client = guard.client()?;
1746        let store = client.state_store();
1747
1748        // Only an event the user composed has content to replace: a redaction or a
1749        // reaction has nothing to put the new content into.
1750        if !store.load_send_queue_requests(&self.room_id).await?.iter().any(|request| {
1751            request.transaction_id == transaction_id && is_own_event_request(request)
1752        }) {
1753            return Ok(false);
1754        }
1755
1756        if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1757            == Some(transaction_id)
1758        {
1759            // Save the intent to edit the associated event.
1760            store
1761                .save_dependent_queued_request(
1762                    &self.room_id,
1763                    transaction_id,
1764                    ChildTransactionId::new(),
1765                    MilliSecondsSinceUnixEpoch::now(),
1766                    DependentQueuedRequestKind::EditEvent { new_content: serializable },
1767                )
1768                .await?;
1769
1770            return Ok(true);
1771        }
1772
1773        let request = QueuedRequestKind::Event {
1774            content: serializable,
1775            #[cfg(feature = "unstable-msc4354")]
1776            sticky_duration: self.sticky_duration_of(store, transaction_id).await?,
1777        };
1778
1779        let edited =
1780            store.update_send_queue_request(&self.room_id, transaction_id, request).await?;
1781
1782        Ok(edited)
1783    }
1784
1785    /// The sticky duration of the queued event `transaction_id`, if it is
1786    /// queued and sticky.
1787    #[cfg(feature = "unstable-msc4354")]
1788    async fn sticky_duration_of(
1789        &self,
1790        store: &DynStateStore,
1791        transaction_id: &TransactionId,
1792    ) -> Result<Option<StickyDurationMs>, RoomSendQueueStorageError> {
1793        let sticky_duration = store
1794            .load_send_queue_requests(&self.room_id)
1795            .await?
1796            .into_iter()
1797            .find(|request| request.transaction_id == *transaction_id)
1798            .and_then(|request| match request.kind {
1799                QueuedRequestKind::Event { sticky_duration, .. } => sticky_duration,
1800                _ => None,
1801            });
1802
1803        Ok(sticky_duration)
1804    }
1805
1806    /// Push requests (and dependents) to upload a media.
1807    ///
1808    /// See the module-level description for details of the whole processus.
1809    #[allow(clippy::too_many_arguments)]
1810    async fn push_media(
1811        &self,
1812        event: RoomMessageEventContent,
1813        content_type: Mime,
1814        send_event_txn: OwnedTransactionId,
1815        created_at: MilliSecondsSinceUnixEpoch,
1816        upload_file_txn: OwnedTransactionId,
1817        file_media_request: MediaRequestParameters,
1818        thumbnail: Option<QueueThumbnailInfo>,
1819        extra_content: Option<serde_json::Map<String, serde_json::Value>>,
1820    ) -> Result<(), RoomSendQueueStorageError> {
1821        let guard = self.store.lock().await;
1822        let client = guard.client()?;
1823        let store = client.state_store();
1824
1825        // There's only a single media to be sent, so it has at most one
1826        // thumbnail.
1827        let thumbnail_file_sizes = vec![thumbnail.as_ref().map(|t| t.file_size)];
1828
1829        let thumbnail_info = self
1830            .push_thumbnail_and_media_uploads(
1831                store,
1832                &content_type,
1833                send_event_txn.clone(),
1834                created_at,
1835                upload_file_txn.clone(),
1836                file_media_request,
1837                thumbnail,
1838            )
1839            .await?;
1840
1841        // Push the dependent request for the event itself.
1842        store
1843            .save_dependent_queued_request(
1844                &self.room_id,
1845                &upload_file_txn,
1846                send_event_txn.clone().into(),
1847                created_at,
1848                DependentQueuedRequestKind::FinishUpload {
1849                    local_echo: Box::new(event),
1850                    file_upload: upload_file_txn.clone(),
1851                    thumbnail_info,
1852                    extra_content,
1853                },
1854            )
1855            .await?;
1856
1857        self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
1858
1859        Ok(())
1860    }
1861
1862    /// Push requests (and dependents) to upload a gallery.
1863    ///
1864    /// See the module-level description for details of the whole processus.
1865    #[cfg(feature = "unstable-msc4274")]
1866    #[allow(clippy::too_many_arguments)]
1867    async fn push_gallery(
1868        &self,
1869        event: RoomMessageEventContent,
1870        send_event_txn: OwnedTransactionId,
1871        created_at: MilliSecondsSinceUnixEpoch,
1872        item_queue_infos: Vec<GalleryItemQueueInfo>,
1873    ) -> Result<(), RoomSendQueueStorageError> {
1874        let guard = self.store.lock().await;
1875        let client = guard.client()?;
1876        let store = client.state_store();
1877
1878        let mut finish_item_infos = Vec::with_capacity(item_queue_infos.len());
1879        let mut thumbnail_file_sizes = Vec::with_capacity(item_queue_infos.len());
1880
1881        let Some((first, rest)) = item_queue_infos.split_first() else {
1882            return Ok(());
1883        };
1884
1885        let GalleryItemQueueInfo { content_type, upload_file_txn, file_media_request, thumbnail } =
1886            first;
1887
1888        let thumbnail_info = self
1889            .push_thumbnail_and_media_uploads(
1890                store,
1891                content_type,
1892                send_event_txn.clone(),
1893                created_at,
1894                upload_file_txn.clone(),
1895                file_media_request.clone(),
1896                thumbnail.clone(),
1897            )
1898            .await?;
1899
1900        finish_item_infos
1901            .push(FinishGalleryItemInfo { file_upload: upload_file_txn.clone(), thumbnail_info });
1902        thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
1903
1904        let mut last_upload_file_txn = upload_file_txn.clone();
1905
1906        for item_queue_info in rest {
1907            let GalleryItemQueueInfo {
1908                content_type,
1909                upload_file_txn,
1910                file_media_request,
1911                thumbnail,
1912            } = item_queue_info;
1913
1914            let thumbnail_info = if let Some(QueueThumbnailInfo {
1915                finish_upload_thumbnail_info: thumbnail_info,
1916                media_request_parameters: thumbnail_media_request,
1917                content_type: thumbnail_content_type,
1918                ..
1919            }) = thumbnail
1920            {
1921                let upload_thumbnail_txn = thumbnail_info.txn.clone();
1922
1923                // Save the thumbnail upload request as a dependent request of
1924                // the last file upload.
1925                store
1926                    .save_dependent_queued_request(
1927                        &self.room_id,
1928                        &last_upload_file_txn,
1929                        upload_thumbnail_txn.clone().into(),
1930                        created_at,
1931                        DependentQueuedRequestKind::UploadFileOrThumbnail {
1932                            content_type: thumbnail_content_type.to_string(),
1933                            cache_key: thumbnail_media_request.clone(),
1934                            related_to: send_event_txn.clone(),
1935                            parent_is_thumbnail_upload: false,
1936                        },
1937                    )
1938                    .await?;
1939
1940                last_upload_file_txn = upload_thumbnail_txn;
1941
1942                Some(thumbnail_info)
1943            } else {
1944                None
1945            };
1946
1947            // Save the file upload as a dependent request of the previous
1948            // upload.
1949            store
1950                .save_dependent_queued_request(
1951                    &self.room_id,
1952                    &last_upload_file_txn,
1953                    upload_file_txn.clone().into(),
1954                    created_at,
1955                    DependentQueuedRequestKind::UploadFileOrThumbnail {
1956                        content_type: content_type.to_string(),
1957                        cache_key: file_media_request.clone(),
1958                        related_to: send_event_txn.clone(),
1959                        parent_is_thumbnail_upload: thumbnail.is_some(),
1960                    },
1961                )
1962                .await?;
1963
1964            finish_item_infos.push(FinishGalleryItemInfo {
1965                file_upload: upload_file_txn.clone(),
1966                thumbnail_info: thumbnail_info.cloned(),
1967            });
1968            thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
1969
1970            last_upload_file_txn = upload_file_txn.clone();
1971        }
1972
1973        // Push the request for the event itself as a dependent request of the
1974        // last file upload.
1975        store
1976            .save_dependent_queued_request(
1977                &self.room_id,
1978                &last_upload_file_txn,
1979                send_event_txn.clone().into(),
1980                created_at,
1981                DependentQueuedRequestKind::FinishGallery {
1982                    local_echo: Box::new(event),
1983                    item_infos: finish_item_infos,
1984                },
1985            )
1986            .await?;
1987
1988        self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
1989
1990        Ok(())
1991    }
1992
1993    /// If a thumbnail exists, pushes a [`QueuedRequestKind::MediaUpload`] to
1994    /// upload it and a [`DependentQueuedRequestKind::UploadFileOrThumbnail`] to
1995    /// upload the media itself. Otherwise, pushes a
1996    /// [`QueuedRequestKind::MediaUpload`] to upload the media directly.
1997    #[allow(clippy::too_many_arguments)]
1998    async fn push_thumbnail_and_media_uploads(
1999        &self,
2000        store: &DynStateStore,
2001        content_type: &Mime,
2002        send_event_txn: OwnedTransactionId,
2003        created_at: MilliSecondsSinceUnixEpoch,
2004        upload_file_txn: OwnedTransactionId,
2005        file_media_request: MediaRequestParameters,
2006        thumbnail: Option<QueueThumbnailInfo>,
2007    ) -> Result<Option<FinishUploadThumbnailInfo>, RoomSendQueueStorageError> {
2008        if let Some(QueueThumbnailInfo {
2009            finish_upload_thumbnail_info: thumbnail_info,
2010            media_request_parameters: thumbnail_media_request,
2011            content_type: thumbnail_content_type,
2012            ..
2013        }) = thumbnail
2014        {
2015            let upload_thumbnail_txn = thumbnail_info.txn.clone();
2016
2017            // Save the thumbnail upload request.
2018            store
2019                .save_send_queue_request(
2020                    &self.room_id,
2021                    upload_thumbnail_txn.clone(),
2022                    created_at,
2023                    QueuedRequestKind::MediaUpload {
2024                        content_type: thumbnail_content_type.to_string(),
2025                        cache_key: thumbnail_media_request,
2026                        thumbnail_source: None, // the thumbnail has no thumbnails :)
2027                        related_to: send_event_txn.clone(),
2028                        #[cfg(feature = "unstable-msc4274")]
2029                        accumulated: vec![],
2030                    },
2031                    Self::LOW_PRIORITY,
2032                )
2033                .await?;
2034
2035            // Save the file upload request as a dependent request of the
2036            // thumbnail upload.
2037            store
2038                .save_dependent_queued_request(
2039                    &self.room_id,
2040                    &upload_thumbnail_txn,
2041                    upload_file_txn.into(),
2042                    created_at,
2043                    DependentQueuedRequestKind::UploadFileOrThumbnail {
2044                        content_type: content_type.to_string(),
2045                        cache_key: file_media_request,
2046                        related_to: send_event_txn,
2047                        parent_is_thumbnail_upload: true,
2048                    },
2049                )
2050                .await?;
2051
2052            Ok(Some(thumbnail_info))
2053        } else {
2054            // Save the file upload as its own request, not a dependent one.
2055            store
2056                .save_send_queue_request(
2057                    &self.room_id,
2058                    upload_file_txn,
2059                    created_at,
2060                    QueuedRequestKind::MediaUpload {
2061                        content_type: content_type.to_string(),
2062                        cache_key: file_media_request,
2063                        thumbnail_source: None,
2064                        related_to: send_event_txn,
2065                        #[cfg(feature = "unstable-msc4274")]
2066                        accumulated: vec![],
2067                    },
2068                    Self::LOW_PRIORITY,
2069                )
2070                .await?;
2071
2072            Ok(None)
2073        }
2074    }
2075
2076    /// Reacts to the given local echo of an event.
2077    #[instrument(skip(self))]
2078    async fn react(
2079        &self,
2080        transaction_id: &TransactionId,
2081        key: String,
2082        created_at: MilliSecondsSinceUnixEpoch,
2083    ) -> Result<Option<ChildTransactionId>, RoomSendQueueStorageError> {
2084        let guard = self.store.lock().await;
2085        let client = guard.client()?;
2086        let store = client.state_store();
2087
2088        let requests = store.load_send_queue_requests(&self.room_id).await?;
2089
2090        // If the target event has been already sent, or isn't something that can be
2091        // reacted to in the first place, abort immediately.
2092        if !requests
2093            .iter()
2094            .any(|item| item.transaction_id == transaction_id && is_own_event_request(item))
2095        {
2096            // We didn't find it as a queued request; try to find it as a
2097            // dependent queued request.
2098            let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
2099            if !dependent_requests
2100                .into_iter()
2101                .filter_map(|item| item.is_own_event().then_some(item.own_transaction_id))
2102                .any(|child_txn| *child_txn == *transaction_id)
2103            {
2104                // We didn't find it as either a request or a dependent request,
2105                // abort.
2106                return Ok(None);
2107            }
2108        }
2109
2110        // Record the dependent request.
2111        let reaction_txn_id = ChildTransactionId::new();
2112        store
2113            .save_dependent_queued_request(
2114                &self.room_id,
2115                transaction_id,
2116                reaction_txn_id.clone(),
2117                created_at,
2118                DependentQueuedRequestKind::ReactEvent { key },
2119            )
2120            .await?;
2121
2122        Ok(Some(reaction_txn_id))
2123    }
2124
2125    /// Returns a list of the local echoes, that is, all the requests that we're
2126    /// about to send but that haven't been sent yet (or are being sent).
2127    async fn local_echoes(
2128        &self,
2129        room: &RoomSendQueue,
2130    ) -> Result<Vec<LocalEcho>, RoomSendQueueStorageError> {
2131        let guard = self.store.lock().await;
2132        let client = guard.client()?;
2133        let store = client.state_store();
2134
2135        let queued_requests = store.load_send_queue_requests(&self.room_id).await?;
2136
2137        // Media upload requests aren't returned as echoes themselves (the media
2138        // event, represented as a dependent request, is), so carry their send
2139        // errors over to the dependent request's echo: a wedged upload wedges
2140        // the media event.
2141        let mut media_upload_errors: HashMap<OwnedTransactionId, QueueWedgeError> = queued_requests
2142            .iter()
2143            .filter_map(|queued| match queued.kind {
2144                QueuedRequestKind::MediaUpload { .. } => {
2145                    queued.error.clone().map(|error| (queued.transaction_id.clone(), error))
2146                }
2147                _ => None,
2148            })
2149            .collect();
2150
2151        let local_requests = queued_requests.into_iter().filter_map(|queued| {
2152            Some(LocalEcho {
2153                transaction_id: queued.transaction_id.clone(),
2154                content: match queued.kind {
2155                    QueuedRequestKind::Event { content, .. } => LocalEchoContent::Event {
2156                        serialized_event: content,
2157                        send_handle: SendHandle {
2158                            room: room.clone(),
2159                            transaction_id: queued.transaction_id,
2160                            media_handles: vec![],
2161                            created_at: queued.created_at,
2162                        },
2163                        send_error: queued.error,
2164                    },
2165
2166                    QueuedRequestKind::MediaUpload { .. } => {
2167                        // Don't return uploaded medias as their own things; the
2168                        // accompanying event represented as a dependent request
2169                        // should be sufficient.
2170                        return None;
2171                    }
2172
2173                    QueuedRequestKind::Redaction { redacts, reason } => {
2174                        LocalEchoContent::Redaction {
2175                            redacts,
2176                            reason,
2177                            send_handle: SendHandle {
2178                                room: room.clone(),
2179                                transaction_id: queued.transaction_id,
2180                                media_handles: vec![],
2181                                created_at: queued.created_at,
2182                            },
2183                            send_error: queued.error,
2184                        }
2185                    }
2186                },
2187            })
2188        });
2189
2190        let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
2191
2192        let reactions_and_medias =
2193            dependent_requests.into_iter().filter_map(|dep| match dep.kind {
2194                DependentQueuedRequestKind::EditEvent { .. }
2195                | DependentQueuedRequestKind::RedactEvent
2196                | DependentQueuedRequestKind::RedactEventWithReason { .. } => {
2197                    // TODO: reflect local edits/redacts too?
2198                    None
2199                }
2200
2201                DependentQueuedRequestKind::ReactEvent { key } => Some(LocalEcho {
2202                    transaction_id: dep.own_transaction_id.clone().into(),
2203                    content: LocalEchoContent::React {
2204                        key,
2205                        send_handle: SendHandle {
2206                            room: room.clone(),
2207                            transaction_id: dep.own_transaction_id.into(),
2208                            media_handles: vec![],
2209                            created_at: dep.created_at,
2210                        },
2211                        applies_to: dep.parent_transaction_id,
2212                    },
2213                }),
2214
2215                DependentQueuedRequestKind::UploadFileOrThumbnail { .. } => {
2216                    // Don't reflect these: only the associated event is
2217                    // interesting to observers.
2218                    None
2219                }
2220
2221                DependentQueuedRequestKind::FinishUpload {
2222                    local_echo,
2223                    file_upload,
2224                    thumbnail_info,
2225                    extra_content,
2226                } => {
2227                    let upload_thumbnail_txn = thumbnail_info.map(|info| info.txn);
2228
2229                    // If one of the uploads wedged, the media event is wedged
2230                    // too.
2231                    let send_error = media_upload_errors.remove(&file_upload).or_else(|| {
2232                        upload_thumbnail_txn
2233                            .as_ref()
2234                            .and_then(|txn| media_upload_errors.remove(&**txn))
2235                    });
2236
2237                    // Materialize as an event local echo.
2238                    Some(LocalEcho {
2239                        transaction_id: dep.own_transaction_id.clone().into(),
2240                        content: LocalEchoContent::Event {
2241                            serialized_event: upload::merge_extra_content(
2242                                SerializableEventContent::new(&(*local_echo).into()).ok()?,
2243                                extra_content,
2244                            )
2245                            .ok()?,
2246                            send_handle: SendHandle {
2247                                room: room.clone(),
2248                                transaction_id: dep.own_transaction_id.into(),
2249                                media_handles: vec![MediaHandles {
2250                                    upload_thumbnail_txn,
2251                                    upload_file_txn: file_upload,
2252                                }],
2253                                created_at: dep.created_at,
2254                            },
2255                            send_error,
2256                        },
2257                    })
2258                }
2259
2260                #[cfg(feature = "unstable-msc4274")]
2261                DependentQueuedRequestKind::FinishGallery { local_echo, item_infos } => {
2262                    // Materialize as an event local echo.
2263                    self.create_gallery_local_echo(
2264                        dep.own_transaction_id,
2265                        room,
2266                        dep.created_at,
2267                        local_echo,
2268                        item_infos,
2269                        &mut media_upload_errors,
2270                    )
2271                }
2272            });
2273
2274        Ok(local_requests.chain(reactions_and_medias).collect())
2275    }
2276
2277    /// Create a local echo for a gallery event.
2278    #[cfg(feature = "unstable-msc4274")]
2279    fn create_gallery_local_echo(
2280        &self,
2281        transaction_id: ChildTransactionId,
2282        room: &RoomSendQueue,
2283        created_at: MilliSecondsSinceUnixEpoch,
2284        local_echo: Box<RoomMessageEventContent>,
2285        item_infos: Vec<FinishGalleryItemInfo>,
2286        media_upload_errors: &mut HashMap<OwnedTransactionId, QueueWedgeError>,
2287    ) -> Option<LocalEcho> {
2288        // If any of the uploads wedged, the gallery event is wedged too.
2289        let send_error = item_infos.iter().find_map(|i| {
2290            media_upload_errors.remove(&i.file_upload).or_else(|| {
2291                i.thumbnail_info.as_ref().and_then(|info| media_upload_errors.remove(&*info.txn))
2292            })
2293        });
2294
2295        Some(LocalEcho {
2296            transaction_id: transaction_id.clone().into(),
2297            content: LocalEchoContent::Event {
2298                serialized_event: SerializableEventContent::new(&(*local_echo).into()).ok()?,
2299                send_handle: SendHandle {
2300                    room: room.clone(),
2301                    transaction_id: transaction_id.into(),
2302                    media_handles: item_infos
2303                        .into_iter()
2304                        .map(|i| MediaHandles {
2305                            upload_thumbnail_txn: i.thumbnail_info.map(|info| info.txn),
2306                            upload_file_txn: i.file_upload,
2307                        })
2308                        .collect(),
2309                    created_at,
2310                },
2311                send_error,
2312            },
2313        })
2314    }
2315
2316    /// Try to apply a single dependent request, whether it's local or remote.
2317    ///
2318    /// This swallows errors that would retrigger every time if we retried
2319    /// applying the dependent request: invalid edit content, etc.
2320    ///
2321    /// Returns true if the dependent request has been sent (or should not be
2322    /// retried later).
2323    #[instrument(skip_all)]
2324    async fn try_apply_single_dependent_request(
2325        &self,
2326        client: &Client,
2327        dependent_request: DependentQueuedRequest,
2328        new_updates: &mut Vec<RoomSendQueueUpdate>,
2329    ) -> Result<bool, RoomSendQueueError> {
2330        let store = client.state_store();
2331
2332        let parent_key = dependent_request.parent_key;
2333
2334        match dependent_request.kind {
2335            DependentQueuedRequestKind::EditEvent { new_content } => {
2336                if let Some(parent_key) = parent_key {
2337                    let Some(event_id) = parent_key.into_event_id() else {
2338                        return Err(RoomSendQueueError::StorageError(
2339                            RoomSendQueueStorageError::InvalidParentKey,
2340                        ));
2341                    };
2342
2343                    // The parent event has been sent, so send an edit event.
2344                    let room = client
2345                        .get_room(&self.room_id)
2346                        .ok_or(RoomSendQueueError::RoomDisappeared)?;
2347
2348                    // Check the event is one we know how to edit with an edit
2349                    // event.
2350
2351                    // It must be deserializable…
2352                    let edited_content = match new_content.deserialize() {
2353                        Ok(AnyMessageLikeEventContent::RoomMessage(c)) => {
2354                            // Assume no relationships.
2355                            EditedContent::RoomMessage(c.into())
2356                        }
2357
2358                        Ok(AnyMessageLikeEventContent::UnstablePollStart(c)) => {
2359                            let poll_start = c.poll_start().clone();
2360                            EditedContent::PollStart {
2361                                fallback_text: poll_start.question.text.clone(),
2362                                new_content: poll_start,
2363                            }
2364                        }
2365
2366                        Ok(c) => {
2367                            warn!("Unsupported edit content type: {:?}", c.event_type());
2368                            return Ok(true);
2369                        }
2370
2371                        Err(err) => {
2372                            warn!("Unable to deserialize: {err}");
2373                            return Ok(true);
2374                        }
2375                    };
2376
2377                    let edit_event = match room.make_edit_event(&event_id, edited_content).await {
2378                        Ok(e) => e,
2379                        Err(err) => {
2380                            warn!("couldn't create edited event: {err}");
2381                            return Ok(true);
2382                        }
2383                    };
2384
2385                    // Queue the edit event in the send queue 🧠.
2386                    let serializable = SerializableEventContent::from_raw(
2387                        Raw::new(&edit_event)
2388                            .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2389                        edit_event.event_type().to_string(),
2390                    );
2391
2392                    store
2393                        .save_send_queue_request(
2394                            &self.room_id,
2395                            dependent_request.own_transaction_id.into(),
2396                            dependent_request.created_at,
2397                            serializable.into(),
2398                            Self::HIGH_PRIORITY,
2399                        )
2400                        .await
2401                        .map_err(RoomSendQueueStorageError::StateStoreError)?;
2402                } else {
2403                    // The parent event is still local; update the local echo.
2404                    let parent_transaction_id = &dependent_request.parent_transaction_id;
2405
2406                    let request = QueuedRequestKind::Event {
2407                        content: new_content,
2408                        #[cfg(feature = "unstable-msc4354")]
2409                        sticky_duration: self
2410                            .sticky_duration_of(store, parent_transaction_id)
2411                            .await?,
2412                    };
2413
2414                    let edited = store
2415                        .update_send_queue_request(&self.room_id, parent_transaction_id, request)
2416                        .await
2417                        .map_err(RoomSendQueueStorageError::StateStoreError)?;
2418
2419                    if !edited {
2420                        warn!("missing local echo upon dependent edit");
2421                    }
2422                }
2423            }
2424
2425            kind @ (DependentQueuedRequestKind::RedactEvent
2426            | DependentQueuedRequestKind::RedactEventWithReason { .. }) => {
2427                let reason = match kind {
2428                    DependentQueuedRequestKind::RedactEventWithReason { reason } => reason,
2429                    // The legacy variant carries no reason.
2430                    _ => None,
2431                };
2432
2433                if let Some(parent_key) = parent_key {
2434                    let Some(event_id) = parent_key.into_event_id() else {
2435                        return Err(RoomSendQueueError::StorageError(
2436                            RoomSendQueueStorageError::InvalidParentKey,
2437                        ));
2438                    };
2439
2440                    // The parent event has been sent; send a redaction.
2441                    let room = client
2442                        .get_room(&self.room_id)
2443                        .ok_or(RoomSendQueueError::RoomDisappeared)?;
2444
2445                    // Ideally we'd use the send queue to send the redaction,
2446                    // but the protocol has changed the shape of a
2447                    // room.redaction after v11, so keep it simple and try once
2448                    // here.
2449
2450                    if let Err(err) = room
2451                        .redact(
2452                            &event_id,
2453                            reason.as_deref(),
2454                            Some(dependent_request.own_transaction_id.into()),
2455                        )
2456                        .await
2457                    {
2458                        warn!("error when sending a redact for {event_id}: {err}");
2459                        return Ok(false);
2460                    }
2461                } else {
2462                    // The parent event is still local (sending must have
2463                    // failed); redact the local echo.
2464                    let removed = store
2465                        .remove_send_queue_request(
2466                            &self.room_id,
2467                            &dependent_request.parent_transaction_id,
2468                        )
2469                        .await
2470                        .map_err(RoomSendQueueStorageError::StateStoreError)?;
2471
2472                    if !removed {
2473                        warn!("missing local echo upon dependent redact");
2474                    }
2475                }
2476            }
2477
2478            DependentQueuedRequestKind::ReactEvent { key } => {
2479                if let Some(parent_key) = parent_key {
2480                    let Some(parent_event_id) = parent_key.into_event_id() else {
2481                        return Err(RoomSendQueueError::StorageError(
2482                            RoomSendQueueStorageError::InvalidParentKey,
2483                        ));
2484                    };
2485
2486                    // Queue the reaction event in the send queue 🧠.
2487                    let react_event =
2488                        ReactionEventContent::new(Annotation::new(parent_event_id, key)).into();
2489                    let serializable = SerializableEventContent::from_raw(
2490                        Raw::new(&react_event)
2491                            .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2492                        react_event.event_type().to_string(),
2493                    );
2494
2495                    store
2496                        .save_send_queue_request(
2497                            &self.room_id,
2498                            dependent_request.own_transaction_id.into(),
2499                            dependent_request.created_at,
2500                            serializable.into(),
2501                            Self::HIGH_PRIORITY,
2502                        )
2503                        .await
2504                        .map_err(RoomSendQueueStorageError::StateStoreError)?;
2505                } else {
2506                    // Not applied yet, we should retry later => false.
2507                    return Ok(false);
2508                }
2509            }
2510
2511            DependentQueuedRequestKind::UploadFileOrThumbnail {
2512                content_type,
2513                cache_key,
2514                related_to,
2515                parent_is_thumbnail_upload,
2516            } => {
2517                let Some(parent_key) = parent_key else {
2518                    // Not finished yet, we should retry later => false.
2519                    return Ok(false);
2520                };
2521                self.handle_dependent_file_or_thumbnail_upload(
2522                    client,
2523                    dependent_request.own_transaction_id.into(),
2524                    parent_key,
2525                    content_type,
2526                    cache_key,
2527                    related_to,
2528                    parent_is_thumbnail_upload,
2529                )
2530                .await?;
2531            }
2532
2533            DependentQueuedRequestKind::FinishUpload {
2534                local_echo,
2535                file_upload,
2536                thumbnail_info,
2537                extra_content,
2538            } => {
2539                let Some(parent_key) = parent_key else {
2540                    // Not finished yet, we should retry later => false.
2541                    return Ok(false);
2542                };
2543                self.handle_dependent_finish_upload(
2544                    client,
2545                    dependent_request.own_transaction_id.into(),
2546                    parent_key,
2547                    *local_echo,
2548                    file_upload,
2549                    thumbnail_info,
2550                    extra_content,
2551                    new_updates,
2552                )
2553                .await?;
2554            }
2555
2556            #[cfg(feature = "unstable-msc4274")]
2557            DependentQueuedRequestKind::FinishGallery { local_echo, item_infos } => {
2558                let Some(parent_key) = parent_key else {
2559                    // Not finished yet, we should retry later => false.
2560                    return Ok(false);
2561                };
2562                self.handle_dependent_finish_gallery_upload(
2563                    client,
2564                    dependent_request.own_transaction_id.into(),
2565                    parent_key,
2566                    *local_echo,
2567                    item_infos,
2568                    new_updates,
2569                )
2570                .await?;
2571            }
2572        }
2573
2574        Ok(true)
2575    }
2576
2577    #[instrument(skip(self))]
2578    async fn apply_dependent_requests(
2579        &self,
2580        new_updates: &mut Vec<RoomSendQueueUpdate>,
2581    ) -> Result<(), RoomSendQueueError> {
2582        let guard = self.store.lock().await;
2583
2584        let client = guard.client()?;
2585        let store = client.state_store();
2586
2587        let dependent_requests = store
2588            .load_dependent_queued_requests(&self.room_id)
2589            .await
2590            .map_err(RoomSendQueueStorageError::StateStoreError)?;
2591
2592        let num_initial_dependent_requests = dependent_requests.len();
2593        if num_initial_dependent_requests == 0 {
2594            // Returning early here avoids a bit of useless logging.
2595            return Ok(());
2596        }
2597
2598        let canonicalized_dependent_requests = canonicalize_dependent_requests(&dependent_requests);
2599
2600        // Get rid of the all non-canonical dependent events.
2601        for original in &dependent_requests {
2602            if !canonicalized_dependent_requests
2603                .iter()
2604                .any(|canonical| canonical.own_transaction_id == original.own_transaction_id)
2605            {
2606                store
2607                    .remove_dependent_queued_request(&self.room_id, &original.own_transaction_id)
2608                    .await
2609                    .map_err(RoomSendQueueStorageError::StateStoreError)?;
2610            }
2611        }
2612
2613        let mut num_dependent_requests = canonicalized_dependent_requests.len();
2614
2615        debug!(
2616            num_dependent_requests,
2617            num_initial_dependent_requests, "starting handling of dependent requests"
2618        );
2619
2620        for dependent in canonicalized_dependent_requests {
2621            let dependent_id = dependent.own_transaction_id.clone();
2622
2623            match self.try_apply_single_dependent_request(&client, dependent, new_updates).await {
2624                Ok(should_remove) => {
2625                    if should_remove {
2626                        // The dependent request has been successfully applied,
2627                        // forget about it.
2628                        store
2629                            .remove_dependent_queued_request(&self.room_id, &dependent_id)
2630                            .await
2631                            .map_err(RoomSendQueueStorageError::StateStoreError)?;
2632
2633                        num_dependent_requests -= 1;
2634                    }
2635                }
2636
2637                Err(err) => {
2638                    warn!("error when applying single dependent request: {err}");
2639                }
2640            }
2641        }
2642
2643        debug!(
2644            leftover_dependent_requests = num_dependent_requests,
2645            "stopped handling dependent request"
2646        );
2647
2648        Ok(())
2649    }
2650
2651    /// Remove a single dependent request from storage.
2652    async fn remove_dependent_send_queue_request(
2653        &self,
2654        dependent_event_id: &ChildTransactionId,
2655    ) -> Result<bool, RoomSendQueueStorageError> {
2656        Ok(self
2657            .store
2658            .lock()
2659            .await
2660            .client()?
2661            .state_store()
2662            .remove_dependent_queued_request(&self.room_id, dependent_event_id)
2663            .await?)
2664    }
2665}
2666
2667#[cfg(feature = "unstable-msc4274")]
2668/// Metadata needed for pushing gallery item uploads onto the send queue.
2669struct GalleryItemQueueInfo {
2670    content_type: Mime,
2671    upload_file_txn: OwnedTransactionId,
2672    file_media_request: MediaRequestParameters,
2673    thumbnail: Option<QueueThumbnailInfo>,
2674}
2675
2676/// The content of a local echo.
2677#[derive(Clone, Debug)]
2678pub enum LocalEchoContent {
2679    /// The local echo contains an actual event ready to display.
2680    Event {
2681        /// Content of the event itself (along with its type) that we are about
2682        /// to send.
2683        serialized_event: SerializableEventContent,
2684        /// A handle to manipulate the sending of the associated event.
2685        send_handle: SendHandle,
2686        /// Whether trying to send this local echo failed in the past with an
2687        /// unrecoverable error (see [`SendQueueRoomError::is_recoverable`]).
2688        send_error: Option<QueueWedgeError>,
2689    },
2690
2691    /// A local echo has been reacted to.
2692    React {
2693        /// The key with which the local echo has been reacted to.
2694        key: String,
2695        /// A handle to manipulate the sending of the reaction.
2696        send_handle: SendHandle,
2697        /// The local echo which has been reacted to.
2698        applies_to: OwnedTransactionId,
2699    },
2700
2701    /// A local echo of a redaction event.
2702    Redaction {
2703        /// The ID of the redacted event.
2704        redacts: OwnedEventId,
2705        /// The reason for the event being redacted.
2706        reason: Option<String>,
2707        /// A handle to manipulate the sending of the associated event.
2708        send_handle: SendHandle,
2709        /// Whether trying to send this local echo failed in the past with an
2710        /// unrecoverable error (see [`SendQueueRoomError::is_recoverable`]).
2711        send_error: Option<QueueWedgeError>,
2712    },
2713}
2714
2715/// A local representation for a request that hasn't been sent yet to the user's
2716/// homeserver.
2717#[derive(Clone, Debug)]
2718pub struct LocalEcho {
2719    /// Transaction id used to identify the associated request.
2720    pub transaction_id: OwnedTransactionId,
2721    /// The content for the local echo.
2722    pub content: LocalEchoContent,
2723}
2724
2725/// An update to a room send queue, observable with
2726/// [`RoomSendQueue::subscribe`].
2727#[derive(Clone, Debug)]
2728pub enum RoomSendQueueUpdate {
2729    /// A new local event is being sent.
2730    ///
2731    /// There's been a user query to create this event. It is being sent to the
2732    /// server.
2733    NewLocalEvent(LocalEcho),
2734
2735    /// A local event that hadn't been sent to the server yet has been cancelled
2736    /// before sending.
2737    CancelledLocalEvent {
2738        /// Transaction id used to identify this event.
2739        transaction_id: OwnedTransactionId,
2740    },
2741
2742    /// A local event's content has been replaced with something else.
2743    ReplacedLocalEvent {
2744        /// Transaction id used to identify this event.
2745        transaction_id: OwnedTransactionId,
2746
2747        /// The new content replacing the previous one.
2748        new_content: SerializableEventContent,
2749    },
2750
2751    /// An error happened when an event was being sent.
2752    ///
2753    /// The event has not been removed from the queue. A recoverable error
2754    /// disables the room's send queue, which must then be manually re-enabled;
2755    /// an unrecoverable one wedges the request, which blocks its room's queue
2756    /// until the request is unwedged or aborted.
2757    SendError {
2758        /// Transaction id used to identify this event.
2759        transaction_id: OwnedTransactionId,
2760        /// Error received while sending the event.
2761        error: Arc<crate::Error>,
2762        /// Whether the error is considered recoverable or not.
2763        ///
2764        /// An error that's recoverable will disable the room's send queue,
2765        /// while an unrecoverable error will be parked, until it's retried or
2766        /// aborted.
2767        is_recoverable: bool,
2768    },
2769
2770    /// The event has been unwedged and sending is now being retried.
2771    RetryEvent {
2772        /// Transaction id used to identify this event.
2773        transaction_id: OwnedTransactionId,
2774    },
2775
2776    /// The event has been sent to the server, and the query returned
2777    /// successfully.
2778    SentEvent {
2779        /// Transaction id used to identify this event.
2780        transaction_id: OwnedTransactionId,
2781        /// Received event id from the send response.
2782        event_id: OwnedEventId,
2783    },
2784
2785    /// A media upload (consisting of a file and possibly a thumbnail) has made
2786    /// progress.
2787    MediaUpload {
2788        /// The media event this uploaded media relates to.
2789        related_to: OwnedTransactionId,
2790
2791        /// The final media source for the file if it has finished uploading.
2792        file: Option<MediaSource>,
2793
2794        /// The index of the media within the transaction. A file and its
2795        /// thumbnail share the same index. Will always be 0 for non-gallery
2796        /// media uploads.
2797        index: u64,
2798
2799        /// The combined upload progress across the file and, if existing, its
2800        /// thumbnail. For gallery uploads, the progress is reported per indexed
2801        /// gallery item.
2802        progress: AbstractProgress,
2803    },
2804}
2805
2806/// A [`RoomSendQueueUpdate`] with an associated [`OwnedRoomId`].
2807///
2808/// This is used by [`SendQueue::subscribe`] to get a single channel to receive
2809/// updates for all [`RoomSendQueue`]s.
2810#[derive(Clone, Debug)]
2811pub struct SendQueueUpdate {
2812    /// The room where the update happened.
2813    pub room_id: OwnedRoomId,
2814
2815    /// The update for this room.
2816    pub update: RoomSendQueueUpdate,
2817}
2818
2819/// An error triggered by the send queue module.
2820#[derive(Debug, thiserror::Error)]
2821pub enum RoomSendQueueError {
2822    /// The room isn't in the joined state.
2823    #[error("the room isn't in the joined state")]
2824    RoomNotJoined,
2825
2826    /// The room is missing from the client.
2827    ///
2828    /// This happens only whenever the client is shutting down.
2829    #[error("the room is now missing from the client")]
2830    RoomDisappeared,
2831
2832    /// Error coming from storage.
2833    #[error(transparent)]
2834    StorageError(#[from] RoomSendQueueStorageError),
2835
2836    /// The attachment event failed to be created.
2837    #[error("the attachment event could not be created")]
2838    FailedToCreateAttachment,
2839
2840    /// The target of an [`RoomSendQueue::edit_with_attachment`] can't be
2841    /// edited with a new attachment.
2842    #[error(transparent)]
2843    Edit(#[from] crate::room::edit::EditError),
2844
2845    /// The gallery contains no items.
2846    #[cfg(feature = "unstable-msc4274")]
2847    #[error("the gallery contains no items")]
2848    EmptyGallery,
2849
2850    /// The gallery event failed to be created.
2851    #[cfg(feature = "unstable-msc4274")]
2852    #[error("the gallery event could not be created")]
2853    FailedToCreateGallery,
2854}
2855
2856/// An error triggered by the send queue storage.
2857#[derive(Debug, thiserror::Error)]
2858pub enum RoomSendQueueStorageError {
2859    /// Error caused by the state store.
2860    #[error(transparent)]
2861    StateStoreError(#[from] StoreError),
2862
2863    /// Error caused by the event cache store.
2864    #[error(transparent)]
2865    EventCacheStoreError(#[from] EventCacheStoreError),
2866
2867    /// Error caused by the event cache store.
2868    #[error(transparent)]
2869    MediaStoreError(#[from] MediaStoreError),
2870
2871    /// Error caused when attempting to get a handle on the event cache store.
2872    #[error(transparent)]
2873    LockError(#[from] CrossProcessLockError),
2874
2875    /// Error caused when (de)serializing into/from json.
2876    #[error(transparent)]
2877    JsonSerialization(#[from] serde_json::Error),
2878
2879    /// A parent key was expected to be of a certain type, and it was another
2880    /// type instead.
2881    #[error("a dependent event had an invalid parent key type")]
2882    InvalidParentKey,
2883
2884    /// The client is shutting down.
2885    #[error("The client is shutting down.")]
2886    ClientShuttingDown,
2887
2888    /// An operation not implemented on a send handle.
2889    #[error("This operation is not implemented for media uploads")]
2890    OperationNotImplementedYet,
2891
2892    /// Trying to edit a media caption for something that's not a media.
2893    #[error("Can't edit a media caption when the underlying event isn't a media")]
2894    InvalidMediaCaptionEdit,
2895}
2896
2897/// Extra transaction IDs useful during an upload.
2898#[derive(Clone, Debug)]
2899struct MediaHandles {
2900    /// Transaction id used when uploading the thumbnail.
2901    ///
2902    /// Optional because a media can be uploaded without a thumbnail.
2903    upload_thumbnail_txn: Option<OwnedTransactionId>,
2904
2905    /// Transaction id used when uploading the media itself.
2906    upload_file_txn: OwnedTransactionId,
2907}
2908
2909/// Future returned by [`RoomSendQueue::send`].
2910#[allow(missing_debug_implementations)]
2911pub struct SendEvent<'a> {
2912    queue: &'a RoomSendQueue,
2913    content: AnyMessageLikeEventContent,
2914    extra_content: Option<serde_json::Map<String, serde_json::Value>>,
2915    #[cfg(feature = "unstable-msc4354")]
2916    sticky_duration: Option<StickyDurationMs>,
2917}
2918
2919impl<'a> SendEvent<'a> {
2920    /// Merge additional top-level fields into the outgoing event's content.
2921    ///
2922    /// The event's own fields take precedence on conflicts.
2923    pub fn with_extra_content(
2924        mut self,
2925        extra_content: serde_json::Map<String, serde_json::Value>,
2926    ) -> Self {
2927        self.extra_content = Some(extra_content);
2928        self
2929    }
2930
2931    /// Send the event as a sticky event for `duration`, clamped to one hour.
2932    ///
2933    /// Note that if the homeserver doesn't support sticky events, it will
2934    /// ignore the duration and send the event unsticky. Server support can
2935    /// be checked with [`Client::supports_sticky_events`].
2936    #[cfg(feature = "unstable-msc4354")]
2937    pub fn with_sticky_duration(mut self, duration: Duration) -> Self {
2938        self.sticky_duration = Some(sticky_duration_ms(duration));
2939        self
2940    }
2941}
2942
2943impl<'a> IntoFuture for SendEvent<'a> {
2944    type Output = Result<SendHandle, RoomSendQueueError>;
2945    boxed_into_future!(extra_bounds: 'a);
2946
2947    fn into_future(self) -> Self::IntoFuture {
2948        Box::pin(async move {
2949            let serialized = upload::merge_extra_content(
2950                SerializableEventContent::new(&self.content)
2951                    .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2952                self.extra_content,
2953            )?;
2954            self.queue
2955                .send_serialized(
2956                    serialized,
2957                    #[cfg(feature = "unstable-msc4354")]
2958                    self.sticky_duration,
2959                )
2960                .await
2961        })
2962    }
2963}
2964
2965/// Future returned by [`RoomSendQueue::send_raw`].
2966#[allow(missing_debug_implementations)]
2967pub struct SendRawEvent<'a> {
2968    queue: &'a RoomSendQueue,
2969    content: SerializableEventContent,
2970    #[cfg(feature = "unstable-msc4354")]
2971    sticky_duration: Option<StickyDurationMs>,
2972}
2973
2974impl SendRawEvent<'_> {
2975    /// Send the event as a sticky event for `duration`, clamped to one hour.
2976    ///
2977    /// Note that if the homeserver doesn't support sticky events, it will
2978    /// ignore the duration and send the event unsticky. Server support can
2979    /// be checked with [`Client::supports_sticky_events`].
2980    #[cfg(feature = "unstable-msc4354")]
2981    pub fn with_sticky_duration(mut self, duration: Duration) -> Self {
2982        self.sticky_duration = Some(sticky_duration_ms(duration));
2983        self
2984    }
2985}
2986
2987impl<'a> IntoFuture for SendRawEvent<'a> {
2988    type Output = Result<SendHandle, RoomSendQueueError>;
2989    boxed_into_future!(extra_bounds: 'a);
2990
2991    fn into_future(self) -> Self::IntoFuture {
2992        Box::pin(async move {
2993            self.queue
2994                .send_serialized(
2995                    self.content,
2996                    #[cfg(feature = "unstable-msc4354")]
2997                    self.sticky_duration,
2998                )
2999                .await
3000        })
3001    }
3002}
3003
3004/// A handle to manipulate an event that was scheduled to be sent to a room.
3005///
3006/// The event may be a room message, a media upload, a redaction or a reaction;
3007/// [`Self::edit`] and [`Self::react`] only apply to the first two, and return
3008/// `false`/`None` for the others.
3009#[derive(Clone, Debug)]
3010pub struct SendHandle {
3011    /// Link to the send queue used to send this request.
3012    room: RoomSendQueue,
3013
3014    /// Transaction id used for the sent request.
3015    ///
3016    /// If this is a media upload, this is the "main" transaction id, i.e. the
3017    /// one used to send the event, and that will be seen by observers.
3018    transaction_id: OwnedTransactionId,
3019
3020    /// Additional handles for a media upload.
3021    media_handles: Vec<MediaHandles>,
3022
3023    /// The time at which the event to be sent has been created.
3024    pub created_at: MilliSecondsSinceUnixEpoch,
3025}
3026
3027impl SendHandle {
3028    /// Creates a new [`SendHandle`].
3029    #[cfg(test)]
3030    pub(crate) fn new(
3031        room: RoomSendQueue,
3032        transaction_id: OwnedTransactionId,
3033        created_at: MilliSecondsSinceUnixEpoch,
3034    ) -> Self {
3035        Self { room, transaction_id, media_handles: vec![], created_at }
3036    }
3037
3038    /// Returns the [`TransactionId`] used for sending the associated event.
3039    pub fn transaction_id(&self) -> &TransactionId {
3040        &self.transaction_id
3041    }
3042
3043    fn nyi_for_uploads(&self) -> Result<(), RoomSendQueueStorageError> {
3044        if !self.media_handles.is_empty() {
3045            Err(RoomSendQueueStorageError::OperationNotImplementedYet)
3046        } else {
3047            Ok(())
3048        }
3049    }
3050
3051    /// Aborts the sending of the event, if it wasn't sent yet.
3052    ///
3053    /// Returns true if the sending could be aborted, false if not (i.e. the
3054    /// event had already been sent).
3055    pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
3056        self.abort_with_reason(None).await
3057    }
3058
3059    /// Aborts the sending of the event, if it wasn't sent yet, with an optional
3060    /// reason.
3061    ///
3062    /// If the event was being sent when the abort was requested and the send
3063    /// succeeds, the event is redacted server-side; the given reason is applied
3064    /// to that redaction. It is unused in every other case (the local echo is
3065    /// simply dropped).
3066    ///
3067    /// Returns true if the sending could be aborted, false if not (i.e. the
3068    /// event had already been sent).
3069    #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3070    pub async fn abort_with_reason(
3071        &self,
3072        reason: Option<String>,
3073    ) -> Result<bool, RoomSendQueueStorageError> {
3074        trace!("received an abort request");
3075
3076        let queue = &self.room.inner.queue;
3077
3078        for handles in &self.media_handles {
3079            if queue.abort_upload(&self.transaction_id, handles).await? {
3080                // Wake up the queue, in case it was blocked on this request
3081                // being wedged.
3082                self.room.inner.notifier.notify_one();
3083
3084                // Propagate a cancelled update.
3085                self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3086                    transaction_id: self.transaction_id.clone(),
3087                });
3088
3089                return Ok(true);
3090            }
3091
3092            // If it failed, it means the sending of the event is not a
3093            // dependent request anymore. Fall back to the regular code path
3094            // below, that handles aborting sending of an event.
3095        }
3096
3097        // A reaction is queued as a dependent request of the event it applies to, so
3098        // it has no entry in the main queue as long as that event hasn't been sent.
3099        let aborted =
3100            queue.remove_dependent_send_queue_request(&self.transaction_id.clone().into()).await?
3101                || queue.cancel_event(&self.transaction_id, reason).await?;
3102
3103        if aborted {
3104            trace!("successful abort");
3105
3106            // Wake up the queue, in case it was blocked on this request being
3107            // wedged.
3108            self.room.inner.notifier.notify_one();
3109
3110            // Propagate a cancelled update too.
3111            self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3112                transaction_id: self.transaction_id.clone(),
3113            });
3114
3115            Ok(true)
3116        } else {
3117            debug!("local echo didn't exist anymore, can't abort");
3118            Ok(false)
3119        }
3120    }
3121
3122    /// Edits the content of a local echo with a raw event content.
3123    ///
3124    /// Returns true if the event to be sent was replaced, false if not (i.e.
3125    /// the event had already been sent).
3126    ///
3127    /// This method should not be used for editing sticky events. Sticky events
3128    /// are collected in an ephemeral map. Entries in that map need to be
3129    /// updated by sending a replacing sticky event with updated content,
3130    /// not by using an `m.replace` relation. Nevertheless, this method
3131    /// applies edits of an _unsent_ sticky event on the queued event
3132    /// directly because it is safe to do so. If, however, the event has
3133    /// already been sent, the edit will be sent as an unsticky event.
3134    #[instrument(skip(self, new_content), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3135    pub async fn edit_raw(
3136        &self,
3137        new_content: Raw<AnyMessageLikeEventContent>,
3138        event_type: String,
3139    ) -> Result<bool, RoomSendQueueStorageError> {
3140        trace!("received an edit request");
3141        self.nyi_for_uploads()?;
3142
3143        let serializable = SerializableEventContent::from_raw(new_content, event_type);
3144
3145        if self.room.inner.queue.replace_event(&self.transaction_id, serializable.clone()).await? {
3146            trace!("successful edit");
3147
3148            // Wake up the queue, in case the room was asleep before the edit.
3149            self.room.inner.notifier.notify_one();
3150
3151            // Propagate a replaced update too.
3152            self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
3153                transaction_id: self.transaction_id.clone(),
3154                new_content: serializable,
3155            });
3156
3157            Ok(true)
3158        } else {
3159            debug!("local echo doesn't exist anymore, can't edit");
3160            Ok(false)
3161        }
3162    }
3163
3164    /// Edits the content of a local echo with an event content.
3165    ///
3166    /// Returns true if the event to be sent was replaced, false if not (i.e.
3167    /// the event had already been sent).
3168    ///
3169    /// This method should not be used for editing sticky events. Sticky events
3170    /// are collected in an ephemeral map. Entries in that map need to be
3171    /// updated by sending a replacing sticky event with updated content,
3172    /// not by using an `m.replace` relation. Nevertheless, this method
3173    /// applies edits of an _unsent_ sticky event on the queued event
3174    /// directly because it is safe to do so. If, however, the event has
3175    /// already been sent, the edit will be sent as an unsticky event.
3176    pub async fn edit(
3177        &self,
3178        new_content: AnyMessageLikeEventContent,
3179    ) -> Result<bool, RoomSendQueueStorageError> {
3180        self.edit_raw(
3181            Raw::new(&new_content).map_err(RoomSendQueueStorageError::JsonSerialization)?,
3182            new_content.event_type().to_string(),
3183        )
3184        .await
3185    }
3186
3187    /// Edits the content of a local echo with a media caption.
3188    ///
3189    /// Will fail if the event to be sent, represented by this send handle,
3190    /// wasn't a media.
3191    pub async fn edit_media_caption(
3192        &self,
3193        caption: Option<String>,
3194        formatted_caption: Option<FormattedBody>,
3195        mentions: Option<Mentions>,
3196    ) -> Result<bool, RoomSendQueueStorageError> {
3197        if let Some(new_content) = self
3198            .room
3199            .inner
3200            .queue
3201            .edit_media_caption(&self.transaction_id, caption, formatted_caption, mentions)
3202            .await?
3203        {
3204            trace!("successful edit of media caption");
3205
3206            // Wake up the queue, in case the room was asleep before the edit.
3207            self.room.inner.notifier.notify_one();
3208
3209            let new_content = SerializableEventContent::new(&new_content)
3210                .map_err(RoomSendQueueStorageError::JsonSerialization)?;
3211
3212            // Propagate a replaced update too.
3213            self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
3214                transaction_id: self.transaction_id.clone(),
3215                new_content,
3216            });
3217
3218            Ok(true)
3219        } else {
3220            debug!("local echo doesn't exist anymore, can't edit media caption");
3221            Ok(false)
3222        }
3223    }
3224
3225    /// Unwedge the local echo associated to this [`SendHandle`] and try to
3226    /// resend it.
3227    pub async fn unwedge(&self) -> Result<(), RoomSendQueueError> {
3228        let room = &self.room.inner;
3229
3230        // If we have media handles, try to unwedge them.
3231        //
3232        // It's fine to always do it to _all_ the transaction IDs at once,
3233        // because only one of the three requests will be active at the same
3234        // time, i.e. only one entry will be updated in the store. The other two
3235        // are either done, or dependent requests.
3236
3237        for handles in &self.media_handles {
3238            room.queue
3239                .mark_as_unwedged(&handles.upload_file_txn)
3240                .await
3241                .map_err(RoomSendQueueError::StorageError)?;
3242
3243            if let Some(txn) = &handles.upload_thumbnail_txn {
3244                room.queue.mark_as_unwedged(txn).await.map_err(RoomSendQueueError::StorageError)?;
3245            }
3246        }
3247
3248        self.room.unwedge_request(&self.transaction_id).await
3249    }
3250
3251    /// Send a reaction to the event as soon as it's sent.
3252    ///
3253    /// If returning `Ok(None)`; this means the reaction couldn't be sent
3254    /// because the event is already a remote one.
3255    #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3256    pub async fn react(
3257        &self,
3258        key: String,
3259    ) -> Result<Option<SendHandle>, RoomSendQueueStorageError> {
3260        trace!("received an intent to react");
3261
3262        let created_at = MilliSecondsSinceUnixEpoch::now();
3263        if let Some(reaction_txn_id) =
3264            self.room.inner.queue.react(&self.transaction_id, key.clone(), created_at).await?
3265        {
3266            trace!("successfully queued react");
3267
3268            // Wake up the queue, in case the room was asleep before the
3269            // sending.
3270            self.room.inner.notifier.notify_one();
3271
3272            // Propagate a new local event.
3273            let send_handle = SendHandle {
3274                room: self.room.clone(),
3275                transaction_id: reaction_txn_id.clone().into(),
3276                media_handles: vec![],
3277                created_at,
3278            };
3279
3280            self.room.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
3281                // Note: we do want to use the `txn_id` we're going to use for
3282                // the reaction, not the one for the event we're reacting to.
3283                transaction_id: reaction_txn_id.into(),
3284                content: LocalEchoContent::React {
3285                    key,
3286                    send_handle: send_handle.clone(),
3287                    applies_to: self.transaction_id.clone(),
3288                },
3289            }));
3290
3291            Ok(Some(send_handle))
3292        } else {
3293            debug!("local echo doesn't exist anymore, can't react");
3294            Ok(None)
3295        }
3296    }
3297}
3298
3299/// Whether a queued request is an event the user composed, as opposed to a
3300/// redaction or a reaction.
3301///
3302/// A reaction is queued as a dependent request at first, but graduates into a
3303/// request of its own, under the same transaction id, once the event it
3304/// applies to has been sent; so being an event is not enough to tell the two
3305/// apart.
3306fn is_own_event_request(request: &QueuedRequest) -> bool {
3307    request.as_event().is_some_and(|content| {
3308        TimelineEventType::from(content.raw().1) != TimelineEventType::Reaction
3309    })
3310}
3311
3312/// From a given source of [`DependentQueuedRequest`], return only the most
3313/// meaningful, i.e. the ones that wouldn't be overridden after applying the
3314/// others.
3315fn canonicalize_dependent_requests(
3316    dependent: &[DependentQueuedRequest],
3317) -> Vec<DependentQueuedRequest> {
3318    let mut by_txn = HashMap::<OwnedTransactionId, Vec<&DependentQueuedRequest>>::new();
3319
3320    for d in dependent {
3321        let prevs = by_txn.entry(d.parent_transaction_id.clone()).or_default();
3322
3323        if prevs.iter().any(|prev| {
3324            matches!(
3325                prev.kind,
3326                DependentQueuedRequestKind::RedactEvent
3327                    | DependentQueuedRequestKind::RedactEventWithReason { .. }
3328            )
3329        }) {
3330            // The parent event has already been flagged for redaction, don't
3331            // consider the other dependent events.
3332            continue;
3333        }
3334
3335        match &d.kind {
3336            DependentQueuedRequestKind::EditEvent { .. } => {
3337                // Replace any previous edit with this one.
3338                if let Some(prev_edit) = prevs
3339                    .iter_mut()
3340                    .find(|prev| matches!(prev.kind, DependentQueuedRequestKind::EditEvent { .. }))
3341                {
3342                    *prev_edit = d;
3343                } else {
3344                    prevs.insert(0, d);
3345                }
3346            }
3347
3348            DependentQueuedRequestKind::UploadFileOrThumbnail { .. }
3349            | DependentQueuedRequestKind::FinishUpload { .. }
3350            | DependentQueuedRequestKind::ReactEvent { .. } => {
3351                // These requests can't be canonicalized, push them as is.
3352                prevs.push(d);
3353            }
3354
3355            #[cfg(feature = "unstable-msc4274")]
3356            DependentQueuedRequestKind::FinishGallery { .. } => {
3357                // This request can't be canonicalized, push it as is.
3358                prevs.push(d);
3359            }
3360
3361            DependentQueuedRequestKind::RedactEvent
3362            | DependentQueuedRequestKind::RedactEventWithReason { .. } => {
3363                // Remove every other dependent action.
3364                prevs.clear();
3365                prevs.push(d);
3366            }
3367        }
3368    }
3369
3370    by_txn.into_values().flat_map(|entries| entries.into_iter().cloned()).collect()
3371}
3372
3373#[cfg(all(test, not(target_family = "wasm")))]
3374mod tests {
3375    use std::{assert_matches, sync::Arc, time::Duration};
3376
3377    use matrix_sdk_base::store::{
3378        ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind,
3379        SerializableEventContent,
3380    };
3381    use matrix_sdk_test::{JoinedRoomBuilder, SyncResponseBuilder, async_test};
3382    use ruma::{
3383        MilliSecondsSinceUnixEpoch, TransactionId,
3384        events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
3385        room_id,
3386    };
3387    use strass::assert_let;
3388
3389    use super::canonicalize_dependent_requests;
3390    use crate::{client::WeakClient, test_utils::logged_in_client};
3391
3392    #[test]
3393    fn test_canonicalize_dependent_events_created_at() {
3394        // Test to ensure the created_at field is being serialized and retrieved
3395        // correctly.
3396        let txn = TransactionId::new();
3397        let created_at = MilliSecondsSinceUnixEpoch::now();
3398
3399        let edit = DependentQueuedRequest {
3400            own_transaction_id: ChildTransactionId::new(),
3401            parent_transaction_id: txn.clone(),
3402            kind: DependentQueuedRequestKind::EditEvent {
3403                new_content: SerializableEventContent::new(
3404                    &RoomMessageEventContent::text_plain("edit").into(),
3405                )
3406                .unwrap(),
3407            },
3408            parent_key: None,
3409            created_at,
3410        };
3411
3412        let res = canonicalize_dependent_requests(&[edit]);
3413
3414        assert_eq!(res.len(), 1);
3415        assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3416        assert_let!(
3417            AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3418        );
3419        assert_eq!(msg.body(), "edit");
3420        assert_eq!(res[0].parent_transaction_id, txn);
3421        assert_eq!(res[0].created_at, created_at);
3422    }
3423
3424    #[async_test]
3425    async fn test_client_no_cycle_with_send_queue() {
3426        for enabled in [true, false] {
3427            let client = logged_in_client(None).await;
3428            let weak_client = WeakClient::from_client(&client);
3429
3430            {
3431                let mut sync_response_builder = SyncResponseBuilder::new();
3432
3433                let room_id = room_id!("!a:b.c");
3434
3435                // Make sure the client knows about the room.
3436                client
3437                    .base_client()
3438                    .receive_sync_response(
3439                        sync_response_builder
3440                            .add_joined_room(JoinedRoomBuilder::new(room_id))
3441                            .build_sync_response(),
3442                    )
3443                    .await
3444                    .unwrap();
3445
3446                let room = client.get_room(room_id).unwrap();
3447                let q = room.send_queue();
3448
3449                let _watcher = q.subscribe().await;
3450
3451                client.send_queue().set_enabled(enabled).await;
3452            }
3453
3454            drop(client);
3455
3456            // Give a bit of time for background tasks to die.
3457            tokio::time::sleep(Duration::from_millis(500)).await;
3458
3459            // The weak client must be the last reference to the client now.
3460            let client = weak_client.get();
3461            assert!(
3462                client.is_none(),
3463                "too many strong references to the client: {}",
3464                Arc::strong_count(&client.unwrap().inner)
3465            );
3466        }
3467    }
3468
3469    #[test]
3470    fn test_canonicalize_dependent_events_smoke_test() {
3471        // Smoke test: canonicalizing a single dependent event returns it.
3472        let txn = TransactionId::new();
3473
3474        let edit = DependentQueuedRequest {
3475            own_transaction_id: ChildTransactionId::new(),
3476            parent_transaction_id: txn.clone(),
3477            kind: DependentQueuedRequestKind::EditEvent {
3478                new_content: SerializableEventContent::new(
3479                    &RoomMessageEventContent::text_plain("edit").into(),
3480                )
3481                .unwrap(),
3482            },
3483            parent_key: None,
3484            created_at: MilliSecondsSinceUnixEpoch::now(),
3485        };
3486        let res = canonicalize_dependent_requests(&[edit]);
3487
3488        assert_eq!(res.len(), 1);
3489        assert_matches!(&res[0].kind, DependentQueuedRequestKind::EditEvent { .. });
3490        assert_eq!(res[0].parent_transaction_id, txn);
3491        assert!(res[0].parent_key.is_none());
3492    }
3493
3494    #[test]
3495    fn test_canonicalize_dependent_events_redaction_preferred() {
3496        // A redaction is preferred over any other kind of dependent event.
3497        let txn = TransactionId::new();
3498
3499        let mut inputs = Vec::with_capacity(100);
3500        let redact = DependentQueuedRequest {
3501            own_transaction_id: ChildTransactionId::new(),
3502            parent_transaction_id: txn.clone(),
3503            kind: DependentQueuedRequestKind::RedactEventWithReason { reason: None },
3504            parent_key: None,
3505            created_at: MilliSecondsSinceUnixEpoch::now(),
3506        };
3507
3508        let edit = DependentQueuedRequest {
3509            own_transaction_id: ChildTransactionId::new(),
3510            parent_transaction_id: txn.clone(),
3511            kind: DependentQueuedRequestKind::EditEvent {
3512                new_content: SerializableEventContent::new(
3513                    &RoomMessageEventContent::text_plain("edit").into(),
3514                )
3515                .unwrap(),
3516            },
3517            parent_key: None,
3518            created_at: MilliSecondsSinceUnixEpoch::now(),
3519        };
3520
3521        inputs.push({
3522            let mut edit = edit.clone();
3523            edit.own_transaction_id = ChildTransactionId::new();
3524            edit
3525        });
3526
3527        inputs.push(redact);
3528
3529        for _ in 0..98 {
3530            let mut edit = edit.clone();
3531            edit.own_transaction_id = ChildTransactionId::new();
3532            inputs.push(edit);
3533        }
3534
3535        let res = canonicalize_dependent_requests(&inputs);
3536
3537        assert_eq!(res.len(), 1);
3538        assert_matches!(&res[0].kind, DependentQueuedRequestKind::RedactEventWithReason { .. });
3539        assert_eq!(res[0].parent_transaction_id, txn);
3540    }
3541
3542    #[test]
3543    fn test_canonicalize_dependent_events_last_edit_preferred() {
3544        let parent_txn = TransactionId::new();
3545
3546        // The latest edit of a list is always preferred.
3547        let inputs = (0..10)
3548            .map(|i| DependentQueuedRequest {
3549                own_transaction_id: ChildTransactionId::new(),
3550                parent_transaction_id: parent_txn.clone(),
3551                kind: DependentQueuedRequestKind::EditEvent {
3552                    new_content: SerializableEventContent::new(
3553                        &RoomMessageEventContent::text_plain(format!("edit{i}")).into(),
3554                    )
3555                    .unwrap(),
3556                },
3557                parent_key: None,
3558                created_at: MilliSecondsSinceUnixEpoch::now(),
3559            })
3560            .collect::<Vec<_>>();
3561
3562        let txn = inputs[9].parent_transaction_id.clone();
3563
3564        let res = canonicalize_dependent_requests(&inputs);
3565
3566        assert_eq!(res.len(), 1);
3567        assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3568        assert_let!(
3569            AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3570        );
3571        assert_eq!(msg.body(), "edit9");
3572        assert_eq!(res[0].parent_transaction_id, txn);
3573    }
3574
3575    #[test]
3576    fn test_canonicalize_multiple_local_echoes() {
3577        let txn1 = TransactionId::new();
3578        let txn2 = TransactionId::new();
3579
3580        let child1 = ChildTransactionId::new();
3581        let child2 = ChildTransactionId::new();
3582
3583        let inputs = vec![
3584            // This one pertains to txn1.
3585            DependentQueuedRequest {
3586                own_transaction_id: child1.clone(),
3587                kind: DependentQueuedRequestKind::RedactEventWithReason { reason: None },
3588                parent_transaction_id: txn1.clone(),
3589                parent_key: None,
3590                created_at: MilliSecondsSinceUnixEpoch::now(),
3591            },
3592            // This one pertains to txn2.
3593            DependentQueuedRequest {
3594                own_transaction_id: child2,
3595                kind: DependentQueuedRequestKind::EditEvent {
3596                    new_content: SerializableEventContent::new(
3597                        &RoomMessageEventContent::text_plain("edit").into(),
3598                    )
3599                    .unwrap(),
3600                },
3601                parent_transaction_id: txn2.clone(),
3602                parent_key: None,
3603                created_at: MilliSecondsSinceUnixEpoch::now(),
3604            },
3605        ];
3606
3607        let res = canonicalize_dependent_requests(&inputs);
3608
3609        // The canonicalization shouldn't depend per event id.
3610        assert_eq!(res.len(), 2);
3611
3612        for dependent in res {
3613            if dependent.own_transaction_id == child1 {
3614                assert_eq!(dependent.parent_transaction_id, txn1);
3615                assert_matches!(
3616                    dependent.kind,
3617                    DependentQueuedRequestKind::RedactEventWithReason { .. }
3618                );
3619            } else {
3620                assert_eq!(dependent.parent_transaction_id, txn2);
3621                assert_matches!(dependent.kind, DependentQueuedRequestKind::EditEvent { .. });
3622            }
3623        }
3624    }
3625
3626    #[test]
3627    fn test_canonicalize_reactions_after_edits() {
3628        // Sending reactions should happen after edits to a given event.
3629        let txn = TransactionId::new();
3630
3631        let react_id = ChildTransactionId::new();
3632        let react = DependentQueuedRequest {
3633            own_transaction_id: react_id.clone(),
3634            kind: DependentQueuedRequestKind::ReactEvent { key: "🧠".to_owned() },
3635            parent_transaction_id: txn.clone(),
3636            parent_key: None,
3637            created_at: MilliSecondsSinceUnixEpoch::now(),
3638        };
3639
3640        let edit_id = ChildTransactionId::new();
3641        let edit = DependentQueuedRequest {
3642            own_transaction_id: edit_id.clone(),
3643            kind: DependentQueuedRequestKind::EditEvent {
3644                new_content: SerializableEventContent::new(
3645                    &RoomMessageEventContent::text_plain("edit").into(),
3646                )
3647                .unwrap(),
3648            },
3649            parent_transaction_id: txn,
3650            parent_key: None,
3651            created_at: MilliSecondsSinceUnixEpoch::now(),
3652        };
3653
3654        let res = canonicalize_dependent_requests(&[react, edit]);
3655
3656        assert_eq!(res.len(), 2);
3657        assert_eq!(res[0].own_transaction_id, edit_id);
3658        assert_eq!(res[1].own_transaction_id, react_id);
3659    }
3660}