Skip to content

feat(send_queue): report progress for media uploads #5008

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 31 commits into
base: main
Choose a base branch
from

Conversation

Johennes
Copy link
Contributor

@Johennes Johennes commented May 6, 2025

This makes it possible to listen to the media upload progress when using the send queue. The progress is communicated via EventSendState::NotSentYet.

I haven't yet fixed / enhanced the tests or added a changelog because I'm not 100% sure about this approach and would like to get some preliminary feedback first.

  • Public API changes documented in changelogs (optional)


Edit by @Hywan:

@Johennes Johennes marked this pull request as ready for review May 26, 2025 18:40
@Johennes Johennes requested a review from a team as a code owner May 26, 2025 18:40
@Johennes Johennes requested review from Hywan and removed request for a team May 26, 2025 18:40
Copy link
Member

@Hywan Hywan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A really nice PR! I've a bit of feedback, but otherwise we are on the right direction.

We (the Matrix Rust team) were discussing the fact the timeline item is going to be recreated for each transmission progress update. Maybe we want a bit of throttle when broadcasting the transmission progress so that we reduce the amount of data sent to the apps. People at Element will start playing with it inside Element X, let's see how it goes.

Copy link
Member

@bnjbvr bnjbvr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, and nice that we could in theory have this! I've got a few questions about the approach below. We'd like to try it out before validating the overall approach, because of the remark related to memory usage across the FFI, but other than that, it would be a great addition!

@jmartinesp
Copy link
Contributor

jmartinesp commented May 28, 2025

I just tested this on EXA and it seems like there's a memory peak when receiving the upload progress:

Grabacion.de.pantalla.2025-05-28.a.las.9.54.21.mov

Keep in mind though that this may also be caused by the upload process itself.


I tried measuring the allocations now and I got this:

image

EventSendProgress$MediaUpload is the binding version of the SDK record, MediaUploadProgress is what we map it to. I think we have 2x the SDK items because we don't map them in the pinned events timeline, so they're discarded.

The range in the image shows an spike in general allocations, specially in the Java Heap. It's a shame we can't filter out items for the graph and just get the values for these allocations, we have no easy way of knowing whether these allocations come mainly from the EventSendProgress objects and their mapping process or there's something else happening at the same time.

@Hywan
Copy link
Member

Hywan commented May 28, 2025

@jmartinesp Thanks! Can you measure if the memory peak maps to the size of the media, or is it something else?

@jmartinesp
Copy link
Contributor

image

I'd say it looks quite similar without these changes, the image above being a bit more stretched.

@Johennes
Copy link
Contributor Author

@jmartinesp thanks a bunch for taking the time to test the impact of this on Android! ❤️

bnjbvr pushed a commit that referenced this pull request Jun 4, 2025
Addendum to #5125 to
allow sending galleries from the FFI crate. This is the final PR for
galleries (apart from possible enhancements to report the upload status
in #5008).

This is somewhat lengthy again, apologies. Most of the changes are just
wrappers and type mapping, however. So I was hoping that it's relatively
straightforward to review in bulk. Happy to try and elaborate on the
changes or break them up into smaller commits if that helps, however.

---------

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
@bnjbvr bnjbvr self-requested a review June 12, 2025 16:09
Copy link
Member

@bnjbvr bnjbvr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could avoid storing the sizes in persistent storage; after all, we're only interested in the aggregate of the remaining files to upload, so keeping this information in memory ought to be sufficient (plus, all the previously uploaded medias should still be in the event cache store until the media event is sent, at the end of the process). Let's try to make it more stateless instead.

This PR is a bit hard to review; if you introduce renamings, please keep the commit history tidy (no merge commits, only rebase/fixup! commits) and put renamings into individual commits. Also, would be sweet to group changes to the same layer (send queue vs timeline vs FFI) in commits, so as to give me a "direction" where to start from, and where to look at, in the end. You don't have to do it for this very specific PR, but for what it's worth, this is the kind of empathetic changes that usually helps (thus speeds up) reviews :)

