Commit 411bc1c78b65
tarball4 files changed+157-227
@@ -188,23 +188,20 @@ memory, the very thing the host streams to avoid. `media_source` hands over the
188188
url and keys `media` would have used, picked the same way, and `access_token`189189
the rest. the byte cap doesn't apply, nothing is buffered190190
191
-uploads are commands that answer once the send queue has them. progress only192
-goes out on a room's own send queue channel, so each room that uploads gets a193
-forwarder, subscribed before the upload is queued and restarted for uploads the194
-sdk resumes from last time. everything but progress reaches the client-wide195
-channel, so one watcher there follows every attachment in the queue and ends196
-each with one `MediaUploadFinished`. sent, cancelled, its room forgotten, or191
+uploads are commands that answer once the send queue has them. the fork puts192
+upload progress on the client-wide send queue channel too, which upstream keeps193
+for each room's own, so one watcher there follows every attachment in the queue,194
+leftovers the sdk resumes from last time included, and ends each with one195
+`MediaUploadFinished`. sent, cancelled, its room forgotten, or197196
missed when that channel overflowed at the wrong moment, since then there's no198197
knowing which. a failure isn't an ending, a wedged upload can still be retried.199198
uploads don't belong to a room's actor, so closing the room leaves them be200199
201
-the forwarders hand their progress to that watcher rather than the stream, so202
-it's the only thing speaking about uploads. anything that ever showed progress203
-or a failure is followed until it gets exactly one `MediaUploadFinished`, and204
-nothing about it goes out after. each forwarder relays its room's sends and205
-cancels behind its progress, so an end never overtakes the ticks before it. an206
-upload ended from anywhere else is remembered until its forwarder's own end207
-turns up, since until then there may be progress still queued for it. the200
+that watcher is the only thing speaking about uploads. anything that ever201
+showed progress or a failure is followed until it gets exactly one202
+`MediaUploadFinished`, and nothing about it goes out after. the sdk sends203
+progress from a detached task, so a tick can turn up behind its own upload's204
+end, and the watcher remembers the last few hundred ends to drop those. the208205
engine stopping ends nothing, `Stopped` covers it209206
210207
## sessions and secrets@@ -390,6 +387,8 @@ which keep their public sans-io apis
390387
sdk's hard-coded list leaves out. image packs use it391388
- msc4027 reaction shortcodes, kept on the timeline's `ReactionInfo` and sent392389
with `Timeline::toggle_reaction_with_shortcode`390
+- `SendQueue::enable_global_upload_progress`, upload progress on the391
+ client-wide send queue channel393392
394393
## public model395394
@@ -246,7 +246,6 @@ struct Queues {
246246
encryption: mpsc::Receiver<Job<EncryptionCommand>>,247247
session_changes: broadcast::Receiver<SessionChange>,248248
persistence: Option<Persistence>,249
- upload_reports: mpsc::Receiver<watchers::RoomUpload>,250249
}251250
252251
/// requests other devices send us, handed from the sdk's event handler to the encryption worker@@ -270,9 +269,6 @@ pub struct Engine {
270269
notifications: mpsc::Sender<Job<NotificationCommand>>,271270
encryption: mpsc::Sender<Job<EncryptionCommand>>,272271
queues: Option<Queues>,273
- /// one progress forwarder per room that has uploaded, and whether it's subscribed yet274
- uploads: HashMap<OwnedRoomId, watch::Receiver<bool>>,275
- upload_reports: mpsc::Sender<watchers::RoomUpload>,276272
/// the client can outlive us, so the handler has to come off it when we stop277273
verification_handler: Option<matrix_sdk::event_handler::EventHandlerHandle>,278274
stop: mpsc::UnboundedSender<Termination>,@@ -334,9 +330,9 @@ impl Engine {
334330
config.event_capacity,335331
);336332
client.send_queue().enable_upload_progress(true);333
+ client.send_queue().enable_global_upload_progress(true);337334
let (note_sender, notes) = mpsc::unbounded_channel();338335
let (stop, stops) = mpsc::unbounded_channel();339
- let (upload_reports, upload_report_queue) = mpsc::channel(config.event_capacity);340336
let (mutations, mutation_queue) = mpsc::channel(config.command_capacity);341337
let (notifications, notification_queue) = mpsc::channel(config.command_capacity);342338
let (encryption, encryption_queue) = mpsc::channel(config.command_capacity);@@ -363,10 +359,7 @@ impl Engine {
363359
encryption: encryption_queue,364360
session_changes,365361
persistence,366
- upload_reports: upload_report_queue,367362
}),368
- upload_reports,369
- uploads: HashMap::new(),370363
verification_handler: None,371364
preparations: prepare::Preparations::default(),372365
stop,@@ -403,7 +396,6 @@ impl Engine {
403396
}404397
}405398
self.start_tasks();406
- self.resume_upload_forwarders().await;407399
self.sync.start().await;408400
409401
let mut shutdown_reply = None;@@ -470,7 +462,6 @@ impl Engine {
470462
Box::pin(watchers::uploads(471463
client.clone(),472464
client.send_queue().subscribe(),473
- queues.upload_reports,474465
events(),475466
)),476467
),@@ -740,45 +731,12 @@ impl Engine {
740731
let Some(room) = self.client.get_room(upload.timeline_id.room_id()) else {741732
return reply.done(Err(Error::room_unknown()));742733
};743
- let mut ready = self.upload_forwarder(&room);744734
let max_bytes = self.config.max_media_bytes;745
- // queueing before the forwarder listens would lose the first progress ticks746735
self.spawn_command("upload", reply, async move {747
- ready748
- .wait_for(|subscribed| *subscribed)749
- .await750
- .map_err(|_| Error::new(ErrorKind::Store, "reading the media send queue failed"))?;751736
media::queue_upload(room, upload, max_bytes).await752737
});753738
}754739
755
- fn upload_forwarder(&mut self, room: &matrix_sdk::Room) -> watch::Receiver<bool> {756
- self.uploads.retain(|_, ready| ready.has_changed().is_ok());757
- if let Some(ready) = self.uploads.get(room.room_id()) {758
- return ready.clone();759
- }760
- let (subscribed, ready) = watch::channel(false);761
- self.tasks.spawn(762
- TaskContext::Command("upload watcher"),763
- watchers::room_uploads(room.clone(), self.upload_reports.clone(), subscribed),764
- );765
- self.uploads766
- .insert(room.room_id().to_owned(), ready.clone());767
- ready768
- }769
-770
- /// the sdk resumes uploads left over from last time, and they deserve progress too771
- async fn resume_upload_forwarders(&mut self) {772
- let Ok(echoes) = self.client.send_queue().local_echoes().await else {773
- return;774
- };775
- for room_id in echoes.keys() {776
- if let Some(room) = self.client.get_room(room_id) {777
- self.upload_forwarder(&room);778
- }779
- }780
- }781
-782740
fn route_timeline(&mut self, command: TimelineCommand, reply: Responder) {783741
let sender = match self.rooms.get(command.room_id()) {784742
Some(RoomSlot::Active { sender, .. }) => Some(sender.clone()),@@ -948,7 +906,6 @@ impl Engine {
948906
self.port.close();949907
self.tasks.shutdown().await;950908
self.rooms.clear();951
- self.uploads.clear();952909
self.media.clear().await;953910
self.sync.stop().await;954911
self.port@@ -1,8 +1,12 @@
1
-use std::{collections::HashSet, sync::Arc, time::Duration};1
+use std::{2
+ collections::{HashSet, VecDeque},3
+ sync::Arc,4
+ time::Duration,5
+};26
37
use futures_util::StreamExt;48
use matrix_sdk::{5
- Client, Room,9
+ Client,610
ruma::events::{AnyMessageLikeEventContent, room::message::MessageType},711
send_queue::{LocalEchoContent, RoomSendQueueUpdate, SendQueueUpdate},812
};@@ -14,7 +18,7 @@ use ruma::{
1418
serde::Raw,1519
time::Instant,1620
};17
-use tokio::sync::{Notify, broadcast::error::RecvError, mpsc, watch};21
+use tokio::sync::{Notify, broadcast::error::RecvError, watch};1822
1923
use super::executor::TaskSet;2024
use crate::{@@ -122,84 +126,20 @@ pub(super) async fn send_queue_errors(
122126
}123127
}124128
125
-/// upload progress only goes out on the room's own channel, never the client-wide one.126
-/// the room's sends and cancels come along behind it, so an upload's end can't127
-/// overtake its own progress128
-pub(super) async fn room_uploads(129
- room: Room,130
- tracker: mpsc::Sender<RoomUpload>,131
- ready: watch::Sender<bool>,132
-) {133
- let room_id = room.room_id().to_owned();134
- let mut updates = match room.send_queue().subscribe().await {135
- Ok((_, updates)) => updates,136
- Err(error) => {137
- tracing::warn!(%room_id, %error, "reading the media send queue failed");138
- return;139
- }140
- };141
- ready.send_replace(true);142
- loop {143
- let report = match updates.recv().await {144
- Ok(RoomSendQueueUpdate::MediaUpload {145
- related_to,146
- file,147
- progress,148
- ..149
- }) => RoomUpload::Progress {150
- upload: (room_id.clone(), related_to),151
- uploaded_bytes: progress.current as u64,152
- total_bytes: progress.total as u64,153
- complete: file.is_some() && progress.current == progress.total,154
- },155
- Ok(RoomSendQueueUpdate::SentEvent {156
- transaction_id,157
- event_id,158
- }) => RoomUpload::Ended(159
- (room_id.clone(), transaction_id),160
- UploadOutcome::Sent { event_id },161
- ),162
- Ok(RoomSendQueueUpdate::CancelledLocalEvent { transaction_id }) => {163
- RoomUpload::Ended((room_id.clone(), transaction_id), UploadOutcome::Cancelled)164
- }165
- Ok(_) => continue,166
- Err(RecvError::Lagged(_)) => RoomUpload::Lagged,167
- Err(RecvError::Closed) => return,168
- };169
- if tracker.send(report).await.is_err() {170
- return;171
- }172
- }173
-}174
-175
-pub(super) enum RoomUpload {176
- Progress {177
- upload: Upload,178
- uploaded_bytes: u64,179
- total_bytes: u64,180
- complete: bool,181
- },182
- Ended(Upload, UploadOutcome),183
- /// an end may have been lost with the ticks184
- Lagged,185
-}186
-187129
/// the one place upload events leave from, so an upload is never reported after it188
-/// ended. progress comes in from the room forwarders, the rest from the client-wide189
-/// channel, which exists before any upload does130
+/// ended. the client-wide channel exists before any upload does. it holds 32 updates and131
+/// progress ticks share it, so this keeps draining while the event stream is full190132
pub(super) async fn uploads(191133
client: Client,192134
mut updates: tokio::sync::broadcast::Receiver<SendQueueUpdate>,193
- mut rooms_reports: mpsc::Receiver<RoomUpload>,194135
events: EventSender,195136
) {196137
let (_, mut rooms) = client.rooms_stream();197
- let mut tracker = UploadTracker {198
- live: attachment_echoes(&client).await,199
- ended: HashSet::new(),200
- };138
+ let mut tracker = UploadTracker::new(attachment_echoes(&client).await);139
+ let mut pending = VecDeque::new();201140
loop {202
- let reports = tokio::select! {141
+ let reports: Vec<Event> = tokio::select! {142
+ biased;203143
update = updates.recv() => match update {204144
Ok(update) => tracker.update(update).into_iter().collect(),205145
Err(RecvError::Lagged(skipped)) => {@@ -208,10 +148,6 @@ pub(super) async fn uploads(
208148
}209149
Err(RecvError::Closed) => return,210150
},211
- Some(report) = rooms_reports.recv() => match report {212
- RoomUpload::Lagged => tracker.resnapshot(attachment_echoes(&client).await),213
- report => tracker.room(report).into_iter().collect(),214
- },215151
update = rooms.next() => {216152
if update.is_none() {217153
return;@@ -221,26 +157,77 @@ pub(super) async fn uploads(
221157
&UploadOutcome::RoomForgotten,222158
)223159
}160
+ permit = events.reserve(), if !pending.is_empty() => {161
+ let Ok(permit) = permit else { return };162
+ permit.send(pending.pop_front().expect("pending was checked"));163
+ continue;164
+ }224165
};225166
for event in reports {226
- if events.send(event).await.is_err() {227
- return;228
- }167
+ queue_upload_event(&mut pending, event);168
+ }169
+ }170
+}171
+172
+/// a progress tick replaces a waiting one for the same upload, unless something else about173
+/// that upload is queued after it174
+fn queue_upload_event(pending: &mut VecDeque<Event>, event: Event) {175
+ if matches!(event, Event::MediaUploadProgress { .. })176
+ && let Some(waiting) = pending177
+ .iter_mut()178
+ .rev()179
+ .find(|waiting| upload_of(waiting) == upload_of(&event))180
+ && matches!(waiting, Event::MediaUploadProgress { .. })181
+ {182
+ *waiting = event;183
+ return;184
+ }185
+ pending.push_back(event);186
+}187
+188
+fn upload_of(event: &Event) -> Option<(&OwnedRoomId, &OwnedTransactionId)> {189
+ match event {190
+ Event::MediaUploadProgress {191
+ room_id,192
+ transaction_id,193
+ ..194
+ }195
+ | Event::MediaUploadFailed {196
+ room_id,197
+ transaction_id,198
+ ..229199
}200
+ | Event::MediaUploadFinished {201
+ room_id,202
+ transaction_id,203
+ ..204
+ } => Some((room_id, transaction_id)),205
+ _ => None,230206
}231207
}232208
233209
type Upload = (OwnedRoomId, OwnedTransactionId);234210
235
-/// everything reported gets exactly one `MediaUploadFinished`. an upload ended from236
-/// anywhere but its room's forwarder goes in `ended` until the forwarder's own end for237
-/// it arrives, since until then the forwarder can still be holding progress for it211
+/// the sdk sends progress ticks from a detached task, so a tick can land after its212
+/// upload's end. it trails by one task hop, so a short memory of ends covers it213
+const REMEMBERED_ENDS: usize = 256;214
+215
+/// everything reported gets exactly one `MediaUploadFinished` and nothing after it238216
struct UploadTracker {239217
live: HashSet<Upload>,240218
ended: HashSet<Upload>,219
+ ended_order: VecDeque<Upload>,241220
}242221
243222
impl UploadTracker {223
+ fn new(live: HashSet<Upload>) -> Self {224
+ Self {225
+ live,226
+ ended: HashSet::new(),227
+ ended_order: VecDeque::new(),228
+ }229
+ }230
+244231
fn update(&mut self, update: SendQueueUpdate) -> Option<Event> {245232
let room_id = update.room_id;246233
let (transaction_id, outcome) = match update.update {@@ -251,6 +238,26 @@ impl UploadTracker {
251238
}252239
return None;253240
}241
+ RoomSendQueueUpdate::MediaUpload {242
+ related_to,243
+ file,244
+ progress,245
+ ..246
+ } => {247
+ let upload = (room_id, related_to);248
+ if self.ended.contains(&upload) {249
+ return None;250
+ }251
+ self.live.insert(upload.clone());252
+ let (room_id, transaction_id) = upload;253
+ return Some(Event::MediaUploadProgress {254
+ room_id,255
+ transaction_id,256
+ uploaded_bytes: progress.current as u64,257
+ total_bytes: progress.total as u64,258
+ complete: file.is_some() && progress.current == progress.total,259
+ });260
+ }254261
RoomSendQueueUpdate::SendError {255262
transaction_id,256263
error,@@ -277,36 +284,7 @@ impl UploadTracker {
277284
}278285
_ => return None,279286
};280
- self.finish((room_id, transaction_id), outcome, true)281
- }282
-283
- fn room(&mut self, report: RoomUpload) -> Option<Event> {284
- match report {285
- RoomUpload::Progress {286
- upload,287
- uploaded_bytes,288
- total_bytes,289
- complete,290
- } => {291
- if self.ended.contains(&upload) {292
- return None;293
- }294
- self.live.insert(upload.clone());295
- let (room_id, transaction_id) = upload;296
- Some(Event::MediaUploadProgress {297
- room_id,298
- transaction_id,299
- uploaded_bytes,300
- total_bytes,301
- complete,302
- })303
- }304
- RoomUpload::Ended(upload, outcome) => {305
- self.ended.remove(&upload);306
- self.finish(upload, outcome, false)307
- }308
- RoomUpload::Lagged => None,309
- }287
+ self.finish((room_id, transaction_id), outcome)310288
}311289
312290
fn resnapshot(&mut self, current: HashSet<Upload>) -> Vec<Event> {@@ -330,17 +308,23 @@ impl UploadTracker {
330308
.collect::<Vec<_>>();331309
ending332310
.into_iter()333
- .filter_map(|upload| self.finish(upload, outcome.clone(), true))311
+ .filter_map(|upload| self.finish(upload, outcome.clone()))334312
.collect()335313
}336314
337
- fn finish(&mut self, upload: Upload, outcome: UploadOutcome, remember: bool) -> Option<Event> {315
+ /// an end for an upload we never saw is remembered too, its ticks may still be coming316
+ fn finish(&mut self, upload: Upload, outcome: UploadOutcome) -> Option<Event> {317
+ if self.ended.insert(upload.clone()) {318
+ self.ended_order.push_back(upload.clone());319
+ if self.ended_order.len() > REMEMBERED_ENDS320
+ && let Some(oldest) = self.ended_order.pop_front()321
+ {322
+ self.ended.remove(&oldest);323
+ }324
+ }338325
if !self.live.remove(&upload) {339326
return None;340327
}341
- if remember {342
- self.ended.insert(upload.clone());343
- }344328
let (room_id, transaction_id) = upload;345329
Some(Event::MediaUploadFinished {346330
room_id,@@ -504,10 +488,10 @@ pub(super) async fn notification_settings(client: Client, events: EventSender) {
504488
505489
#[cfg(test)]506490
mod tests {507
- use matrix_sdk::send_queue::{RoomSendQueueUpdate, SendQueueUpdate};491
+ use matrix_sdk::send_queue::{AbstractProgress, RoomSendQueueUpdate, SendQueueUpdate};508492
use ruma::{OwnedTransactionId, event_id, owned_room_id};509493
510
- use super::{HashSet, RoomUpload, Upload, UploadTracker};494
+ use super::{HashSet, Upload, UploadTracker};511495
use crate::{Event, UploadOutcome};512496
513497
fn upload() -> Upload {@@ -517,26 +501,30 @@ mod tests {
517501
)518502
}519503
520
- fn tick() -> RoomUpload {521
- RoomUpload::Progress {522
- upload: upload(),523
- uploaded_bytes: 1,524
- total_bytes: 2,525
- complete: false,504
+ fn update(update: RoomSendQueueUpdate) -> SendQueueUpdate {505
+ SendQueueUpdate {506
+ room_id: upload().0,507
+ update,526508
}527509
}528510
529
- fn sent() -> UploadOutcome {530
- UploadOutcome::Sent {531
- event_id: event_id!("$sent:localhost").to_owned(),532
- }511
+ fn tick() -> SendQueueUpdate {512
+ update(RoomSendQueueUpdate::MediaUpload {513
+ related_to: upload().1,514
+ file: None,515
+ index: 0,516
+ progress: AbstractProgress {517
+ current: 1,518
+ total: 2,519
+ },520
+ })533521
}534522
535
- fn tracker() -> UploadTracker {536
- UploadTracker {537
- live: HashSet::new(),538
- ended: HashSet::new(),539
- }523
+ fn sent() -> SendQueueUpdate {524
+ update(RoomSendQueueUpdate::SentEvent {525
+ transaction_id: upload().1,526
+ event_id: event_id!("$sent:localhost").to_owned(),527
+ })540528
}541529
542530
fn finished(event: Option<Event>) -> Option<UploadOutcome> {@@ -547,9 +535,21 @@ mod tests {
547535
}548536
549537
#[test]538
+ fn a_tick_trailing_its_end_says_nothing() {539
+ let mut tracker = UploadTracker::new(HashSet::new());540
+ assert!(tracker.update(tick()).is_some());541
+ assert!(matches!(542
+ finished(tracker.update(sent())),543
+ Some(UploadOutcome::Sent { .. })544
+ ));545
+ assert!(tracker.update(tick()).is_none());546
+ assert!(tracker.live.is_empty());547
+ }548
+549
+ #[test]550550
fn a_stale_tick_after_a_missed_end_changes_nothing() {551
- let mut tracker = tracker();552
- assert!(tracker.room(tick()).is_some());551
+ let mut tracker = UploadTracker::new(HashSet::new());552
+ assert!(tracker.update(tick()).is_some());553553
let missed = tracker.resnapshot(HashSet::new());554554
assert!(matches!(555555
missed.as_slice(),@@ -558,38 +558,8 @@ mod tests {
558558
..559559
}]560560
));561
-562
- assert!(tracker.room(tick()).is_none());563
- let (room_id, transaction_id) = upload();564
- let global = SendQueueUpdate {565
- room_id,566
- update: RoomSendQueueUpdate::SentEvent {567
- transaction_id,568
- event_id: event_id!("$sent:localhost").to_owned(),569
- },570
- };571
- assert!(tracker.update(global).is_none());572
- assert!(tracker.room(RoomUpload::Ended(upload(), sent())).is_none());573
- assert!(tracker.live.is_empty() && tracker.ended.is_empty());574
- }575
-576
- #[test]577
- fn an_end_the_tracker_never_knew_about_still_follows_its_progress() {578
- let mut tracker = tracker();579
- let (room_id, transaction_id) = upload();580
- let global = SendQueueUpdate {581
- room_id,582
- update: RoomSendQueueUpdate::SentEvent {583
- transaction_id,584
- event_id: event_id!("$sent:localhost").to_owned(),585
- },586
- };587
- assert!(tracker.update(global).is_none());588
- assert!(tracker.room(tick()).is_some());589
- assert_eq!(590
- finished(tracker.room(RoomUpload::Ended(upload(), sent()))),591
- Some(sent())592
- );593
- assert!(tracker.live.is_empty() && tracker.ended.is_empty());561
+ assert!(tracker.update(tick()).is_none());562
+ assert!(tracker.update(sent()).is_none());563
+ assert!(tracker.live.is_empty());594564
}595565
}@@ -992,6 +992,10 @@ impl EventSender {
992992
self.0.send(event).await.map_err(|_| SendEventError)993993
}994994
995
+ pub(crate) async fn reserve(&self) -> Result<mpsc::Permit<'_, Event>, SendEventError> {996
+ self.0.reserve().await.map_err(|_| SendEventError)997
+ }998
+995999
/// resolves once every `EventStream` is gone9961000
pub(crate) async fn closed(&self) {9971001
self.0.closed().await;