1use std::{
138 collections::{BTreeMap, HashMap},
139 future::IntoFuture,
140 ops::Not,
141 str::FromStr as _,
142 sync::{
143 Arc, OnceLock, 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 api::error::ErrorKind,
175 events::{
176 AnyMessageLikeEventContent, Mentions, MessageLikeEventContent as _, TimelineEventType,
177 reaction::ReactionEventContent,
178 relation::Annotation,
179 room::{
180 MediaSource,
181 message::{FormattedBody, RoomMessageEventContent},
182 },
183 },
184 serde::Raw,
185};
186use tokio::sync::{Mutex, Notify, OwnedMutexGuard, broadcast, oneshot};
187use tracing::{debug, error, info, instrument, trace, warn};
188
189#[cfg(feature = "unstable-msc4354")]
190use crate::utils::sticky_duration_ms;
191use crate::{
192 Client, Media, Room, SessionChange, TransmissionProgress,
193 client::WeakClient,
194 config::RequestConfig,
195 error::RetryKind,
196 room::{WeakRoom, edit::EditedContent},
197};
198
199mod progress;
200mod upload;
201
202pub use progress::AbstractProgress;
203
204const STORE_ERROR_BACKOFF: Duration = Duration::from_millis(250);
207
208pub struct SendQueue {
210 client: Client,
211}
212
213#[cfg(not(tarpaulin_include))]
214impl std::fmt::Debug for SendQueue {
215 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216 f.debug_struct("SendQueue").finish_non_exhaustive()
217 }
218}
219
220impl SendQueue {
221 pub(super) fn new(client: Client) -> Self {
222 Self { client }
223 }
224
225 pub async fn respawn_tasks_for_rooms_with_unsent_requests(&self) {
228 if !self.is_enabled() {
229 return;
230 }
231
232 let room_ids =
233 self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
234 |err| {
235 warn!("error when loading rooms with unsent requests: {err}");
236 Vec::new()
237 },
238 );
239
240 for room_id in room_ids {
243 if let Some(room) = self.client.get_room(&room_id) {
244 let _ = self.for_room(room);
245 }
246 }
247 }
248
249 async fn session_change_task(client: WeakClient) {
252 let Some(strong_client) = client.get() else { return };
253 let mut session_changes = strong_client.subscribe_to_session_changes();
254 drop(strong_client);
256
257 loop {
258 match session_changes.recv().await {
259 Ok(SessionChange::TokensRefreshed) => {
260 let Some(client) = client.get() else { break };
261 client.send_queue().unwedge_requests_with_expired_token().await;
262 }
263
264 Ok(SessionChange::UnknownToken(_)) => {}
265
266 Err(broadcast::error::RecvError::Lagged(num)) => {
269 warn!(num, "missed some session changes, unwedging just in case");
270 let Some(client) = client.get() else { break };
271 client.send_queue().unwedge_requests_with_expired_token().await;
272 }
273
274 Err(broadcast::error::RecvError::Closed) => break,
275 }
276 }
277 }
278
279 async fn unwedge_requests_with_expired_token(&self) {
282 let room_ids = match self.client.state_store().load_rooms_with_unsent_requests().await {
283 Ok(room_ids) => room_ids,
284 Err(err) => {
285 warn!("error when loading rooms with unsent requests: {err}");
286 return;
287 }
288 };
289
290 for room_id in room_ids {
291 let Some(room) = self.client.get_room(&room_id) else { continue };
292 self.for_room(room).unwedge_requests_with_expired_token().await;
293 }
294 }
295
296 #[inline(always)]
298 fn data(&self) -> &SendQueueData {
299 &self.client.inner.send_queue_data
300 }
301
302 pub(crate) fn for_room(&self, room: Room) -> RoomSendQueue {
305 let data = self.data();
306
307 data.session_change_task.get_or_init(|| {
308 let client = self.client.downgrade();
309 self.client
310 .task_monitor()
311 .spawn_infinite_task("send_queue_session_change", Self::session_change_task(client))
312 .abort_on_drop()
313 });
314
315 let mut map = data.rooms.write().unwrap();
316
317 let room_id = room.room_id();
318 if let Some(room_q) = map.get(room_id).cloned() {
319 return room_q;
320 }
321
322 let owned_room_id = room_id.to_owned();
323 let room_q = RoomSendQueue::new(
324 self.is_enabled(),
325 data.global_update_sender.clone(),
326 data.error_sender.clone(),
327 data.is_dropping.clone(),
328 &self.client,
329 owned_room_id.clone(),
330 data.report_media_upload_progress.clone(),
331 );
332
333 map.insert(owned_room_id, room_q.clone());
334
335 room_q
336 }
337
338 pub async fn set_enabled(&self, enabled: bool) {
348 debug!(?enabled, "setting global send queue enablement");
349
350 self.data().globally_enabled.store(enabled, Ordering::SeqCst);
351
352 for room in self.data().rooms.read().unwrap().values() {
354 room.set_enabled(enabled);
355 }
356
357 self.respawn_tasks_for_rooms_with_unsent_requests().await;
360 }
361
362 pub fn is_enabled(&self) -> bool {
364 self.data().globally_enabled.load(Ordering::SeqCst)
365 }
366
367 pub fn enable_upload_progress(&self, enabled: bool) {
369 self.data().report_media_upload_progress.store(enabled, Ordering::SeqCst);
370 }
371
372 pub fn subscribe(&self) -> broadcast::Receiver<SendQueueUpdate> {
377 self.data().global_update_sender.subscribe()
378 }
379
380 pub async fn local_echoes(
382 &self,
383 ) -> Result<BTreeMap<OwnedRoomId, Vec<LocalEcho>>, RoomSendQueueError> {
384 let room_ids =
385 self.client.state_store().load_rooms_with_unsent_requests().await.unwrap_or_else(
386 |err| {
387 warn!("error when loading rooms with unsent requests: {err}");
388 Vec::new()
389 },
390 );
391
392 let mut local_echoes: BTreeMap<OwnedRoomId, Vec<LocalEcho>> = BTreeMap::new();
393
394 for room_id in room_ids {
395 if let Some(room) = self.client.get_room(&room_id) {
396 let queue = self.for_room(room);
397 local_echoes
398 .insert(room_id.to_owned(), queue.inner.queue.local_echoes(&queue).await?);
399 }
400 }
401
402 Ok(local_echoes)
403 }
404
405 pub fn subscribe_errors(&self) -> broadcast::Receiver<SendQueueRoomError> {
408 self.data().error_sender.subscribe()
409 }
410}
411
412#[derive(Clone, Debug)]
415struct QueueThumbnailInfo {
416 finish_upload_thumbnail_info: FinishUploadThumbnailInfo,
418
419 media_request_parameters: MediaRequestParameters,
421
422 content_type: Mime,
424
425 file_size: usize,
427}
428
429#[derive(Clone, Debug)]
431pub struct SendQueueRoomError {
432 pub room_id: OwnedRoomId,
434
435 pub error: Arc<crate::Error>,
437
438 pub is_recoverable: bool,
443}
444
445impl Client {
446 pub fn send_queue(&self) -> SendQueue {
449 SendQueue::new(self.clone())
450 }
451}
452
453pub(super) struct SendQueueData {
454 rooms: RwLock<BTreeMap<OwnedRoomId, RoomSendQueue>>,
456
457 globally_enabled: AtomicBool,
462
463 global_update_sender: broadcast::Sender<SendQueueUpdate>,
467
468 error_sender: broadcast::Sender<SendQueueRoomError>,
470
471 is_dropping: Arc<AtomicBool>,
473
474 report_media_upload_progress: Arc<AtomicBool>,
476
477 session_change_task: OnceLock<BackgroundTaskHandle>,
479}
480
481impl SendQueueData {
482 pub fn new(globally_enabled: bool) -> Self {
484 let (global_update_sender, _) = broadcast::channel(32);
485 let (error_sender, _) = broadcast::channel(32);
486
487 Self {
488 rooms: Default::default(),
489 globally_enabled: AtomicBool::new(globally_enabled),
490 global_update_sender,
491 error_sender,
492 is_dropping: Arc::new(false.into()),
493 report_media_upload_progress: Arc::new(false.into()),
494 session_change_task: OnceLock::new(),
495 }
496 }
497}
498
499impl Drop for SendQueueData {
500 fn drop(&mut self) {
501 debug!("globally dropping the send queue");
504 self.is_dropping.store(true, Ordering::SeqCst);
505
506 let rooms = self.rooms.read().unwrap();
507 for room in rooms.values() {
508 room.inner.notifier.notify_one();
509 }
510 }
511}
512
513impl Room {
514 pub fn send_queue(&self) -> RoomSendQueue {
516 self.client.send_queue().for_room(self.clone())
517 }
518}
519
520#[derive(Clone)]
524pub struct RoomSendQueue {
525 inner: Arc<RoomSendQueueInner>,
526}
527
528#[cfg(not(tarpaulin_include))]
529impl std::fmt::Debug for RoomSendQueue {
530 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
531 f.debug_struct("RoomSendQueue").finish_non_exhaustive()
532 }
533}
534
535impl RoomSendQueue {
536 fn new(
537 globally_enabled: bool,
538 global_update_sender: broadcast::Sender<SendQueueUpdate>,
539 global_error_sender: broadcast::Sender<SendQueueRoomError>,
540 is_dropping: Arc<AtomicBool>,
541 client: &Client,
542 room_id: OwnedRoomId,
543 report_media_upload_progress: Arc<AtomicBool>,
544 ) -> Self {
545 let (update_sender, _) = broadcast::channel(32);
546
547 let queue = QueueStorage::new(client.downgrade(), room_id.clone());
548 let notifier = Arc::new(Notify::new());
549
550 let weak_room = WeakRoom::new(client.downgrade(), room_id);
551 let locally_enabled = Arc::new(AtomicBool::new(globally_enabled));
552
553 let task = client.task_monitor().spawn_infinite_task(
554 "send_queue",
555 Self::sending_task(
556 weak_room.clone(),
557 queue.clone(),
558 notifier.clone(),
559 global_update_sender.clone(),
560 update_sender.clone(),
561 locally_enabled.clone(),
562 global_error_sender,
563 is_dropping,
564 report_media_upload_progress,
565 ),
566 );
567
568 Self {
569 inner: Arc::new(RoomSendQueueInner {
570 room: weak_room,
571 global_update_sender,
572 update_sender,
573 _task: task,
574 queue,
575 notifier,
576 locally_enabled,
577 }),
578 }
579 }
580
581 pub fn send_raw(
596 &self,
597 content: Raw<AnyMessageLikeEventContent>,
598 event_type: String,
599 ) -> SendRawEvent<'_> {
600 SendRawEvent {
601 queue: self,
602 content: SerializableEventContent::from_raw(content, event_type),
603 #[cfg(feature = "unstable-msc4354")]
604 sticky_duration: None,
605 }
606 }
607
608 async fn send_serialized(
610 &self,
611 content: SerializableEventContent,
612 #[cfg(feature = "unstable-msc4354")] sticky_duration: Option<StickyDurationMs>,
613 ) -> Result<SendHandle, RoomSendQueueError> {
614 let Some(room) = self.inner.room.get() else {
615 return Err(RoomSendQueueError::RoomDisappeared);
616 };
617 if room.state() != RoomState::Joined {
618 return Err(RoomSendQueueError::RoomNotJoined);
619 }
620
621 let request = QueuedRequestKind::Event {
622 content: content.clone(),
623 #[cfg(feature = "unstable-msc4354")]
624 sticky_duration,
625 };
626
627 let created_at = MilliSecondsSinceUnixEpoch::now();
628 let transaction_id = self.inner.queue.push(request, created_at).await?;
629 trace!(%transaction_id, "manager sends a raw event to the background task");
630
631 self.inner.notifier.notify_one();
632
633 let send_handle = SendHandle {
634 room: self.clone(),
635 transaction_id: transaction_id.clone(),
636 media_handles: vec![],
637 created_at,
638 };
639
640 self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
641 transaction_id,
642 content: LocalEchoContent::Event {
643 serialized_event: content,
644 send_handle: send_handle.clone(),
645 send_error: None,
646 },
647 }));
648
649 Ok(send_handle)
650 }
651
652 pub fn send(&self, content: AnyMessageLikeEventContent) -> SendEvent<'_> {
667 SendEvent {
668 queue: self,
669 content,
670 extra_content: None,
671 #[cfg(feature = "unstable-msc4354")]
672 sticky_duration: None,
673 }
674 }
675
676 pub async fn redact(
691 &self,
692 redacts: OwnedEventId,
693 reason: Option<&str>,
694 ) -> Result<SendHandle, RoomSendQueueError> {
695 let Some(room) = self.inner.room.get() else {
696 return Err(RoomSendQueueError::RoomDisappeared);
697 };
698 if room.state() != RoomState::Joined {
699 return Err(RoomSendQueueError::RoomNotJoined);
700 }
701
702 let request = QueuedRequestKind::Redaction {
703 redacts: redacts.clone(),
704 reason: reason.map(str::to_owned),
705 };
706
707 let created_at = MilliSecondsSinceUnixEpoch::now();
708 let transaction_id = self.inner.queue.push(request, created_at).await?;
709 trace!(%transaction_id, "manager sends a redaction event to the background task");
710
711 self.inner.notifier.notify_one();
712
713 let send_handle = SendHandle {
714 room: self.clone(),
715 transaction_id: transaction_id.clone(),
716 media_handles: vec![],
717 created_at,
718 };
719
720 self.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
721 transaction_id,
722 content: LocalEchoContent::Redaction {
723 redacts,
724 reason: reason.map(str::to_owned),
725 send_handle: send_handle.clone(),
726 send_error: None,
727 },
728 }));
729
730 Ok(send_handle)
731 }
732
733 pub async fn subscribe(
739 &self,
740 ) -> Result<(Vec<LocalEcho>, broadcast::Receiver<RoomSendQueueUpdate>), RoomSendQueueError>
741 {
742 let local_echoes = self.inner.queue.local_echoes(self).await?;
743
744 Ok((local_echoes, self.inner.update_sender.subscribe()))
745 }
746
747 #[allow(clippy::too_many_arguments)]
753 #[instrument(skip_all, fields(room_id = %room.room_id()))]
754 async fn sending_task(
755 room: WeakRoom,
756 queue: QueueStorage,
757 notifier: Arc<Notify>,
758 global_update_sender: broadcast::Sender<SendQueueUpdate>,
759 update_sender: broadcast::Sender<RoomSendQueueUpdate>,
760 locally_enabled: Arc<AtomicBool>,
761 global_error_sender: broadcast::Sender<SendQueueRoomError>,
762 is_dropping: Arc<AtomicBool>,
763 report_media_upload_progress: Arc<AtomicBool>,
764 ) {
765 trace!("spawned the sending task");
766
767 let room_id = room.room_id();
768
769 loop {
770 if is_dropping.load(Ordering::SeqCst) {
772 trace!("shutting down!");
773 break;
774 }
775
776 let mut new_updates = Vec::new();
779 if let Err(err) = queue.apply_dependent_requests(&mut new_updates).await {
780 warn!("errors when applying dependent requests: {err}");
781 }
782
783 for up in new_updates {
784 send_update(&global_update_sender, &update_sender, room_id, up);
785 }
786
787 if !locally_enabled.load(Ordering::SeqCst) {
788 trace!("not enabled, sleeping");
789 notifier.notified().await;
791 continue;
792 }
793
794 let (queued_request, cancel_upload_rx) = match queue.peek_next_to_send().await {
795 Ok(Some(request)) => request,
796
797 Ok(None) => {
798 trace!("queue is empty or blocked on a wedged request, sleeping");
799 notifier.notified().await;
801 continue;
802 }
803
804 Err(err) => {
805 warn!("error when loading next request to send: {err}");
806 matrix_sdk_common::sleep::sleep(STORE_ERROR_BACKOFF).await;
809 continue;
810 }
811 };
812
813 let txn_id = queued_request.transaction_id.clone();
814 trace!(txn_id = %txn_id, "received a request to send!");
815
816 let Some(room) = room.get() else {
817 if is_dropping.load(Ordering::SeqCst) {
818 break;
819 }
820 error!("the weak room couldn't be upgraded but we're not shutting down?");
821 continue;
822 };
823
824 let (related_txn_id, media_upload_progress_info, http_progress) =
830 if let QueuedRequestKind::MediaUpload {
831 cache_key,
832 thumbnail_source,
833 #[cfg(feature = "unstable-msc4274")]
834 accumulated,
835 related_to,
836 ..
837 } = &queued_request.kind
838 {
839 let (media_upload_progress_info, http_progress) =
842 if report_media_upload_progress.load(Ordering::SeqCst) {
843 let media_upload_progress_info =
844 RoomSendQueue::create_media_upload_progress_info(
845 &queued_request.transaction_id,
846 related_to,
847 cache_key,
848 thumbnail_source.as_ref(),
849 #[cfg(feature = "unstable-msc4274")]
850 accumulated,
851 &room,
852 &queue,
853 )
854 .await;
855
856 let progress = RoomSendQueue::create_media_upload_progress_observable(
857 &media_upload_progress_info,
858 related_to,
859 &update_sender,
860 );
861
862 (Some(media_upload_progress_info), Some(progress))
863 } else {
864 Default::default()
865 };
866
867 (Some(related_to.clone()), media_upload_progress_info, http_progress)
868 } else {
869 Default::default()
870 };
871
872 match Self::handle_request(&room, queued_request, cancel_upload_rx, http_progress).await
873 {
874 Ok((Some(parent_key), encryption_info)) => match queue
875 .mark_as_sent(&txn_id, parent_key.clone())
876 .await
877 {
878 Ok(()) => match parent_key {
879 SentRequestKey::Event { event_id, event, event_type } => {
880 send_update(
881 &global_update_sender,
882 &update_sender,
883 room_id,
884 RoomSendQueueUpdate::SentEvent {
885 transaction_id: txn_id,
886 event_id: event_id.clone(),
887 },
888 );
889
890 if let Some(event_cache) = room.client().event_cache()
917 && let Ok((room_event_cache, _drop_handles)) =
918 event_cache.room(room.room_id()).await
919 {
920 let timeline_event = match Raw::from_json_string(
921 format!(
923 "{{\
924 \"event_id\":\"{event_id}\",\
925 \"origin_server_ts\":{ts},\
926 \"sender\":\"{sender}\",\
927 \"type\":\"{type}\",\
928 \"content\":{content}\
929 }}",
930 event_id = event_id,
931 ts = MilliSecondsSinceUnixEpoch::now().get(),
932 sender = room.client().user_id().expect("Client must be logged-in"),
933 type = event_type,
934 content = event.into_json(),
935 ),
936 ) {
937 Ok(event) => match encryption_info {
938 #[cfg(feature = "e2e-encryption")]
939 Some(encryption_info) => {
940 use matrix_sdk_base::deserialized_responses::DecryptedRoomEvent;
941 let decrypted_event = DecryptedRoomEvent {
942 event: event.cast_unchecked(),
943 encryption_info: Arc::new(encryption_info),
944 unsigned_encryption_info: None,
945 };
946 Some(TimelineEvent::from_decrypted(
947 decrypted_event,
948 None,
949 ))
950 }
951 _ => Some(TimelineEvent::from_plaintext(event)),
952 },
953 Err(err) => {
954 error!(
955 ?err,
956 "Failed to build the (sync) event before the saving in the Event Cache"
957 );
958 None
959 }
960 };
961
962 if let Some(timeline_event) = timeline_event
966 && let Err(err) = room_event_cache
967 .insert_sent_event_from_send_queue(timeline_event)
968 .await
969 {
970 error!(
971 ?err,
972 "Failed to save the sent event in the Event Cache"
973 );
974 }
975 } else {
976 info!(
977 "Cannot insert the sent event in the Event Cache because \
978 either the room no longer exists, or the Room Event Cache cannot be retrieved"
979 );
980 }
981 }
982
983 SentRequestKey::Media(sent_media_info) => {
984 let index =
987 media_upload_progress_info.as_ref().map_or(0, |info| info.index);
988 let progress = media_upload_progress_info
989 .as_ref()
990 .map(|info| {
991 AbstractProgress { current: info.bytes, total: info.bytes }
992 + info.offsets
993 })
994 .unwrap_or(AbstractProgress { current: 1, total: 1 });
995
996 let _ = update_sender.send(RoomSendQueueUpdate::MediaUpload {
1000 related_to: related_txn_id.as_ref().unwrap_or(&txn_id).clone(),
1001 file: Some(sent_media_info.file),
1002 index,
1003 progress,
1004 });
1005 }
1006
1007 SentRequestKey::Redaction { event_id, redacts, reason } => {
1008 send_update(
1009 &global_update_sender,
1010 &update_sender,
1011 room_id,
1012 RoomSendQueueUpdate::SentEvent {
1013 transaction_id: txn_id,
1014 event_id: event_id.clone(),
1015 },
1016 );
1017
1018 if let Some(event_cache) = room.client().event_cache()
1024 && let Ok((room_event_cache, _drop_handles)) =
1025 event_cache.room(room.room_id()).await
1026 {
1027 let content_field_redacts = room.version().is_some_and(|id| {
1028 id.rules()
1029 .is_some_and(|rules| rules.redaction.content_field_redacts)
1030 });
1031 let redacts = if content_field_redacts.not() {
1032 format!("\"redacts\":\"{redacts}\",")
1033 } else {
1034 "".to_owned()
1035 };
1036 let reason = reason.map_or_else(
1037 || "".to_owned(),
1038 |r| format!("\"reason\": \"{r}\""),
1039 );
1040 let content = if content_field_redacts {
1041 format!("\"redacts\":\"{redacts}\",{reason}")
1042 } else {
1043 reason
1044 };
1045
1046 let timeline_event = match Raw::from_json_string(
1047 format!(
1049 "{{\
1050 {redacts}\
1051 \"event_id\":\"{event_id}\",\
1052 \"origin_server_ts\":{ts},\
1053 \"sender\":\"{sender}\",\
1054 \"type\":\"{type}\",\
1055 \"content\":{{{content}}}\
1056 }}",
1057 redacts = redacts,
1058 event_id = event_id,
1059 ts = MilliSecondsSinceUnixEpoch::now().get(),
1060 sender = room.client().user_id().expect("Client must be logged-in"),
1061 type = TimelineEventType::RoomRedaction,
1062 content = content
1063 ),
1064 ) {
1065 Ok(event) => Some(TimelineEvent::from_plaintext(event)),
1066 Err(err) => {
1067 error!(
1068 ?err,
1069 "Failed to build the (sync) redaction event before the saving in the Event Cache"
1070 );
1071 None
1072 }
1073 };
1074
1075 if let Some(timeline_event) = timeline_event
1079 && let Err(err) = room_event_cache
1080 .insert_sent_event_from_send_queue(timeline_event)
1081 .await
1082 {
1083 error!(
1084 ?err,
1085 "Failed to save the sent redaction event in the Event Cache"
1086 );
1087 }
1088 } else {
1089 info!(
1090 "Cannot insert the sent redaction event in the Event Cache because \
1091 either the room no longer exists, or the Room Event Cache cannot be retrieved"
1092 );
1093 }
1094 }
1095 },
1096
1097 Err(err) => {
1098 warn!("unable to mark queued request as sent: {err}");
1099 }
1100 },
1101
1102 Ok((None, _)) => {
1103 debug!("Request has been aborted while running, continuing.");
1104 }
1105
1106 Err(err)
1111 if err.client_api_error_kind() == Some(&ErrorKind::DuplicateAnnotation) =>
1112 {
1113 debug!(txn_id = %txn_id, "the server already has this reaction, dropping it");
1114
1115 queue.mark_as_not_being_sent(&txn_id).await;
1116
1117 match queue.cancel_event(&txn_id, None).await {
1118 Ok(true) => send_update(
1119 &global_update_sender,
1120 &update_sender,
1121 room_id,
1122 RoomSendQueueUpdate::CancelledLocalEvent { transaction_id: txn_id },
1123 ),
1124
1125 Ok(false) => {}
1128
1129 Err(storage_error) => {
1130 error!(
1133 txn_id = %txn_id,
1134 "couldn't drop the duplicate reaction, disabling the queue: {storage_error}"
1135 );
1136 locally_enabled.store(false, Ordering::SeqCst);
1137 }
1138 }
1139 }
1140
1141 Err(err) => {
1142 let is_recoverable = match err {
1143 crate::Error::Http(ref http_err) => {
1144 matches!(
1146 http_err.retry_kind(),
1147 RetryKind::Transient { .. } | RetryKind::NetworkFailure
1148 )
1149 }
1150
1151 crate::Error::ConcurrentRequestFailed => true,
1157
1158 _ => false,
1160 };
1161
1162 if is_recoverable {
1163 locally_enabled.store(false, Ordering::SeqCst);
1167
1168 warn!(txn_id = %txn_id, error = ?err, "Recoverable error when sending request: {err}, disabling send queue");
1169
1170 queue.mark_as_not_being_sent(&txn_id).await;
1173
1174 } else {
1180 warn!(txn_id = %txn_id, error = ?err, "Unrecoverable error when sending request: {err}");
1181
1182 if let Err(storage_error) =
1187 queue.mark_as_wedged(&txn_id, QueueWedgeError::from(&err)).await
1188 {
1189 error!(
1193 "unable to mark request as wedged, disabling the queue: {storage_error}"
1194 );
1195 locally_enabled.store(false, Ordering::SeqCst);
1196 }
1197 }
1198
1199 let error = Arc::new(err);
1200
1201 let _ = global_error_sender.send(SendQueueRoomError {
1202 room_id: room_id.to_owned(),
1203 error: error.clone(),
1204 is_recoverable,
1205 });
1206
1207 send_update(
1208 &global_update_sender,
1209 &update_sender,
1210 room_id,
1211 RoomSendQueueUpdate::SendError {
1212 transaction_id: related_txn_id.unwrap_or(txn_id),
1213 error,
1214 is_recoverable,
1215 },
1216 );
1217 }
1218 }
1219 }
1220
1221 info!("exited sending task");
1222 }
1223
1224 async fn handle_request(
1227 room: &Room,
1228 request: QueuedRequest,
1229 cancel_upload_rx: Option<oneshot::Receiver<()>>,
1230 progress: Option<SharedObservable<TransmissionProgress>>,
1231 ) -> Result<(Option<SentRequestKey>, Option<EncryptionInfo>), crate::Error> {
1232 match request.kind {
1233 QueuedRequestKind::Event {
1234 content,
1235 #[cfg(feature = "unstable-msc4354")]
1236 sticky_duration,
1237 } => {
1238 let (event, event_type) = content.into_raw();
1239
1240 let future = room
1241 .send_raw(&event_type, &event)
1242 .with_transaction_id(&request.transaction_id)
1243 .with_request_config(RequestConfig::short_retry());
1244
1245 #[cfg(feature = "unstable-msc4354")]
1246 let future = match sticky_duration {
1247 Some(duration) => {
1248 future.with_sticky_duration(Duration::from_millis(duration.get().into()))
1249 }
1250 None => future,
1251 };
1252
1253 let result = future.await?;
1254
1255 trace!(txn_id = %request.transaction_id, event_id = %result.response.event_id, "event successfully sent");
1256
1257 Ok((
1258 Some(SentRequestKey::Event {
1259 event_id: result.response.event_id,
1260 event,
1261 event_type,
1262 }),
1263 result.encryption_info,
1264 ))
1265 }
1266
1267 QueuedRequestKind::MediaUpload {
1268 content_type,
1269 cache_key,
1270 thumbnail_source,
1271 related_to: relates_to,
1272 #[cfg(feature = "unstable-msc4274")]
1273 accumulated,
1274 } => {
1275 trace!(%relates_to, "uploading media related to event");
1276
1277 let fut = async move {
1278 let data = room
1279 .client()
1280 .media_store()
1281 .lock()
1282 .await?
1283 .get_media_content(&cache_key)
1284 .await?
1285 .ok_or(crate::Error::SendQueueWedgeError(Box::new(
1286 QueueWedgeError::MissingMediaContent,
1287 )))?;
1288
1289 let mime = Mime::from_str(&content_type).map_err(|_| {
1290 crate::Error::SendQueueWedgeError(Box::new(
1291 QueueWedgeError::InvalidMimeType { mime_type: content_type.clone() },
1292 ))
1293 })?;
1294
1295 #[cfg(feature = "e2e-encryption")]
1296 let media_source = if room.latest_encryption_state().await?.is_encrypted() {
1297 trace!("upload will be encrypted (encrypted room)");
1298
1299 let mut cursor = std::io::Cursor::new(data);
1300 let mut req = room
1301 .client
1302 .upload_encrypted_file(&mut cursor)
1303 .with_request_config(RequestConfig::short_retry());
1304 if let Some(progress) = progress {
1305 req = req.with_send_progress_observable(progress);
1306 }
1307 let encrypted_file = req.await?;
1308
1309 MediaSource::Encrypted(Box::new(encrypted_file))
1310 } else {
1311 trace!("upload will be in clear text (room without encryption)");
1312
1313 let request_config = RequestConfig::short_retry()
1314 .timeout(Media::reasonable_upload_timeout(&data));
1315 let mut req =
1316 room.client().media().upload(&mime, data, Some(request_config));
1317 if let Some(progress) = progress {
1318 req = req.with_send_progress_observable(progress);
1319 }
1320 let res = req.await?;
1321
1322 MediaSource::Plain(res.content_uri)
1323 };
1324
1325 #[cfg(not(feature = "e2e-encryption"))]
1326 let media_source = {
1327 let request_config = RequestConfig::short_retry()
1328 .timeout(Media::reasonable_upload_timeout(&data));
1329 let mut req =
1330 room.client().media().upload(&mime, data, Some(request_config));
1331 if let Some(progress) = progress {
1332 req = req.with_send_progress_observable(progress);
1333 }
1334 let res = req.await?;
1335 MediaSource::Plain(res.content_uri)
1336 };
1337
1338 let uri = match &media_source {
1339 MediaSource::Plain(uri) => uri,
1340 MediaSource::Encrypted(encrypted_file) => &encrypted_file.url,
1341 };
1342 trace!(%relates_to, mxc_uri = %uri, "media successfully uploaded");
1343
1344 Ok((
1345 Some(SentRequestKey::Media(SentMediaInfo {
1346 file: media_source,
1347 thumbnail: thumbnail_source,
1348 #[cfg(feature = "unstable-msc4274")]
1349 accumulated,
1350 })),
1351 None,
1352 ))
1353 };
1354
1355 let wait_for_cancel = async move {
1356 if let Some(rx) = cancel_upload_rx {
1357 rx.await
1358 } else {
1359 std::future::pending().await
1360 }
1361 };
1362
1363 tokio::select! {
1364 biased;
1365
1366 _ = wait_for_cancel => {
1367 Ok((None, None))
1368 }
1369
1370 res = fut => {
1371 res
1372 }
1373 }
1374 }
1375
1376 QueuedRequestKind::Redaction { redacts, reason } => {
1377 let result = room
1378 .redact(&redacts, reason.as_deref(), Some(request.transaction_id.clone()))
1379 .await?;
1380
1381 trace!(txn_id = %request.transaction_id, event_id = %result.event_id, "redaction successfully sent");
1382
1383 Ok((
1384 Some(SentRequestKey::Redaction { event_id: result.event_id, redacts, reason }),
1385 None,
1386 ))
1387 }
1388 }
1389 }
1390
1391 pub fn is_enabled(&self) -> bool {
1393 self.inner.locally_enabled.load(Ordering::SeqCst)
1394 }
1395
1396 pub fn set_enabled(&self, enabled: bool) {
1398 self.inner.locally_enabled.store(enabled, Ordering::SeqCst);
1399
1400 if enabled {
1403 self.inner.notifier.notify_one();
1404 }
1405 }
1406
1407 fn send_update(&self, update: RoomSendQueueUpdate) {
1411 let _ = self.inner.update_sender.send(update.clone());
1412 let _ = self
1413 .inner
1414 .global_update_sender
1415 .send(SendQueueUpdate { room_id: self.inner.room.room_id().to_owned(), update });
1416 }
1417
1418 async fn unwedge_requests_with_expired_token(&self) {
1421 let transaction_ids = match self.inner.queue.requests_with_expired_token().await {
1422 Ok(transaction_ids) => transaction_ids,
1423 Err(err) => {
1424 warn!("error when looking for requests wedged by an expired token: {err}");
1425 return;
1426 }
1427 };
1428
1429 for transaction_id in transaction_ids {
1430 if let Err(err) = self.unwedge_request(&transaction_id).await {
1431 warn!(%transaction_id, "error when unwedging a request: {err}");
1432 }
1433 }
1434 }
1435
1436 async fn unwedge_request(
1439 &self,
1440 transaction_id: &TransactionId,
1441 ) -> Result<(), RoomSendQueueError> {
1442 self.inner
1443 .queue
1444 .mark_as_unwedged(transaction_id)
1445 .await
1446 .map_err(RoomSendQueueError::StorageError)?;
1447
1448 self.inner.notifier.notify_one();
1451
1452 self.send_update(RoomSendQueueUpdate::RetryEvent {
1453 transaction_id: transaction_id.to_owned(),
1454 });
1455
1456 Ok(())
1457 }
1458}
1459
1460fn send_update(
1461 global_update_sender: &broadcast::Sender<SendQueueUpdate>,
1462 update_sender: &broadcast::Sender<RoomSendQueueUpdate>,
1463 room_id: &RoomId,
1464 update: RoomSendQueueUpdate,
1465) {
1466 let _ = update_sender.send(update.clone());
1467 let _ = global_update_sender.send(SendQueueUpdate { room_id: room_id.to_owned(), update });
1468}
1469
1470impl From<&crate::Error> for QueueWedgeError {
1471 fn from(value: &crate::Error) -> Self {
1472 match value {
1473 #[cfg(feature = "e2e-encryption")]
1474 crate::Error::OlmError(error) => match &**error {
1475 OlmError::SessionRecipientCollectionError(error) => match error {
1476 SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(user_map) => {
1477 QueueWedgeError::InsecureDevices { user_device_map: user_map.clone() }
1478 }
1479
1480 SessionRecipientCollectionError::VerifiedUserChangedIdentity(users) => {
1481 QueueWedgeError::IdentityViolations { users: users.clone() }
1482 }
1483
1484 SessionRecipientCollectionError::CrossSigningNotSetup
1485 | SessionRecipientCollectionError::SendingFromUnverifiedDevice => {
1486 QueueWedgeError::CrossVerificationRequired
1487 }
1488 },
1489 _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1490 },
1491
1492 crate::Error::SendQueueWedgeError(error) => *error.clone(),
1494
1495 crate::Error::Http(error) if error.is_rejected_token() => {
1496 QueueWedgeError::ExpiredAccessToken
1497 }
1498
1499 _ => QueueWedgeError::GenericApiError { msg: value.to_string() },
1500 }
1501 }
1502}
1503
1504struct RoomSendQueueInner {
1505 room: WeakRoom,
1507
1508 global_update_sender: broadcast::Sender<SendQueueUpdate>,
1512
1513 update_sender: broadcast::Sender<RoomSendQueueUpdate>,
1519
1520 queue: QueueStorage,
1526
1527 notifier: Arc<Notify>,
1530
1531 locally_enabled: Arc<AtomicBool>,
1534
1535 _task: BackgroundTaskHandle,
1538}
1539
1540struct BeingSentInfo {
1542 transaction_id: OwnedTransactionId,
1544
1545 cancel_upload: Option<oneshot::Sender<()>>,
1548}
1549
1550impl BeingSentInfo {
1551 fn cancel_upload(self) -> bool {
1556 if let Some(cancel_upload) = self.cancel_upload {
1557 let _ = cancel_upload.send(());
1558 true
1559 } else {
1560 false
1561 }
1562 }
1563}
1564
1565#[derive(Clone)]
1568struct StoreLock {
1569 client: WeakClient,
1571
1572 being_sent: Arc<Mutex<Option<BeingSentInfo>>>,
1577}
1578
1579impl StoreLock {
1580 async fn lock(&self) -> StoreLockGuard {
1582 StoreLockGuard {
1583 client: self.client.clone(),
1584 being_sent: self.being_sent.clone().lock_owned().await,
1585 }
1586 }
1587}
1588
1589struct StoreLockGuard {
1591 client: WeakClient,
1593
1594 being_sent: OwnedMutexGuard<Option<BeingSentInfo>>,
1597}
1598
1599impl StoreLockGuard {
1600 fn client(&self) -> Result<Client, RoomSendQueueStorageError> {
1602 self.client.get().ok_or(RoomSendQueueStorageError::ClientShuttingDown)
1603 }
1604}
1605
1606#[derive(Clone)]
1607struct QueueStorage {
1608 store: StoreLock,
1611
1612 room_id: OwnedRoomId,
1614
1615 thumbnail_file_sizes: Arc<SyncMutex<HashMap<OwnedTransactionId, Vec<Option<usize>>>>>,
1628}
1629
1630impl QueueStorage {
1631 const LOW_PRIORITY: usize = 0;
1633
1634 const HIGH_PRIORITY: usize = 10;
1636
1637 fn new(client: WeakClient, room: OwnedRoomId) -> Self {
1639 Self {
1640 room_id: room,
1641 store: StoreLock { client, being_sent: Default::default() },
1642 thumbnail_file_sizes: Default::default(),
1643 }
1644 }
1645
1646 async fn push(
1650 &self,
1651 request: QueuedRequestKind,
1652 created_at: MilliSecondsSinceUnixEpoch,
1653 ) -> Result<OwnedTransactionId, RoomSendQueueStorageError> {
1654 let transaction_id = TransactionId::new();
1655
1656 self.store
1657 .lock()
1658 .await
1659 .client()?
1660 .state_store()
1661 .save_send_queue_request(
1662 &self.room_id,
1663 transaction_id.clone(),
1664 created_at,
1665 request,
1666 Self::LOW_PRIORITY,
1667 )
1668 .await?;
1669
1670 Ok(transaction_id)
1671 }
1672
1673 async fn peek_next_to_send(
1678 &self,
1679 ) -> Result<Option<(QueuedRequest, Option<oneshot::Receiver<()>>)>, RoomSendQueueStorageError>
1680 {
1681 let mut guard = self.store.lock().await;
1682 let queued_requests =
1683 guard.client()?.state_store().load_send_queue_requests(&self.room_id).await?;
1684
1685 if let Some(request) = queued_requests.first().filter(|queued| !queued.is_wedged()) {
1692 let (cancel_upload_tx, cancel_upload_rx) =
1693 if matches!(request.kind, QueuedRequestKind::MediaUpload { .. }) {
1694 let (tx, rx) = oneshot::channel();
1695 (Some(tx), Some(rx))
1696 } else {
1697 Default::default()
1698 };
1699
1700 let prev = guard.being_sent.replace(BeingSentInfo {
1701 transaction_id: request.transaction_id.clone(),
1702 cancel_upload: cancel_upload_tx,
1703 });
1704
1705 if let Some(prev) = prev {
1706 error!(
1707 prev_txn = ?prev.transaction_id,
1708 "a previous request was still active while picking a new one"
1709 );
1710 }
1711
1712 Ok(Some((request.clone(), cancel_upload_rx)))
1713 } else {
1714 Ok(None)
1715 }
1716 }
1717
1718 async fn mark_as_not_being_sent(&self, transaction_id: &TransactionId) {
1722 let was_being_sent = self.store.lock().await.being_sent.take();
1723
1724 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1725 if prev_txn != Some(transaction_id) {
1726 error!(prev_txn = ?prev_txn, "previous active request didn't match that we expect (after transient error)");
1727 }
1728 }
1729
1730 async fn mark_as_wedged(
1734 &self,
1735 transaction_id: &TransactionId,
1736 reason: QueueWedgeError,
1737 ) -> Result<(), RoomSendQueueStorageError> {
1738 let mut guard = self.store.lock().await;
1740 let was_being_sent = guard.being_sent.take();
1741
1742 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1743 if prev_txn != Some(transaction_id) {
1744 error!(
1745 ?prev_txn,
1746 "previous active request didn't match that we expect (after permanent error)",
1747 );
1748 }
1749
1750 Ok(guard
1751 .client()?
1752 .state_store()
1753 .update_send_queue_request_status(&self.room_id, transaction_id, Some(reason))
1754 .await?)
1755 }
1756
1757 async fn mark_as_unwedged(
1760 &self,
1761 transaction_id: &TransactionId,
1762 ) -> Result<(), RoomSendQueueStorageError> {
1763 Ok(self
1764 .store
1765 .lock()
1766 .await
1767 .client()?
1768 .state_store()
1769 .update_send_queue_request_status(&self.room_id, transaction_id, None)
1770 .await?)
1771 }
1772
1773 async fn requests_with_expired_token(
1776 &self,
1777 ) -> Result<Vec<OwnedTransactionId>, RoomSendQueueStorageError> {
1778 Ok(self
1779 .store
1780 .lock()
1781 .await
1782 .client()?
1783 .state_store()
1784 .load_send_queue_requests(&self.room_id)
1785 .await?
1786 .into_iter()
1787 .filter(|request| matches!(request.error, Some(QueueWedgeError::ExpiredAccessToken)))
1788 .map(|request| request.transaction_id)
1789 .collect())
1790 }
1791
1792 async fn mark_as_sent(
1795 &self,
1796 transaction_id: &TransactionId,
1797 parent_key: SentRequestKey,
1798 ) -> Result<(), RoomSendQueueStorageError> {
1799 let mut guard = self.store.lock().await;
1801 let was_being_sent = guard.being_sent.take();
1802
1803 let prev_txn = was_being_sent.as_ref().map(|info| info.transaction_id.as_ref());
1804 if prev_txn != Some(transaction_id) {
1805 error!(
1806 ?prev_txn,
1807 "previous active request didn't match that we expect (after successful send)",
1808 );
1809 }
1810
1811 let client = guard.client()?;
1812 let store = client.state_store();
1813
1814 store
1816 .mark_dependent_queued_requests_as_ready(&self.room_id, transaction_id, parent_key)
1817 .await?;
1818
1819 let removed = store.remove_send_queue_request(&self.room_id, transaction_id).await?;
1820
1821 if !removed {
1822 warn!(txn_id = %transaction_id, "request marked as sent was missing from storage");
1823 }
1824
1825 self.thumbnail_file_sizes.lock().remove(transaction_id);
1826
1827 Ok(())
1828 }
1829
1830 async fn cancel_event(
1837 &self,
1838 transaction_id: &TransactionId,
1839 reason: Option<String>,
1840 ) -> Result<bool, RoomSendQueueStorageError> {
1841 let guard = self.store.lock().await;
1842
1843 if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1844 == Some(transaction_id)
1845 {
1846 guard
1848 .client()?
1849 .state_store()
1850 .save_dependent_queued_request(
1851 &self.room_id,
1852 transaction_id,
1853 ChildTransactionId::new(),
1854 MilliSecondsSinceUnixEpoch::now(),
1855 DependentQueuedRequestKind::RedactEventWithReason { reason },
1856 )
1857 .await?;
1858
1859 return Ok(true);
1860 }
1861
1862 let removed = guard
1863 .client()?
1864 .state_store()
1865 .remove_send_queue_request(&self.room_id, transaction_id)
1866 .await?;
1867
1868 self.thumbnail_file_sizes.lock().remove(transaction_id);
1869
1870 Ok(removed)
1871 }
1872
1873 async fn replace_event(
1880 &self,
1881 transaction_id: &TransactionId,
1882 serializable: SerializableEventContent,
1883 ) -> Result<bool, RoomSendQueueStorageError> {
1884 let guard = self.store.lock().await;
1885 let client = guard.client()?;
1886 let store = client.state_store();
1887
1888 if !store.load_send_queue_requests(&self.room_id).await?.iter().any(|request| {
1891 request.transaction_id == transaction_id && is_own_event_request(request)
1892 }) {
1893 return Ok(false);
1894 }
1895
1896 if guard.being_sent.as_ref().map(|info| info.transaction_id.as_ref())
1897 == Some(transaction_id)
1898 {
1899 store
1901 .save_dependent_queued_request(
1902 &self.room_id,
1903 transaction_id,
1904 ChildTransactionId::new(),
1905 MilliSecondsSinceUnixEpoch::now(),
1906 DependentQueuedRequestKind::EditEvent { new_content: serializable },
1907 )
1908 .await?;
1909
1910 return Ok(true);
1911 }
1912
1913 let request = QueuedRequestKind::Event {
1914 content: serializable,
1915 #[cfg(feature = "unstable-msc4354")]
1916 sticky_duration: self.sticky_duration_of(store, transaction_id).await?,
1917 };
1918
1919 let edited =
1920 store.update_send_queue_request(&self.room_id, transaction_id, request).await?;
1921
1922 Ok(edited)
1923 }
1924
1925 #[cfg(feature = "unstable-msc4354")]
1928 async fn sticky_duration_of(
1929 &self,
1930 store: &DynStateStore,
1931 transaction_id: &TransactionId,
1932 ) -> Result<Option<StickyDurationMs>, RoomSendQueueStorageError> {
1933 let sticky_duration = store
1934 .load_send_queue_requests(&self.room_id)
1935 .await?
1936 .into_iter()
1937 .find(|request| request.transaction_id == *transaction_id)
1938 .and_then(|request| match request.kind {
1939 QueuedRequestKind::Event { sticky_duration, .. } => sticky_duration,
1940 _ => None,
1941 });
1942
1943 Ok(sticky_duration)
1944 }
1945
1946 #[allow(clippy::too_many_arguments)]
1950 async fn push_media(
1951 &self,
1952 event: RoomMessageEventContent,
1953 content_type: Mime,
1954 send_event_txn: OwnedTransactionId,
1955 created_at: MilliSecondsSinceUnixEpoch,
1956 upload_file_txn: OwnedTransactionId,
1957 file_media_request: MediaRequestParameters,
1958 thumbnail: Option<QueueThumbnailInfo>,
1959 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
1960 ) -> Result<(), RoomSendQueueStorageError> {
1961 let guard = self.store.lock().await;
1962 let client = guard.client()?;
1963 let store = client.state_store();
1964
1965 let thumbnail_file_sizes = vec![thumbnail.as_ref().map(|t| t.file_size)];
1968
1969 let thumbnail_info = self
1970 .push_thumbnail_and_media_uploads(
1971 store,
1972 &content_type,
1973 send_event_txn.clone(),
1974 created_at,
1975 upload_file_txn.clone(),
1976 file_media_request,
1977 thumbnail,
1978 )
1979 .await?;
1980
1981 store
1983 .save_dependent_queued_request(
1984 &self.room_id,
1985 &upload_file_txn,
1986 send_event_txn.clone().into(),
1987 created_at,
1988 DependentQueuedRequestKind::FinishUpload {
1989 local_echo: Box::new(event),
1990 file_upload: upload_file_txn.clone(),
1991 thumbnail_info,
1992 extra_content,
1993 },
1994 )
1995 .await?;
1996
1997 self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
1998
1999 Ok(())
2000 }
2001
2002 #[cfg(feature = "unstable-msc4274")]
2006 #[allow(clippy::too_many_arguments)]
2007 async fn push_gallery(
2008 &self,
2009 event: RoomMessageEventContent,
2010 send_event_txn: OwnedTransactionId,
2011 created_at: MilliSecondsSinceUnixEpoch,
2012 item_queue_infos: Vec<GalleryItemQueueInfo>,
2013 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
2014 ) -> Result<(), RoomSendQueueStorageError> {
2015 let guard = self.store.lock().await;
2016 let client = guard.client()?;
2017 let store = client.state_store();
2018
2019 let mut finish_item_infos = Vec::with_capacity(item_queue_infos.len());
2020 let mut thumbnail_file_sizes = Vec::with_capacity(item_queue_infos.len());
2021
2022 let Some((first, rest)) = item_queue_infos.split_first() else {
2023 return Ok(());
2024 };
2025
2026 let GalleryItemQueueInfo {
2027 content_type,
2028 upload_file_txn,
2029 file_media_request,
2030 thumbnail,
2031 extra_content: item_extra_content,
2032 } = first;
2033
2034 let thumbnail_info = self
2035 .push_thumbnail_and_media_uploads(
2036 store,
2037 content_type,
2038 send_event_txn.clone(),
2039 created_at,
2040 upload_file_txn.clone(),
2041 file_media_request.clone(),
2042 thumbnail.clone(),
2043 )
2044 .await?;
2045
2046 finish_item_infos.push(FinishGalleryItemInfo {
2047 file_upload: upload_file_txn.clone(),
2048 thumbnail_info,
2049 extra_content: item_extra_content.clone(),
2050 });
2051 thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
2052
2053 let mut last_upload_file_txn = upload_file_txn.clone();
2054
2055 for item_queue_info in rest {
2056 let GalleryItemQueueInfo {
2057 content_type,
2058 upload_file_txn,
2059 file_media_request,
2060 thumbnail,
2061 extra_content: item_extra_content,
2062 } = item_queue_info;
2063
2064 let thumbnail_info = if let Some(QueueThumbnailInfo {
2065 finish_upload_thumbnail_info: thumbnail_info,
2066 media_request_parameters: thumbnail_media_request,
2067 content_type: thumbnail_content_type,
2068 ..
2069 }) = thumbnail
2070 {
2071 let upload_thumbnail_txn = thumbnail_info.txn.clone();
2072
2073 store
2076 .save_dependent_queued_request(
2077 &self.room_id,
2078 &last_upload_file_txn,
2079 upload_thumbnail_txn.clone().into(),
2080 created_at,
2081 DependentQueuedRequestKind::UploadFileOrThumbnail {
2082 content_type: thumbnail_content_type.to_string(),
2083 cache_key: thumbnail_media_request.clone(),
2084 related_to: send_event_txn.clone(),
2085 parent_is_thumbnail_upload: false,
2086 },
2087 )
2088 .await?;
2089
2090 last_upload_file_txn = upload_thumbnail_txn;
2091
2092 Some(thumbnail_info)
2093 } else {
2094 None
2095 };
2096
2097 store
2100 .save_dependent_queued_request(
2101 &self.room_id,
2102 &last_upload_file_txn,
2103 upload_file_txn.clone().into(),
2104 created_at,
2105 DependentQueuedRequestKind::UploadFileOrThumbnail {
2106 content_type: content_type.to_string(),
2107 cache_key: file_media_request.clone(),
2108 related_to: send_event_txn.clone(),
2109 parent_is_thumbnail_upload: thumbnail.is_some(),
2110 },
2111 )
2112 .await?;
2113
2114 finish_item_infos.push(FinishGalleryItemInfo {
2115 file_upload: upload_file_txn.clone(),
2116 thumbnail_info: thumbnail_info.cloned(),
2117 extra_content: item_extra_content.clone(),
2118 });
2119 thumbnail_file_sizes.push(thumbnail.as_ref().map(|t| t.file_size));
2120
2121 last_upload_file_txn = upload_file_txn.clone();
2122 }
2123
2124 store
2127 .save_dependent_queued_request(
2128 &self.room_id,
2129 &last_upload_file_txn,
2130 send_event_txn.clone().into(),
2131 created_at,
2132 DependentQueuedRequestKind::FinishGallery {
2133 local_echo: Box::new(event),
2134 item_infos: finish_item_infos,
2135 extra_content,
2136 },
2137 )
2138 .await?;
2139
2140 self.thumbnail_file_sizes.lock().insert(send_event_txn, thumbnail_file_sizes);
2141
2142 Ok(())
2143 }
2144
2145 #[allow(clippy::too_many_arguments)]
2150 async fn push_thumbnail_and_media_uploads(
2151 &self,
2152 store: &DynStateStore,
2153 content_type: &Mime,
2154 send_event_txn: OwnedTransactionId,
2155 created_at: MilliSecondsSinceUnixEpoch,
2156 upload_file_txn: OwnedTransactionId,
2157 file_media_request: MediaRequestParameters,
2158 thumbnail: Option<QueueThumbnailInfo>,
2159 ) -> Result<Option<FinishUploadThumbnailInfo>, RoomSendQueueStorageError> {
2160 if let Some(QueueThumbnailInfo {
2161 finish_upload_thumbnail_info: thumbnail_info,
2162 media_request_parameters: thumbnail_media_request,
2163 content_type: thumbnail_content_type,
2164 ..
2165 }) = thumbnail
2166 {
2167 let upload_thumbnail_txn = thumbnail_info.txn.clone();
2168
2169 store
2171 .save_send_queue_request(
2172 &self.room_id,
2173 upload_thumbnail_txn.clone(),
2174 created_at,
2175 QueuedRequestKind::MediaUpload {
2176 content_type: thumbnail_content_type.to_string(),
2177 cache_key: thumbnail_media_request,
2178 thumbnail_source: None, related_to: send_event_txn.clone(),
2180 #[cfg(feature = "unstable-msc4274")]
2181 accumulated: vec![],
2182 },
2183 Self::LOW_PRIORITY,
2184 )
2185 .await?;
2186
2187 store
2190 .save_dependent_queued_request(
2191 &self.room_id,
2192 &upload_thumbnail_txn,
2193 upload_file_txn.into(),
2194 created_at,
2195 DependentQueuedRequestKind::UploadFileOrThumbnail {
2196 content_type: content_type.to_string(),
2197 cache_key: file_media_request,
2198 related_to: send_event_txn,
2199 parent_is_thumbnail_upload: true,
2200 },
2201 )
2202 .await?;
2203
2204 Ok(Some(thumbnail_info))
2205 } else {
2206 store
2208 .save_send_queue_request(
2209 &self.room_id,
2210 upload_file_txn,
2211 created_at,
2212 QueuedRequestKind::MediaUpload {
2213 content_type: content_type.to_string(),
2214 cache_key: file_media_request,
2215 thumbnail_source: None,
2216 related_to: send_event_txn,
2217 #[cfg(feature = "unstable-msc4274")]
2218 accumulated: vec![],
2219 },
2220 Self::LOW_PRIORITY,
2221 )
2222 .await?;
2223
2224 Ok(None)
2225 }
2226 }
2227
2228 #[instrument(skip(self))]
2230 async fn react(
2231 &self,
2232 transaction_id: &TransactionId,
2233 key: String,
2234 created_at: MilliSecondsSinceUnixEpoch,
2235 ) -> Result<Option<ChildTransactionId>, RoomSendQueueStorageError> {
2236 let guard = self.store.lock().await;
2237 let client = guard.client()?;
2238 let store = client.state_store();
2239
2240 let requests = store.load_send_queue_requests(&self.room_id).await?;
2241
2242 if !requests
2245 .iter()
2246 .any(|item| item.transaction_id == transaction_id && is_own_event_request(item))
2247 {
2248 let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
2251 if !dependent_requests
2252 .into_iter()
2253 .filter_map(|item| item.is_own_event().then_some(item.own_transaction_id))
2254 .any(|child_txn| *child_txn == *transaction_id)
2255 {
2256 return Ok(None);
2259 }
2260 }
2261
2262 let reaction_txn_id = ChildTransactionId::new();
2264 store
2265 .save_dependent_queued_request(
2266 &self.room_id,
2267 transaction_id,
2268 reaction_txn_id.clone(),
2269 created_at,
2270 DependentQueuedRequestKind::ReactEvent { key },
2271 )
2272 .await?;
2273
2274 Ok(Some(reaction_txn_id))
2275 }
2276
2277 async fn local_echoes(
2280 &self,
2281 room: &RoomSendQueue,
2282 ) -> Result<Vec<LocalEcho>, RoomSendQueueStorageError> {
2283 let guard = self.store.lock().await;
2284 let client = guard.client()?;
2285 let store = client.state_store();
2286
2287 let queued_requests = store.load_send_queue_requests(&self.room_id).await?;
2288
2289 let mut media_upload_errors: HashMap<OwnedTransactionId, QueueWedgeError> = queued_requests
2294 .iter()
2295 .filter_map(|queued| match queued.kind {
2296 QueuedRequestKind::MediaUpload { .. } => {
2297 queued.error.clone().map(|error| (queued.transaction_id.clone(), error))
2298 }
2299 _ => None,
2300 })
2301 .collect();
2302
2303 let local_requests = queued_requests.into_iter().filter_map(|queued| {
2304 Some(LocalEcho {
2305 transaction_id: queued.transaction_id.clone(),
2306 content: match queued.kind {
2307 QueuedRequestKind::Event { content, .. } => LocalEchoContent::Event {
2308 serialized_event: content,
2309 send_handle: SendHandle {
2310 room: room.clone(),
2311 transaction_id: queued.transaction_id,
2312 media_handles: vec![],
2313 created_at: queued.created_at,
2314 },
2315 send_error: queued.error,
2316 },
2317
2318 QueuedRequestKind::MediaUpload { .. } => {
2319 return None;
2323 }
2324
2325 QueuedRequestKind::Redaction { redacts, reason } => {
2326 LocalEchoContent::Redaction {
2327 redacts,
2328 reason,
2329 send_handle: SendHandle {
2330 room: room.clone(),
2331 transaction_id: queued.transaction_id,
2332 media_handles: vec![],
2333 created_at: queued.created_at,
2334 },
2335 send_error: queued.error,
2336 }
2337 }
2338 },
2339 })
2340 });
2341
2342 let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
2343
2344 let reactions_and_medias =
2345 dependent_requests.into_iter().filter_map(|dep| match dep.kind {
2346 DependentQueuedRequestKind::EditEvent { .. }
2347 | DependentQueuedRequestKind::RedactEvent
2348 | DependentQueuedRequestKind::RedactEventWithReason { .. } => {
2349 None
2351 }
2352
2353 DependentQueuedRequestKind::ReactEvent { key } => Some(LocalEcho {
2354 transaction_id: dep.own_transaction_id.clone().into(),
2355 content: LocalEchoContent::React {
2356 key,
2357 send_handle: SendHandle {
2358 room: room.clone(),
2359 transaction_id: dep.own_transaction_id.into(),
2360 media_handles: vec![],
2361 created_at: dep.created_at,
2362 },
2363 applies_to: dep.parent_transaction_id,
2364 },
2365 }),
2366
2367 DependentQueuedRequestKind::UploadFileOrThumbnail { .. } => {
2368 None
2371 }
2372
2373 DependentQueuedRequestKind::FinishUpload {
2374 local_echo,
2375 file_upload,
2376 thumbnail_info,
2377 extra_content,
2378 } => {
2379 let upload_thumbnail_txn = thumbnail_info.map(|info| info.txn);
2380
2381 let send_error = media_upload_errors.remove(&file_upload).or_else(|| {
2384 upload_thumbnail_txn
2385 .as_ref()
2386 .and_then(|txn| media_upload_errors.remove(&**txn))
2387 });
2388
2389 Some(LocalEcho {
2391 transaction_id: dep.own_transaction_id.clone().into(),
2392 content: LocalEchoContent::Event {
2393 serialized_event: upload::merge_extra_content(
2394 SerializableEventContent::new(&(*local_echo).into()).ok()?,
2395 extra_content,
2396 )
2397 .ok()?,
2398 send_handle: SendHandle {
2399 room: room.clone(),
2400 transaction_id: dep.own_transaction_id.into(),
2401 media_handles: vec![MediaHandles {
2402 upload_thumbnail_txn,
2403 upload_file_txn: file_upload,
2404 }],
2405 created_at: dep.created_at,
2406 },
2407 send_error,
2408 },
2409 })
2410 }
2411
2412 #[cfg(feature = "unstable-msc4274")]
2413 DependentQueuedRequestKind::FinishGallery {
2414 local_echo,
2415 item_infos,
2416 extra_content,
2417 } => {
2418 self.create_gallery_local_echo(
2420 dep.own_transaction_id,
2421 room,
2422 dep.created_at,
2423 local_echo,
2424 item_infos,
2425 extra_content,
2426 &mut media_upload_errors,
2427 )
2428 }
2429 });
2430
2431 Ok(local_requests.chain(reactions_and_medias).collect())
2432 }
2433
2434 #[cfg(feature = "unstable-msc4274")]
2436 #[allow(clippy::too_many_arguments)]
2437 fn create_gallery_local_echo(
2438 &self,
2439 transaction_id: ChildTransactionId,
2440 room: &RoomSendQueue,
2441 created_at: MilliSecondsSinceUnixEpoch,
2442 local_echo: Box<RoomMessageEventContent>,
2443 item_infos: Vec<FinishGalleryItemInfo>,
2444 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
2445 media_upload_errors: &mut HashMap<OwnedTransactionId, QueueWedgeError>,
2446 ) -> Option<LocalEcho> {
2447 let send_error = item_infos.iter().find_map(|i| {
2449 media_upload_errors.remove(&i.file_upload).or_else(|| {
2450 i.thumbnail_info.as_ref().and_then(|info| media_upload_errors.remove(&*info.txn))
2451 })
2452 });
2453
2454 Some(LocalEcho {
2455 transaction_id: transaction_id.clone().into(),
2456 content: LocalEchoContent::Event {
2457 serialized_event: upload::merge_gallery_extra_content(
2458 SerializableEventContent::new(&(*local_echo).into()).ok()?,
2459 extra_content,
2460 item_infos.iter().map(|item| item.extra_content.clone()),
2461 )
2462 .ok()?,
2463 send_handle: SendHandle {
2464 room: room.clone(),
2465 transaction_id: transaction_id.into(),
2466 media_handles: item_infos
2467 .into_iter()
2468 .map(|i| MediaHandles {
2469 upload_thumbnail_txn: i.thumbnail_info.map(|info| info.txn),
2470 upload_file_txn: i.file_upload,
2471 })
2472 .collect(),
2473 created_at,
2474 },
2475 send_error,
2476 },
2477 })
2478 }
2479
2480 #[instrument(skip_all)]
2488 async fn try_apply_single_dependent_request(
2489 &self,
2490 client: &Client,
2491 dependent_request: DependentQueuedRequest,
2492 new_updates: &mut Vec<RoomSendQueueUpdate>,
2493 ) -> Result<bool, RoomSendQueueError> {
2494 let store = client.state_store();
2495
2496 let parent_key = dependent_request.parent_key;
2497
2498 match dependent_request.kind {
2499 DependentQueuedRequestKind::EditEvent { new_content } => {
2500 if let Some(parent_key) = parent_key {
2501 let Some(event_id) = parent_key.into_event_id() else {
2502 return Err(RoomSendQueueError::StorageError(
2503 RoomSendQueueStorageError::InvalidParentKey,
2504 ));
2505 };
2506
2507 let room = client
2509 .get_room(&self.room_id)
2510 .ok_or(RoomSendQueueError::RoomDisappeared)?;
2511
2512 let mut unknown_fields = None;
2518
2519 let edited_content = match new_content.deserialize() {
2521 Ok(AnyMessageLikeEventContent::RoomMessage(c)) => {
2522 unknown_fields = upload::unknown_fields(&new_content, &c);
2523
2524 EditedContent::RoomMessage(c.into())
2526 }
2527
2528 Ok(AnyMessageLikeEventContent::UnstablePollStart(c)) => {
2529 let poll_start = c.poll_start().clone();
2530 EditedContent::PollStart {
2531 fallback_text: poll_start.question.text.clone(),
2532 new_content: poll_start,
2533 }
2534 }
2535
2536 Ok(c) => {
2537 warn!("Unsupported edit content type: {:?}", c.event_type());
2538 return Ok(true);
2539 }
2540
2541 Err(err) => {
2542 warn!("Unable to deserialize: {err}");
2543 return Ok(true);
2544 }
2545 };
2546
2547 let edit_event = match room.make_edit_event(&event_id, edited_content).await {
2548 Ok(e) => e,
2549 Err(err) => {
2550 warn!("couldn't create edited event: {err}");
2551 return Ok(true);
2552 }
2553 };
2554
2555 let mut edit_json = serde_json::to_value(&edit_event)
2556 .map_err(RoomSendQueueStorageError::JsonSerialization)?;
2557
2558 if let Some(serde_json::Value::Object(mut unknown)) = unknown_fields
2561 && let Some(edited) = edit_json.get_mut("m.new_content")
2562 {
2563 unknown.remove("m.relates_to");
2565 unknown.remove("m.new_content");
2566 upload::restore_unknown_fields(edited, unknown.into());
2567 }
2568
2569 let serializable = SerializableEventContent::from_raw(
2571 Raw::from_json(
2572 serde_json::value::to_raw_value(&edit_json)
2573 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2574 ),
2575 edit_event.event_type().to_string(),
2576 );
2577
2578 store
2579 .save_send_queue_request(
2580 &self.room_id,
2581 dependent_request.own_transaction_id.into(),
2582 dependent_request.created_at,
2583 serializable.into(),
2584 Self::HIGH_PRIORITY,
2585 )
2586 .await
2587 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2588 } else {
2589 let parent_transaction_id = &dependent_request.parent_transaction_id;
2591
2592 let request = QueuedRequestKind::Event {
2593 content: new_content,
2594 #[cfg(feature = "unstable-msc4354")]
2595 sticky_duration: self
2596 .sticky_duration_of(store, parent_transaction_id)
2597 .await?,
2598 };
2599
2600 let edited = store
2601 .update_send_queue_request(&self.room_id, parent_transaction_id, request)
2602 .await
2603 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2604
2605 if !edited {
2606 warn!("missing local echo upon dependent edit");
2607 }
2608 }
2609 }
2610
2611 kind @ (DependentQueuedRequestKind::RedactEvent
2612 | DependentQueuedRequestKind::RedactEventWithReason { .. }) => {
2613 let reason = match kind {
2614 DependentQueuedRequestKind::RedactEventWithReason { reason } => reason,
2615 _ => None,
2617 };
2618
2619 if let Some(parent_key) = parent_key {
2620 let Some(event_id) = parent_key.into_event_id() else {
2621 return Err(RoomSendQueueError::StorageError(
2622 RoomSendQueueStorageError::InvalidParentKey,
2623 ));
2624 };
2625
2626 let room = client
2628 .get_room(&self.room_id)
2629 .ok_or(RoomSendQueueError::RoomDisappeared)?;
2630
2631 if let Err(err) = room
2637 .redact(
2638 &event_id,
2639 reason.as_deref(),
2640 Some(dependent_request.own_transaction_id.into()),
2641 )
2642 .await
2643 {
2644 warn!("error when sending a redact for {event_id}: {err}");
2645 return Ok(false);
2646 }
2647 } else {
2648 let removed = store
2651 .remove_send_queue_request(
2652 &self.room_id,
2653 &dependent_request.parent_transaction_id,
2654 )
2655 .await
2656 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2657
2658 if !removed {
2659 warn!("missing local echo upon dependent redact");
2660 }
2661 }
2662 }
2663
2664 DependentQueuedRequestKind::ReactEvent { key } => {
2665 if let Some(parent_key) = parent_key {
2666 let Some(parent_event_id) = parent_key.into_event_id() else {
2667 return Err(RoomSendQueueError::StorageError(
2668 RoomSendQueueStorageError::InvalidParentKey,
2669 ));
2670 };
2671
2672 let react_event =
2674 ReactionEventContent::new(Annotation::new(parent_event_id, key)).into();
2675 let serializable = SerializableEventContent::from_raw(
2676 Raw::new(&react_event)
2677 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
2678 react_event.event_type().to_string(),
2679 );
2680
2681 store
2682 .save_send_queue_request(
2683 &self.room_id,
2684 dependent_request.own_transaction_id.into(),
2685 dependent_request.created_at,
2686 serializable.into(),
2687 Self::HIGH_PRIORITY,
2688 )
2689 .await
2690 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2691 } else {
2692 return Ok(false);
2694 }
2695 }
2696
2697 DependentQueuedRequestKind::UploadFileOrThumbnail {
2698 content_type,
2699 cache_key,
2700 related_to,
2701 parent_is_thumbnail_upload,
2702 } => {
2703 let Some(parent_key) = parent_key else {
2704 return Ok(false);
2706 };
2707 self.handle_dependent_file_or_thumbnail_upload(
2708 client,
2709 dependent_request.own_transaction_id.into(),
2710 parent_key,
2711 content_type,
2712 cache_key,
2713 related_to,
2714 parent_is_thumbnail_upload,
2715 )
2716 .await?;
2717 }
2718
2719 DependentQueuedRequestKind::FinishUpload {
2720 local_echo,
2721 file_upload,
2722 thumbnail_info,
2723 extra_content,
2724 } => {
2725 let Some(parent_key) = parent_key else {
2726 return Ok(false);
2728 };
2729 self.handle_dependent_finish_upload(
2730 client,
2731 dependent_request.own_transaction_id.into(),
2732 parent_key,
2733 *local_echo,
2734 file_upload,
2735 thumbnail_info,
2736 extra_content,
2737 new_updates,
2738 )
2739 .await?;
2740 }
2741
2742 #[cfg(feature = "unstable-msc4274")]
2743 DependentQueuedRequestKind::FinishGallery { local_echo, item_infos, extra_content } => {
2744 let Some(parent_key) = parent_key else {
2745 return Ok(false);
2747 };
2748 self.handle_dependent_finish_gallery_upload(
2749 client,
2750 dependent_request.own_transaction_id.into(),
2751 parent_key,
2752 *local_echo,
2753 item_infos,
2754 extra_content,
2755 new_updates,
2756 )
2757 .await?;
2758 }
2759 }
2760
2761 Ok(true)
2762 }
2763
2764 #[instrument(skip(self))]
2765 async fn apply_dependent_requests(
2766 &self,
2767 new_updates: &mut Vec<RoomSendQueueUpdate>,
2768 ) -> Result<(), RoomSendQueueError> {
2769 let guard = self.store.lock().await;
2770
2771 let client = guard.client()?;
2772 let store = client.state_store();
2773
2774 let dependent_requests = store
2775 .load_dependent_queued_requests(&self.room_id)
2776 .await
2777 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2778
2779 let num_initial_dependent_requests = dependent_requests.len();
2780 if num_initial_dependent_requests == 0 {
2781 return Ok(());
2783 }
2784
2785 let canonicalized_dependent_requests = canonicalize_dependent_requests(&dependent_requests);
2786
2787 for original in &dependent_requests {
2789 if !canonicalized_dependent_requests
2790 .iter()
2791 .any(|canonical| canonical.own_transaction_id == original.own_transaction_id)
2792 {
2793 store
2794 .remove_dependent_queued_request(&self.room_id, &original.own_transaction_id)
2795 .await
2796 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2797 }
2798 }
2799
2800 let mut num_dependent_requests = canonicalized_dependent_requests.len();
2801
2802 debug!(
2803 num_dependent_requests,
2804 num_initial_dependent_requests, "starting handling of dependent requests"
2805 );
2806
2807 for dependent in canonicalized_dependent_requests {
2808 let dependent_id = dependent.own_transaction_id.clone();
2809
2810 match self.try_apply_single_dependent_request(&client, dependent, new_updates).await {
2811 Ok(should_remove) => {
2812 if should_remove {
2813 store
2816 .remove_dependent_queued_request(&self.room_id, &dependent_id)
2817 .await
2818 .map_err(RoomSendQueueStorageError::StateStoreError)?;
2819
2820 num_dependent_requests -= 1;
2821 }
2822 }
2823
2824 Err(err) => {
2825 warn!("error when applying single dependent request: {err}");
2826 }
2827 }
2828 }
2829
2830 debug!(
2831 leftover_dependent_requests = num_dependent_requests,
2832 "stopped handling dependent request"
2833 );
2834
2835 Ok(())
2836 }
2837
2838 async fn remove_dependent_send_queue_request(
2840 &self,
2841 dependent_event_id: &ChildTransactionId,
2842 ) -> Result<bool, RoomSendQueueStorageError> {
2843 Ok(self
2844 .store
2845 .lock()
2846 .await
2847 .client()?
2848 .state_store()
2849 .remove_dependent_queued_request(&self.room_id, dependent_event_id)
2850 .await?)
2851 }
2852}
2853
2854#[cfg(feature = "unstable-msc4274")]
2855struct GalleryItemQueueInfo {
2857 content_type: Mime,
2858 upload_file_txn: OwnedTransactionId,
2859 file_media_request: MediaRequestParameters,
2860 thumbnail: Option<QueueThumbnailInfo>,
2861 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
2862}
2863
2864#[derive(Clone, Debug)]
2866pub enum LocalEchoContent {
2867 Event {
2869 serialized_event: SerializableEventContent,
2872 send_handle: SendHandle,
2874 send_error: Option<QueueWedgeError>,
2877 },
2878
2879 React {
2881 key: String,
2883 send_handle: SendHandle,
2885 applies_to: OwnedTransactionId,
2887 },
2888
2889 Redaction {
2891 redacts: OwnedEventId,
2893 reason: Option<String>,
2895 send_handle: SendHandle,
2897 send_error: Option<QueueWedgeError>,
2900 },
2901}
2902
2903#[derive(Clone, Debug)]
2906pub struct LocalEcho {
2907 pub transaction_id: OwnedTransactionId,
2909 pub content: LocalEchoContent,
2911}
2912
2913#[derive(Clone, Debug)]
2916pub enum RoomSendQueueUpdate {
2917 NewLocalEvent(LocalEcho),
2922
2923 CancelledLocalEvent {
2926 transaction_id: OwnedTransactionId,
2928 },
2929
2930 ReplacedLocalEvent {
2932 transaction_id: OwnedTransactionId,
2934
2935 new_content: SerializableEventContent,
2937 },
2938
2939 SendError {
2946 transaction_id: OwnedTransactionId,
2948 error: Arc<crate::Error>,
2950 is_recoverable: bool,
2956 },
2957
2958 RetryEvent {
2960 transaction_id: OwnedTransactionId,
2962 },
2963
2964 SentEvent {
2967 transaction_id: OwnedTransactionId,
2969 event_id: OwnedEventId,
2971 },
2972
2973 MediaUpload {
2976 related_to: OwnedTransactionId,
2978
2979 file: Option<MediaSource>,
2981
2982 index: u64,
2986
2987 progress: AbstractProgress,
2991 },
2992}
2993
2994#[derive(Clone, Debug)]
2999pub struct SendQueueUpdate {
3000 pub room_id: OwnedRoomId,
3002
3003 pub update: RoomSendQueueUpdate,
3005}
3006
3007#[derive(Debug, thiserror::Error)]
3009pub enum RoomSendQueueError {
3010 #[error("the room isn't in the joined state")]
3012 RoomNotJoined,
3013
3014 #[error("the room is now missing from the client")]
3018 RoomDisappeared,
3019
3020 #[error(transparent)]
3022 StorageError(#[from] RoomSendQueueStorageError),
3023
3024 #[error("the attachment event could not be created")]
3026 FailedToCreateAttachment,
3027
3028 #[error(transparent)]
3031 Edit(#[from] crate::room::edit::EditError),
3032
3033 #[cfg(feature = "unstable-msc4274")]
3035 #[error("the gallery contains no items")]
3036 EmptyGallery,
3037
3038 #[cfg(feature = "unstable-msc4274")]
3040 #[error("the gallery event could not be created")]
3041 FailedToCreateGallery,
3042}
3043
3044#[derive(Debug, thiserror::Error)]
3046pub enum RoomSendQueueStorageError {
3047 #[error(transparent)]
3049 StateStoreError(#[from] StoreError),
3050
3051 #[error(transparent)]
3053 EventCacheStoreError(#[from] EventCacheStoreError),
3054
3055 #[error(transparent)]
3057 MediaStoreError(#[from] MediaStoreError),
3058
3059 #[error(transparent)]
3061 LockError(#[from] CrossProcessLockError),
3062
3063 #[error(transparent)]
3065 JsonSerialization(#[from] serde_json::Error),
3066
3067 #[error("a dependent event had an invalid parent key type")]
3070 InvalidParentKey,
3071
3072 #[error("The client is shutting down.")]
3074 ClientShuttingDown,
3075
3076 #[error("This operation is not implemented for media uploads")]
3078 OperationNotImplementedYet,
3079
3080 #[error("Can't edit a media caption when the underlying event isn't a media")]
3082 InvalidMediaCaptionEdit,
3083}
3084
3085#[derive(Clone, Debug)]
3087struct MediaHandles {
3088 upload_thumbnail_txn: Option<OwnedTransactionId>,
3092
3093 upload_file_txn: OwnedTransactionId,
3095}
3096
3097#[allow(missing_debug_implementations)]
3099pub struct SendEvent<'a> {
3100 queue: &'a RoomSendQueue,
3101 content: AnyMessageLikeEventContent,
3102 extra_content: Option<serde_json::Map<String, serde_json::Value>>,
3103 #[cfg(feature = "unstable-msc4354")]
3104 sticky_duration: Option<StickyDurationMs>,
3105}
3106
3107impl<'a> SendEvent<'a> {
3108 pub fn with_extra_content(
3112 mut self,
3113 extra_content: serde_json::Map<String, serde_json::Value>,
3114 ) -> Self {
3115 self.extra_content = Some(extra_content);
3116 self
3117 }
3118
3119 #[cfg(feature = "unstable-msc4354")]
3125 pub fn with_sticky_duration(mut self, duration: Duration) -> Self {
3126 self.sticky_duration = Some(sticky_duration_ms(duration));
3127 self
3128 }
3129}
3130
3131impl<'a> IntoFuture for SendEvent<'a> {
3132 type Output = Result<SendHandle, RoomSendQueueError>;
3133 boxed_into_future!(extra_bounds: 'a);
3134
3135 fn into_future(self) -> Self::IntoFuture {
3136 Box::pin(async move {
3137 let serialized = upload::merge_extra_content(
3138 SerializableEventContent::new(&self.content)
3139 .map_err(RoomSendQueueStorageError::JsonSerialization)?,
3140 self.extra_content,
3141 )?;
3142 self.queue
3143 .send_serialized(
3144 serialized,
3145 #[cfg(feature = "unstable-msc4354")]
3146 self.sticky_duration,
3147 )
3148 .await
3149 })
3150 }
3151}
3152
3153#[allow(missing_debug_implementations)]
3155pub struct SendRawEvent<'a> {
3156 queue: &'a RoomSendQueue,
3157 content: SerializableEventContent,
3158 #[cfg(feature = "unstable-msc4354")]
3159 sticky_duration: Option<StickyDurationMs>,
3160}
3161
3162impl SendRawEvent<'_> {
3163 #[cfg(feature = "unstable-msc4354")]
3169 pub fn with_sticky_duration(mut self, duration: Duration) -> Self {
3170 self.sticky_duration = Some(sticky_duration_ms(duration));
3171 self
3172 }
3173}
3174
3175impl<'a> IntoFuture for SendRawEvent<'a> {
3176 type Output = Result<SendHandle, RoomSendQueueError>;
3177 boxed_into_future!(extra_bounds: 'a);
3178
3179 fn into_future(self) -> Self::IntoFuture {
3180 Box::pin(async move {
3181 self.queue
3182 .send_serialized(
3183 self.content,
3184 #[cfg(feature = "unstable-msc4354")]
3185 self.sticky_duration,
3186 )
3187 .await
3188 })
3189 }
3190}
3191
3192#[derive(Clone, Debug)]
3198pub struct SendHandle {
3199 room: RoomSendQueue,
3201
3202 transaction_id: OwnedTransactionId,
3207
3208 media_handles: Vec<MediaHandles>,
3210
3211 pub created_at: MilliSecondsSinceUnixEpoch,
3213}
3214
3215impl SendHandle {
3216 #[cfg(test)]
3218 pub(crate) fn new(
3219 room: RoomSendQueue,
3220 transaction_id: OwnedTransactionId,
3221 created_at: MilliSecondsSinceUnixEpoch,
3222 ) -> Self {
3223 Self { room, transaction_id, media_handles: vec![], created_at }
3224 }
3225
3226 pub fn transaction_id(&self) -> &TransactionId {
3228 &self.transaction_id
3229 }
3230
3231 fn nyi_for_uploads(&self) -> Result<(), RoomSendQueueStorageError> {
3232 if !self.media_handles.is_empty() {
3233 Err(RoomSendQueueStorageError::OperationNotImplementedYet)
3234 } else {
3235 Ok(())
3236 }
3237 }
3238
3239 pub async fn abort(&self) -> Result<bool, RoomSendQueueStorageError> {
3244 self.abort_with_reason(None).await
3245 }
3246
3247 #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3258 pub async fn abort_with_reason(
3259 &self,
3260 reason: Option<String>,
3261 ) -> Result<bool, RoomSendQueueStorageError> {
3262 trace!("received an abort request");
3263
3264 let queue = &self.room.inner.queue;
3265
3266 for handles in &self.media_handles {
3267 if queue.abort_upload(&self.transaction_id, handles).await? {
3268 self.room.inner.notifier.notify_one();
3271
3272 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3274 transaction_id: self.transaction_id.clone(),
3275 });
3276
3277 return Ok(true);
3278 }
3279
3280 }
3284
3285 let aborted =
3289 queue.remove_dependent_send_queue_request(&self.transaction_id.clone().into()).await?
3290 || queue.cancel_event(&self.transaction_id, reason).await?;
3291
3292 if aborted {
3293 trace!("successful abort");
3294
3295 self.room.inner.notifier.notify_one();
3298
3299 self.room.send_update(RoomSendQueueUpdate::CancelledLocalEvent {
3301 transaction_id: self.transaction_id.clone(),
3302 });
3303
3304 Ok(true)
3305 } else {
3306 debug!("local echo didn't exist anymore, can't abort");
3307 Ok(false)
3308 }
3309 }
3310
3311 #[instrument(skip(self, new_content), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3324 pub async fn edit_raw(
3325 &self,
3326 new_content: Raw<AnyMessageLikeEventContent>,
3327 event_type: String,
3328 ) -> Result<bool, RoomSendQueueStorageError> {
3329 trace!("received an edit request");
3330 self.nyi_for_uploads()?;
3331
3332 let serializable = SerializableEventContent::from_raw(new_content, event_type);
3333
3334 if self.room.inner.queue.replace_event(&self.transaction_id, serializable.clone()).await? {
3335 trace!("successful edit");
3336
3337 self.room.inner.notifier.notify_one();
3339
3340 self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
3342 transaction_id: self.transaction_id.clone(),
3343 new_content: serializable,
3344 });
3345
3346 Ok(true)
3347 } else {
3348 debug!("local echo doesn't exist anymore, can't edit");
3349 Ok(false)
3350 }
3351 }
3352
3353 pub async fn edit(
3366 &self,
3367 new_content: AnyMessageLikeEventContent,
3368 ) -> Result<bool, RoomSendQueueStorageError> {
3369 self.edit_raw(
3370 Raw::new(&new_content).map_err(RoomSendQueueStorageError::JsonSerialization)?,
3371 new_content.event_type().to_string(),
3372 )
3373 .await
3374 }
3375
3376 pub async fn edit_media_caption(
3381 &self,
3382 caption: Option<String>,
3383 formatted_caption: Option<FormattedBody>,
3384 mentions: Option<Mentions>,
3385 ) -> Result<bool, RoomSendQueueStorageError> {
3386 if let Some(new_content) = self
3387 .room
3388 .inner
3389 .queue
3390 .edit_media_caption(&self.transaction_id, caption, formatted_caption, mentions)
3391 .await?
3392 {
3393 trace!("successful edit of media caption");
3394
3395 self.room.inner.notifier.notify_one();
3397
3398 self.room.send_update(RoomSendQueueUpdate::ReplacedLocalEvent {
3400 transaction_id: self.transaction_id.clone(),
3401 new_content,
3402 });
3403
3404 Ok(true)
3405 } else {
3406 debug!("local echo doesn't exist anymore, can't edit media caption");
3407 Ok(false)
3408 }
3409 }
3410
3411 pub async fn unwedge(&self) -> Result<(), RoomSendQueueError> {
3414 let room = &self.room.inner;
3415
3416 for handles in &self.media_handles {
3424 room.queue
3425 .mark_as_unwedged(&handles.upload_file_txn)
3426 .await
3427 .map_err(RoomSendQueueError::StorageError)?;
3428
3429 if let Some(txn) = &handles.upload_thumbnail_txn {
3430 room.queue.mark_as_unwedged(txn).await.map_err(RoomSendQueueError::StorageError)?;
3431 }
3432 }
3433
3434 self.room.unwedge_request(&self.transaction_id).await
3435 }
3436
3437 #[instrument(skip(self), fields(room_id = %self.room.inner.room.room_id(), txn_id = %self.transaction_id))]
3442 pub async fn react(
3443 &self,
3444 key: String,
3445 ) -> Result<Option<SendHandle>, RoomSendQueueStorageError> {
3446 trace!("received an intent to react");
3447
3448 let created_at = MilliSecondsSinceUnixEpoch::now();
3449 if let Some(reaction_txn_id) =
3450 self.room.inner.queue.react(&self.transaction_id, key.clone(), created_at).await?
3451 {
3452 trace!("successfully queued react");
3453
3454 self.room.inner.notifier.notify_one();
3457
3458 let send_handle = SendHandle {
3460 room: self.room.clone(),
3461 transaction_id: reaction_txn_id.clone().into(),
3462 media_handles: vec![],
3463 created_at,
3464 };
3465
3466 self.room.send_update(RoomSendQueueUpdate::NewLocalEvent(LocalEcho {
3467 transaction_id: reaction_txn_id.into(),
3470 content: LocalEchoContent::React {
3471 key,
3472 send_handle: send_handle.clone(),
3473 applies_to: self.transaction_id.clone(),
3474 },
3475 }));
3476
3477 Ok(Some(send_handle))
3478 } else {
3479 debug!("local echo doesn't exist anymore, can't react");
3480 Ok(None)
3481 }
3482 }
3483}
3484
3485fn is_own_event_request(request: &QueuedRequest) -> bool {
3493 request.as_event().is_some_and(|content| {
3494 TimelineEventType::from(content.raw().1) != TimelineEventType::Reaction
3495 })
3496}
3497
3498fn canonicalize_dependent_requests(
3502 dependent: &[DependentQueuedRequest],
3503) -> Vec<DependentQueuedRequest> {
3504 let mut by_txn = HashMap::<OwnedTransactionId, Vec<&DependentQueuedRequest>>::new();
3505
3506 for d in dependent {
3507 let prevs = by_txn.entry(d.parent_transaction_id.clone()).or_default();
3508
3509 if prevs.iter().any(|prev| {
3510 matches!(
3511 prev.kind,
3512 DependentQueuedRequestKind::RedactEvent
3513 | DependentQueuedRequestKind::RedactEventWithReason { .. }
3514 )
3515 }) {
3516 continue;
3519 }
3520
3521 match &d.kind {
3522 DependentQueuedRequestKind::EditEvent { .. } => {
3523 if let Some(prev_edit) = prevs
3525 .iter_mut()
3526 .find(|prev| matches!(prev.kind, DependentQueuedRequestKind::EditEvent { .. }))
3527 {
3528 *prev_edit = d;
3529 } else {
3530 prevs.insert(0, d);
3531 }
3532 }
3533
3534 DependentQueuedRequestKind::UploadFileOrThumbnail { .. }
3535 | DependentQueuedRequestKind::FinishUpload { .. }
3536 | DependentQueuedRequestKind::ReactEvent { .. } => {
3537 prevs.push(d);
3539 }
3540
3541 #[cfg(feature = "unstable-msc4274")]
3542 DependentQueuedRequestKind::FinishGallery { .. } => {
3543 prevs.push(d);
3545 }
3546
3547 DependentQueuedRequestKind::RedactEvent
3548 | DependentQueuedRequestKind::RedactEventWithReason { .. } => {
3549 prevs.clear();
3551 prevs.push(d);
3552 }
3553 }
3554 }
3555
3556 by_txn.into_values().flat_map(|entries| entries.into_iter().cloned()).collect()
3557}
3558
3559#[cfg(all(test, not(target_family = "wasm")))]
3560mod tests {
3561 use std::{assert_matches, sync::Arc, time::Duration};
3562
3563 use matrix_sdk_base::store::{
3564 ChildTransactionId, DependentQueuedRequest, DependentQueuedRequestKind,
3565 SerializableEventContent,
3566 };
3567 use matrix_sdk_test::{JoinedRoomBuilder, SyncResponseBuilder, async_test};
3568 use ruma::{
3569 MilliSecondsSinceUnixEpoch, TransactionId,
3570 api::error::UnknownTokenErrorData,
3571 event_id,
3572 events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
3573 room_id,
3574 };
3575 use strass::assert_let;
3576
3577 use super::{RoomSendQueueUpdate, canonicalize_dependent_requests};
3578 use crate::{
3579 SessionChange,
3580 test_utils::{logged_in_client, mocks::MatrixMockServer},
3581 };
3582
3583 #[test]
3584 fn test_canonicalize_dependent_events_created_at() {
3585 let txn = TransactionId::new();
3588 let created_at = MilliSecondsSinceUnixEpoch::now();
3589
3590 let edit = DependentQueuedRequest {
3591 own_transaction_id: ChildTransactionId::new(),
3592 parent_transaction_id: txn.clone(),
3593 kind: DependentQueuedRequestKind::EditEvent {
3594 new_content: SerializableEventContent::new(
3595 &RoomMessageEventContent::text_plain("edit").into(),
3596 )
3597 .unwrap(),
3598 },
3599 parent_key: None,
3600 created_at,
3601 };
3602
3603 let res = canonicalize_dependent_requests(&[edit]);
3604
3605 assert_eq!(res.len(), 1);
3606 assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3607 assert_let!(
3608 AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3609 );
3610 assert_eq!(msg.body(), "edit");
3611 assert_eq!(res[0].parent_transaction_id, txn);
3612 assert_eq!(res[0].created_at, created_at);
3613 }
3614
3615 #[async_test]
3616 async fn test_client_no_cycle_with_send_queue() {
3617 for enabled in [true, false] {
3618 let client = logged_in_client(None).await;
3619 let weak_client = client.downgrade();
3620
3621 {
3622 let mut sync_response_builder = SyncResponseBuilder::new();
3623
3624 let room_id = room_id!("!a:b.c");
3625
3626 client
3628 .base_client()
3629 .receive_sync_response(
3630 sync_response_builder
3631 .add_joined_room(JoinedRoomBuilder::new(room_id))
3632 .build_sync_response(),
3633 )
3634 .await
3635 .unwrap();
3636
3637 let room = client.get_room(room_id).unwrap();
3638 let q = room.send_queue();
3639
3640 let _watcher = q.subscribe().await;
3641
3642 client.send_queue().set_enabled(enabled).await;
3643 }
3644
3645 drop(client);
3646
3647 tokio::time::sleep(Duration::from_millis(500)).await;
3649
3650 let client = weak_client.get();
3652 assert!(
3653 client.is_none(),
3654 "too many strong references to the client: {}",
3655 Arc::strong_count(&client.unwrap().inner)
3656 );
3657 }
3658 }
3659
3660 #[test]
3661 fn test_canonicalize_dependent_events_smoke_test() {
3662 let txn = TransactionId::new();
3664
3665 let edit = DependentQueuedRequest {
3666 own_transaction_id: ChildTransactionId::new(),
3667 parent_transaction_id: txn.clone(),
3668 kind: DependentQueuedRequestKind::EditEvent {
3669 new_content: SerializableEventContent::new(
3670 &RoomMessageEventContent::text_plain("edit").into(),
3671 )
3672 .unwrap(),
3673 },
3674 parent_key: None,
3675 created_at: MilliSecondsSinceUnixEpoch::now(),
3676 };
3677 let res = canonicalize_dependent_requests(&[edit]);
3678
3679 assert_eq!(res.len(), 1);
3680 assert_matches!(&res[0].kind, DependentQueuedRequestKind::EditEvent { .. });
3681 assert_eq!(res[0].parent_transaction_id, txn);
3682 assert!(res[0].parent_key.is_none());
3683 }
3684
3685 #[test]
3686 fn test_canonicalize_dependent_events_redaction_preferred() {
3687 let txn = TransactionId::new();
3689
3690 let mut inputs = Vec::with_capacity(100);
3691 let redact = DependentQueuedRequest {
3692 own_transaction_id: ChildTransactionId::new(),
3693 parent_transaction_id: txn.clone(),
3694 kind: DependentQueuedRequestKind::RedactEventWithReason { reason: None },
3695 parent_key: None,
3696 created_at: MilliSecondsSinceUnixEpoch::now(),
3697 };
3698
3699 let edit = DependentQueuedRequest {
3700 own_transaction_id: ChildTransactionId::new(),
3701 parent_transaction_id: txn.clone(),
3702 kind: DependentQueuedRequestKind::EditEvent {
3703 new_content: SerializableEventContent::new(
3704 &RoomMessageEventContent::text_plain("edit").into(),
3705 )
3706 .unwrap(),
3707 },
3708 parent_key: None,
3709 created_at: MilliSecondsSinceUnixEpoch::now(),
3710 };
3711
3712 inputs.push({
3713 let mut edit = edit.clone();
3714 edit.own_transaction_id = ChildTransactionId::new();
3715 edit
3716 });
3717
3718 inputs.push(redact);
3719
3720 for _ in 0..98 {
3721 let mut edit = edit.clone();
3722 edit.own_transaction_id = ChildTransactionId::new();
3723 inputs.push(edit);
3724 }
3725
3726 let res = canonicalize_dependent_requests(&inputs);
3727
3728 assert_eq!(res.len(), 1);
3729 assert_matches!(&res[0].kind, DependentQueuedRequestKind::RedactEventWithReason { .. });
3730 assert_eq!(res[0].parent_transaction_id, txn);
3731 }
3732
3733 #[test]
3734 fn test_canonicalize_dependent_events_last_edit_preferred() {
3735 let parent_txn = TransactionId::new();
3736
3737 let inputs = (0..10)
3739 .map(|i| DependentQueuedRequest {
3740 own_transaction_id: ChildTransactionId::new(),
3741 parent_transaction_id: parent_txn.clone(),
3742 kind: DependentQueuedRequestKind::EditEvent {
3743 new_content: SerializableEventContent::new(
3744 &RoomMessageEventContent::text_plain(format!("edit{i}")).into(),
3745 )
3746 .unwrap(),
3747 },
3748 parent_key: None,
3749 created_at: MilliSecondsSinceUnixEpoch::now(),
3750 })
3751 .collect::<Vec<_>>();
3752
3753 let txn = inputs[9].parent_transaction_id.clone();
3754
3755 let res = canonicalize_dependent_requests(&inputs);
3756
3757 assert_eq!(res.len(), 1);
3758 assert_let!(DependentQueuedRequestKind::EditEvent { new_content } = &res[0].kind);
3759 assert_let!(
3760 AnyMessageLikeEventContent::RoomMessage(msg) = new_content.deserialize().unwrap()
3761 );
3762 assert_eq!(msg.body(), "edit9");
3763 assert_eq!(res[0].parent_transaction_id, txn);
3764 }
3765
3766 #[test]
3767 fn test_canonicalize_multiple_local_echoes() {
3768 let txn1 = TransactionId::new();
3769 let txn2 = TransactionId::new();
3770
3771 let child1 = ChildTransactionId::new();
3772 let child2 = ChildTransactionId::new();
3773
3774 let inputs = vec![
3775 DependentQueuedRequest {
3777 own_transaction_id: child1.clone(),
3778 kind: DependentQueuedRequestKind::RedactEventWithReason { reason: None },
3779 parent_transaction_id: txn1.clone(),
3780 parent_key: None,
3781 created_at: MilliSecondsSinceUnixEpoch::now(),
3782 },
3783 DependentQueuedRequest {
3785 own_transaction_id: child2,
3786 kind: DependentQueuedRequestKind::EditEvent {
3787 new_content: SerializableEventContent::new(
3788 &RoomMessageEventContent::text_plain("edit").into(),
3789 )
3790 .unwrap(),
3791 },
3792 parent_transaction_id: txn2.clone(),
3793 parent_key: None,
3794 created_at: MilliSecondsSinceUnixEpoch::now(),
3795 },
3796 ];
3797
3798 let res = canonicalize_dependent_requests(&inputs);
3799
3800 assert_eq!(res.len(), 2);
3802
3803 for dependent in res {
3804 if dependent.own_transaction_id == child1 {
3805 assert_eq!(dependent.parent_transaction_id, txn1);
3806 assert_matches!(
3807 dependent.kind,
3808 DependentQueuedRequestKind::RedactEventWithReason { .. }
3809 );
3810 } else {
3811 assert_eq!(dependent.parent_transaction_id, txn2);
3812 assert_matches!(dependent.kind, DependentQueuedRequestKind::EditEvent { .. });
3813 }
3814 }
3815 }
3816
3817 #[test]
3818 fn test_canonicalize_reactions_after_edits() {
3819 let txn = TransactionId::new();
3821
3822 let react_id = ChildTransactionId::new();
3823 let react = DependentQueuedRequest {
3824 own_transaction_id: react_id.clone(),
3825 kind: DependentQueuedRequestKind::ReactEvent { key: "🧠".to_owned() },
3826 parent_transaction_id: txn.clone(),
3827 parent_key: None,
3828 created_at: MilliSecondsSinceUnixEpoch::now(),
3829 };
3830
3831 let edit_id = ChildTransactionId::new();
3832 let edit = DependentQueuedRequest {
3833 own_transaction_id: edit_id.clone(),
3834 kind: DependentQueuedRequestKind::EditEvent {
3835 new_content: SerializableEventContent::new(
3836 &RoomMessageEventContent::text_plain("edit").into(),
3837 )
3838 .unwrap(),
3839 },
3840 parent_transaction_id: txn,
3841 parent_key: None,
3842 created_at: MilliSecondsSinceUnixEpoch::now(),
3843 };
3844
3845 let res = canonicalize_dependent_requests(&[react, edit]);
3846
3847 assert_eq!(res.len(), 2);
3848 assert_eq!(res[0].own_transaction_id, edit_id);
3849 assert_eq!(res[1].own_transaction_id, react_id);
3850 }
3851
3852 #[async_test]
3853 async fn test_request_wedged_by_a_rejected_token_is_unwedged_on_lag() {
3854 let mock = MatrixMockServer::new().await;
3855 let client = mock.client_builder().build().await;
3856 let room = mock.sync_joined_room(&client, room_id!("!a:b.c")).await;
3857
3858 let q = room.send_queue();
3859 let (_, mut watch) = q.subscribe().await.unwrap();
3860
3861 mock.mock_room_state_encryption().plain().mount().await;
3862 mock.mock_room_send().error_unknown_token(false).mock_once().mount().await;
3863 mock.mock_room_send().ok(event_id!("$42")).mock_once().mount().await;
3864
3865 q.send(RoomMessageEventContent::text_plain("hello").into()).await.unwrap();
3866
3867 assert_let!(Ok(RoomSendQueueUpdate::NewLocalEvent(_)) = watch.recv().await);
3868 assert_let!(
3869 Ok(RoomSendQueueUpdate::SendError { is_recoverable: false, .. }) = watch.recv().await
3870 );
3871
3872 let sender = &client.auth_ctx().session_change_sender;
3875 sender.send(SessionChange::TokensRefreshed).unwrap();
3876 sender.send(SessionChange::UnknownToken(UnknownTokenErrorData::new())).unwrap();
3877
3878 assert_let!(Ok(RoomSendQueueUpdate::RetryEvent { .. }) = watch.recv().await);
3880 assert_let!(Ok(RoomSendQueueUpdate::SentEvent { .. }) = watch.recv().await);
3881 }
3882}