Commit 7cf348521204
tarball4 files changed+158-79
@@ -155,9 +155,11 @@ uploads don't belong to a room's actor, so closing the room leaves them be
155155
the forwarders hand their progress to that watcher rather than the stream, so156156
it's the only thing speaking about uploads. anything that ever showed progress157157
or a failure is followed until it gets exactly one `MediaUploadFinished`, and158
-nothing about it goes out after. it remembers what has ended for as long as it159
-runs, since a forwarder can still be sitting on progress for an upload that's160
-already sent and nothing says when that backlog is gone158
+nothing about it goes out after. each forwarder relays its room's sends and159
+cancels behind its progress, so an end never overtakes the ticks before it. an160
+upload ended from anywhere else is remembered until its forwarder's own end161
+turns up, since until then there may be progress still queued for it. the162
+engine stopping ends nothing, `Stopped` covers it161163
162164
## sessions and secrets163165
@@ -219,7 +219,7 @@ struct Queues {
219219
notifications: mpsc::Receiver<Job<NotificationCommand>>,220220
encryption: mpsc::Receiver<Job<EncryptionCommand>>,221221
session_changes: broadcast::Receiver<SessionChange>,222
- upload_progress: mpsc::Receiver<watchers::UploadProgress>,222
+ upload_reports: mpsc::Receiver<watchers::RoomUpload>,223223
}224224
225225
/// requests other devices send us, handed from the sdk's event handler to the encryption worker@@ -245,7 +245,7 @@ pub struct Engine {
245245
queues: Option<Queues>,246246
/// one progress forwarder per room that has uploaded, and whether it's subscribed yet247247
uploads: HashMap<OwnedRoomId, watch::Receiver<bool>>,248
- upload_progress: mpsc::Sender<watchers::UploadProgress>,248
+ upload_reports: mpsc::Sender<watchers::RoomUpload>,249249
/// the client can outlive us, so the handler has to come off it when we stop250250
verification_handler: Option<matrix_sdk::event_handler::EventHandlerHandle>,251251
stop: mpsc::UnboundedSender<Termination>,@@ -290,7 +290,7 @@ impl Engine {
290290
client.send_queue().enable_upload_progress(true);291291
let (note_sender, notes) = mpsc::unbounded_channel();292292
let (stop, stops) = mpsc::unbounded_channel();293
- let (upload_progress, upload_progress_queue) = mpsc::channel(config.event_capacity);293
+ let (upload_reports, upload_report_queue) = mpsc::channel(config.event_capacity);294294
let (mutations, mutation_queue) = mpsc::channel(config.command_capacity);295295
let (notifications, notification_queue) = mpsc::channel(config.command_capacity);296296
let (encryption, encryption_queue) = mpsc::channel(config.command_capacity);@@ -316,9 +316,9 @@ impl Engine {
316316
notifications: notification_queue,317317
encryption: encryption_queue,318318
session_changes,319
- upload_progress: upload_progress_queue,319
+ upload_reports: upload_report_queue,320320
}),321
- upload_progress,321
+ upload_reports,322322
uploads: HashMap::new(),323323
verification_handler: None,324324
stop,@@ -410,7 +410,7 @@ impl Engine {
410410
Box::pin(watchers::uploads(411411
client.clone(),412412
client.send_queue().subscribe(),413
- queues.upload_progress,413
+ queues.upload_reports,414414
events(),415415
)),416416
),@@ -654,7 +654,7 @@ impl Engine {
654654
let (subscribed, ready) = watch::channel(false);655655
self.tasks.spawn(656656
TaskContext::Command("upload watcher"),657
- watchers::room_uploads(room.clone(), self.upload_progress.clone(), subscribed),657
+ watchers::room_uploads(room.clone(), self.upload_reports.clone(), subscribed),658658
);659659
self.uploads660660
.insert(room.room_id().to_owned(), ready.clone());@@ -145,10 +145,12 @@ pub(super) async fn send_queue_errors(
145145
}146146
}147147
148
-/// upload progress only goes out on the room's own channel, never the client-wide one148
+/// upload progress only goes out on the room's own channel, never the client-wide one.149
+/// the room's sends and cancels come along behind it, so an upload's end can't150
+/// overtake its own progress149151
pub(super) async fn room_uploads(150152
room: Room,151
- progress: mpsc::Sender<UploadProgress>,153
+ tracker: mpsc::Sender<RoomUpload>,152154
ready: watch::Sender<bool>,153155
) {154156
let room_id = room.room_id().to_owned();@@ -161,38 +163,49 @@ pub(super) async fn room_uploads(
161163
};162164
ready.send_replace(true);163165
loop {164
- let update = match updates.recv().await {165
- Ok(update) => update,166
- // a missed tick is fine, the next one says where it's got to167
- Err(RecvError::Lagged(_)) => continue,166
+ let report = match updates.recv().await {167
+ Ok(RoomSendQueueUpdate::MediaUpload {168
+ related_to,169
+ file,170
+ progress,171
+ ..172
+ }) => RoomUpload::Progress {173
+ upload: (room_id.clone(), related_to),174
+ uploaded_bytes: progress.current as u64,175
+ total_bytes: progress.total as u64,176
+ complete: file.is_some() && progress.current == progress.total,177
+ },178
+ Ok(RoomSendQueueUpdate::SentEvent {179
+ transaction_id,180
+ event_id,181
+ }) => RoomUpload::Ended(182
+ (room_id.clone(), transaction_id),183
+ UploadOutcome::Sent { event_id },184
+ ),185
+ Ok(RoomSendQueueUpdate::CancelledLocalEvent { transaction_id }) => RoomUpload::Ended(186
+ (room_id.clone(), transaction_id),187
+ UploadOutcome::Cancelled,188
+ ),189
+ Ok(_) => continue,190
+ Err(RecvError::Lagged(_)) => RoomUpload::Lagged,168191
Err(RecvError::Closed) => return,169192
};170
- let RoomSendQueueUpdate::MediaUpload {171
- related_to,172
- file,173
- progress: done,174
- ..175
- } = update176
- else {177
- continue;178
- };179
- let tick = UploadProgress {180
- upload: (room_id.clone(), related_to),181
- uploaded_bytes: done.current as u64,182
- total_bytes: done.total as u64,183
- complete: file.is_some() && done.current == done.total,184
- };185
- if progress.send(tick).await.is_err() {193
+ if tracker.send(report).await.is_err() {186194
return;187195
}188196
}189197
}190198
191
-pub(super) struct UploadProgress {192
- upload: Upload,193
- uploaded_bytes: u64,194
- total_bytes: u64,195
- complete: bool,199
+pub(super) enum RoomUpload {200
+ Progress {201
+ upload: Upload,202
+ uploaded_bytes: u64,203
+ total_bytes: u64,204
+ complete: bool,205
+ },206
+ Ended(Upload, UploadOutcome),207
+ /// an end may have been lost with the ticks208
+ Lagged,196209
}197210
198211
/// the one place upload events leave from, so an upload is never reported after it@@ -201,7 +214,7 @@ pub(super) struct UploadProgress {
201214
pub(super) async fn uploads(202215
client: Client,203216
mut updates: tokio::sync::broadcast::Receiver<SendQueueUpdate>,204
- mut progress: mpsc::Receiver<UploadProgress>,217
+ mut rooms_reports: mpsc::Receiver<RoomUpload>,205218
events: EventSender,206219
) {207220
let (_, mut rooms) = client.rooms_stream();@@ -214,13 +227,15 @@ pub(super) async fn uploads(
214227
update = updates.recv() => match update {215228
Ok(update) => tracker.update(update).into_iter().collect(),216229
Err(RecvError::Lagged(skipped)) => {217
- tracing::warn!(skipped, "upload watcher lagged");218
- let current = attachment_echoes(&client).await;219
- tracker.resnapshot(current)230
+ tracing::warn!(skipped, "upload tracker lagged");231
+ tracker.resnapshot(attachment_echoes(&client).await)220232
}221233
Err(RecvError::Closed) => return,222234
},223
- Some(tick) = progress.recv() => tracker.progress(tick).into_iter().collect(),235
+ Some(report) = rooms_reports.recv() => match report {236
+ RoomUpload::Lagged => tracker.resnapshot(attachment_echoes(&client).await),237
+ report => tracker.room(report).into_iter().collect(),238
+ },224239
update = rooms.next() => {225240
if update.is_none() {226241
return;@@ -241,9 +256,9 @@ pub(super) async fn uploads(
241256
242257
type Upload = (OwnedRoomId, OwnedTransactionId);243258
244
-/// everything reported gets exactly one `MediaUploadFinished`. `ended` is kept for good,245
-/// since a room forwarder can still be holding progress for an upload the client-wide246
-/// channel has already seen sent259
+/// everything reported gets exactly one `MediaUploadFinished`. an upload ended from260
+/// anywhere but its room's forwarder goes in `ended` until the forwarder's own end for261
+/// it arrives, since until then the forwarder can still be holding progress for it247262
struct UploadTracker {248263
live: HashSet<Upload>,249264
ended: HashSet<Upload>,@@ -286,22 +301,36 @@ impl UploadTracker {
286301
}287302
_ => return None,288303
};289
- self.finish((room_id, transaction_id), outcome)304
+ self.finish((room_id, transaction_id), outcome, true)290305
}291306
292
- fn progress(&mut self, tick: UploadProgress) -> Option<Event> {293
- if self.ended.contains(&tick.upload) {294
- return None;307
+ fn room(&mut self, report: RoomUpload) -> Option<Event> {308
+ match report {309
+ RoomUpload::Progress {310
+ upload,311
+ uploaded_bytes,312
+ total_bytes,313
+ complete,314
+ } => {315
+ if self.ended.contains(&upload) {316
+ return None;317
+ }318
+ self.live.insert(upload.clone());319
+ let (room_id, transaction_id) = upload;320
+ Some(Event::MediaUploadProgress {321
+ room_id,322
+ transaction_id,323
+ uploaded_bytes,324
+ total_bytes,325
+ complete,326
+ })327
+ }328
+ RoomUpload::Ended(upload, outcome) => {329
+ self.ended.remove(&upload);330
+ self.finish(upload, outcome, false)331
+ }332
+ RoomUpload::Lagged => None,295333
}296
- self.live.insert(tick.upload.clone());297
- let (room_id, transaction_id) = tick.upload;298
- Some(Event::MediaUploadProgress {299
- room_id,300
- transaction_id,301
- uploaded_bytes: tick.uploaded_bytes,302
- total_bytes: tick.total_bytes,303
- complete: tick.complete,304
- })305334
}306335
307336
fn resnapshot(&mut self, current: HashSet<Upload>) -> Vec<Event> {@@ -325,15 +354,17 @@ impl UploadTracker {
325354
.collect::<Vec<_>>();326355
ending327356
.into_iter()328
- .filter_map(|upload| self.finish(upload, outcome.clone()))357
+ .filter_map(|upload| self.finish(upload, outcome.clone(), true))329358
.collect()330359
}331360
332
- fn finish(&mut self, upload: Upload, outcome: UploadOutcome) -> Option<Event> {361
+ fn finish(&mut self, upload: Upload, outcome: UploadOutcome, remember: bool) -> Option<Event> {333362
if !self.live.remove(&upload) {334363
return None;335364
}336
- self.ended.insert(upload.clone());365
+ if remember {366
+ self.ended.insert(upload.clone());367
+ }337368
let (room_id, transaction_id) = upload;338369
Some(Event::MediaUploadFinished {339370
room_id,@@ -445,27 +476,49 @@ mod tests {
445476
use matrix_sdk::send_queue::{RoomSendQueueUpdate, SendQueueUpdate};446477
use ruma::{OwnedTransactionId, event_id, owned_room_id};447478
448
- use super::{HashSet, UploadProgress, UploadTracker};479
+ use super::{HashSet, RoomUpload, Upload, UploadTracker};449480
use crate::{Event, UploadOutcome};450481
451
- #[test]452
- fn an_upload_only_progress_saw_still_ends_once() {453
- let upload = (482
+ fn upload() -> Upload {483
+ (454484
owned_room_id!("!room:localhost"),455485
OwnedTransactionId::from("upload"),456
- );457
- let tick = || UploadProgress {458
- upload: upload.clone(),486
+ )487
+ }488
+489
+ fn tick() -> RoomUpload {490
+ RoomUpload::Progress {491
+ upload: upload(),459492
uploaded_bytes: 1,460493
total_bytes: 2,461494
complete: false,462
- };463
- let mut tracker = UploadTracker {495
+ }496
+ }497
+498
+ fn sent() -> UploadOutcome {499
+ UploadOutcome::Sent {500
+ event_id: event_id!("$sent:localhost").to_owned(),501
+ }502
+ }503
+504
+ fn tracker() -> UploadTracker {505
+ UploadTracker {464506
live: HashSet::new(),465507
ended: HashSet::new(),466
- };467
- assert!(tracker.progress(tick()).is_some());508
+ }509
+ }510
+511
+ fn finished(event: Option<Event>) -> Option<UploadOutcome> {512
+ match event? {513
+ Event::MediaUploadFinished { outcome, .. } => Some(outcome),514
+ _ => None,515
+ }516
+ }468517
518
+ #[test]519
+ fn a_stale_tick_after_a_missed_end_changes_nothing() {520
+ let mut tracker = tracker();521
+ assert!(tracker.room(tick()).is_some());469522
let missed = tracker.resnapshot(HashSet::new());470523
assert!(matches!(471524
missed.as_slice(),@@ -475,15 +528,37 @@ mod tests {
475528
}]476529
));477530
478
- assert!(tracker.progress(tick()).is_none());479
- let sent = SendQueueUpdate {480
- room_id: upload.0.clone(),531
+ assert!(tracker.room(tick()).is_none());532
+ let (room_id, transaction_id) = upload();533
+ let global = SendQueueUpdate {534
+ room_id,535
+ update: RoomSendQueueUpdate::SentEvent {536
+ transaction_id,537
+ event_id: event_id!("$sent:localhost").to_owned(),538
+ },539
+ };540
+ assert!(tracker.update(global).is_none());541
+ assert!(tracker.room(RoomUpload::Ended(upload(), sent())).is_none());542
+ assert!(tracker.live.is_empty() && tracker.ended.is_empty());543
+ }544
+545
+ #[test]546
+ fn an_end_the_tracker_never_knew_about_still_follows_its_progress() {547
+ let mut tracker = tracker();548
+ let (room_id, transaction_id) = upload();549
+ let global = SendQueueUpdate {550
+ room_id,481551
update: RoomSendQueueUpdate::SentEvent {482
- transaction_id: upload.1.clone(),552
+ transaction_id,483553
event_id: event_id!("$sent:localhost").to_owned(),484554
},485555
};486
- assert!(tracker.update(sent).is_none());487
- assert!(tracker.resnapshot(HashSet::from([upload])).is_empty());556
+ assert!(tracker.update(global).is_none());557
+ assert!(tracker.room(tick()).is_some());558
+ assert_eq!(559
+ finished(tracker.room(RoomUpload::Ended(upload(), sent()))),560
+ Some(sent())561
+ );562
+ assert!(tracker.live.is_empty() && tracker.ended.is_empty());488563
}489564
}@@ -681,7 +681,9 @@ pub enum Event {
681681
kind: crate::ErrorKind,682682
will_retry: bool,683683
},684
- /// every upload ends with exactly one of these684
+ /// an upload that turned up in a progress or failure event gets exactly one of685
+ /// these while the engine runs, and nothing about it after. stopping ends686
+ /// nothing, `Stopped` covers whatever is still going and the sdk resumes it next run685687
MediaUploadFinished {686688
room_id: OwnedRoomId,687689
transaction_id: OwnedTransactionId,