I'd like to give this more thought and another look. Also, please add at least one end-to-end test so as to validate the approach :)

Johennes added 7 commits July 7, 2025 19:58
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
…QueueThumbnailInfo struct for easier extension

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
…ting later

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
…of unstable-msc4274 to use it during media progress reporting later

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
…_request

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
…pseudo units

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
…Upload to prepare it for communicating upload progress

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
@Johennes Johennes force-pushed the johannes/send-queue-media-progress branch from 857dcd2 to 934038e Compare July 7, 2025 17:58
Johennes added 2 commits July 8, 2025 12:00
…ate::NotSentYet

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
…ntYet

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
@Johennes Johennes force-pushed the johannes/send-queue-media-progress branch from bc57b71 to 5da557d Compare July 8, 2025 10:00
Copy link

codecov bot commented Jul 8, 2025

Codecov Report

Attention: Patch coverage is 90.21277% with 23 lines in your changes missing coverage. Please review.

Project coverage is 88.80%. Comparing base (18b169c) to head (7cba439).

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
crates/matrix-sdk/src/send_queue/mod.rs 91.34% 17 Missing and 1 partial ⚠️
...rates/matrix-sdk-ui/src/timeline/controller/mod.rs 64.28% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5008      +/-   ##
==========================================
+ Coverage   88.78%   88.80%   +0.01%     
==========================================
  Files         334      334              
  Lines       91236    91414     +178     
  Branches    91236    91414     +178     
==========================================
+ Hits        81007    81178     +171     
- Misses       6400     6406       +6     
- Partials     3829     3830       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@Johennes
Copy link
Contributor Author

Johennes commented Jul 8, 2025

@bnjbvr this is now ready for review again. I've addressed all previous comments other than #5008 (comment). Since I had previously made a mess out of the git history I also rebased everything into 10 self-contained commits. I will use only fix-ups from here on but wanted to try and make the base as easy to review as possible.

Regarding the approach, I went for a mix of using the event cache, info from dependent requests and an in-memory cache for thumbnail sizes.

Copy link
Member

@bnjbvr bnjbvr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks A LOT for breaking this into digestible commits; this made the review so much simpler. Overall this starts to look great, I've got a few non-trivial improvement requests that warrant another look, sorry about this. But we're reaching a nice state now. Really appreciate the test changes too 👏

Usually at this point, I'd try to help with the merging of this PR, by addressing the comments myself, but turns out I'll be away starting today and for a week or so, so I don't have much time to do this. If you're bored with this PR, I'll be happy to address my own comments when I'm back. Alternatively, another reviewer can make sure that my comments have been addressed in the meanwhile (although it'd be a bit of a stretch for them to read all the prior work).

Thanks a bunch, though; excited to get this soon!

queue_thumbnail_info: Some((
FinishUploadThumbnailInfo { txn, width: None, height: None },
thumbnail_media_request,
queue_thumbnail_info: Some(QueueThumbnailInfo {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nice!


/// In-memory mapping of media transaction IDs to thumbnail sizes for the
/// purpose of progress reporting.
thumbnail_size_cache: Arc<Mutex<HashMap<OwnedTransactionId, Vec<Option<usize>>>>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Could we explain in the code comment the oddities about the value:
    1. Why do we need to store multiple thumbnail sizes for a single media transaction? As far as I understand, the transaction id is for the media event (either a regular (single) media event or a gallery event)
    2. Why can it be None? Could we store a Vec<usize> instead, and filter out the cases where there's no thumbnails? (and not even insert into this map, in this case)
  2. Could we use a sync mutex here, aka matrix_sdk_common::locks::Mutex? we might not need the async part, if the lock is only held across sync portions of the code
  3. Out of curiosity, would it make sense to have this in send_queue::StoreLock, which already contains some information about the current request? (in the form of BeingSent)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Fixed in cf51ac9.
  2. Fixed in 97a553f.
  3. I will have to look into this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I addressed 3 with 971dcda and ad963c7. I had to switch back to the async Mutex though because that has lock_owned and I couldn't figure out the lifetimes without it. 🙈

Comment on lines +759 to +760
let client = room.client();
let mut req = client
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: looks like client can be inlined here, since it's only used once?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually thought that, too, but the compiler won't let me. It looks like upload_encrypted_file borrows the client. If I inline room.client(), the client is dropped after this statement but then later needed again in req = req.with_send_progress_observable(progress);.

error[E0716]: temporary value dropped while borrowed
   --> crates/matrix-sdk/src/send_queue/mod.rs:944:39
    |
944 |                         let mut req = room.client()
    |                                       ^^^^^^^^^^^^^ creates a temporary value which is freed while still in use
945 |                             .upload_encrypted_file(&mut cursor)
946 |                             .with_request_config(RequestConfig::short_retry());
    |                                                                               - temporary value is freed at the end of this statement
...
949 |                             req = req.with_send_progress_observable(progress);
    |                                   --- borrow later used here
    |
    = note: consider using a `let` binding to create a longer lived value


/// Determine the size of a pending file upload, if this is a thumbnail
/// upload or return 0 otherwise.
async fn get_dependent_pending_file_upload_size(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, in the worst case, we'll have to take the event cache store lock twice, and the state store lock once, only to retrieve data that was in the stores. Since we already duplicate some of this data in the in-memory cache, we might make more use of this in-memory cache, by preemptively storing all the useful information in there, such that we don't need extra store accesses here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could also store the other needed sizes there, yes. I think we would still need the current look-ups from persisted data though because in the case of galleries, we would otherwise not have anything to report progress for the remaining gallery items?

Comment on lines +297 to +300
// The progress only increases.
if let Some(prev_progress) = prev_progress {
assert!(progress.current >= prev_progress.current);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Johennes added 20 commits July 9, 2025 19:14
…s updates

Control send queue progress reporting via the send queue rather than via the client
…dQueueUpdate::MediaUpload

Enable upload progress via the send queue rather than via the client builder
…tSendState::NotSentYet

Enable upload progress via the send queue rather than via the client builder
…dQueueUpdate::MediaUpload

Correct comments of assert_update macros
…dQueueUpdate::MediaUpload

Split fields of uploaded_with_progress across several lines
…dQueueUpdate::MediaUpload

Clarify that index will always be zero outside of galleries
…tSendState::NotSentYet

Clarify that index will always be zero outside of galleries
…::NotSentYet

Clarify that index will always be zero outside of galleries
…dQueueUpdate::MediaUpload

Also assert the index when asserting upload progress
…dQueueUpdate::MediaUpload

Clarify what progress means for gallery uploads
…tSendState::NotSentYet

Clarify what progress means for gallery uploads
…dQueueUpdate::MediaUpload

Rename uploaded and pending for better clarity
…dQueueUpdate::MediaUpload

Only check if upload progress should be reported before starting uploads
…dQueueUpdate::MediaUpload

Reuse MediaUploadInfo in function parameters
…s reporting later

Expand doc comment for thumbnail_size_cache
…s reporting later

Move thumbnail size cache into StoreLock
…dQueueUpdate::MediaUpload

Read cached thumbnail size from StoreLock
@Johennes
Copy link
Contributor Author

Johennes commented Jul 10, 2025

Usually at this point, I'd try to help with the merging of this PR, by addressing the comments myself, but turns out I'll be away starting today and for a week or so, so I don't have much time to do this. If you're bored with this PR, I'll be happy to address my own comments when I'm back. Alternatively, another reviewer can make sure that my comments have been addressed in the meanwhile (although it'd be a bit of a stretch for them to read all the prior work).

Thanks for the review. 🙏 Letting this sit until you return seems fine from my perspective. I have addressed all comments apart from those I left unresolved.

PS: There was a conflict from a change on main. I wasn't sure how to resolve this but the folks in #matrix-rust-sdk-dev:flipdot.org said a merge should be ok.

@Johennes Johennes requested a review from bnjbvr July 10, 2025 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants