From 053ae0ec25e31a0c64ed5cdabc82301e6b554c3f Mon Sep 17 00:00:00 2001 From: YoursFunny Date: Thu, 24 Sep 2026 15:29:17 +0800 Subject: [PATCH] fix(send): charge the photo download window to the memory budget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A non-photo body charged the process-wide budget for as long as download_to_temp held it; a photo charged nothing until its prepare step, so the download itself — up to MAX_PHOTO_DOWNLOAD_BYTES per item, six items per batch, eight prep slots process-wide — was memory the MEMORY_BUDGET comment promised was bounded but was not (~6x32 MiB on top of the accounted512 MiB). Photos now reserve their own download cap for that window; the prepare step still charges header probe + decode buffer, the only overlap that is actually held in memory together. Net accounting stays within the declared budget instead of exceeding it whenever a batch of large photos downloads at once (audit: upload.rs photo window uncharged). --- crates/xmedia-bot/src/send/upload.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/crates/xmedia-bot/src/send/upload.rs b/crates/xmedia-bot/src/send/upload.rs index fe74bbd..0f88d02 100644 --- a/crates/xmedia-bot/src/send/upload.rs +++ b/crates/xmedia-bot/src/send/upload.rs @@ -112,16 +112,20 @@ async fn download_to_temp( } else { MAX_MEDIA_UPLOAD_BYTES }; - // A non-photo body is buffered whole and can now be 50 MB, so it charges - // the process-wide budget for as long as this function holds it (one - // 64 MiB unit covers the cap): `PREP_SLOTS` bounds how many are in flight, - // this bounds what they add up to. Photos charge their real buffer after - // the download, once their header predicts it. - let _budget = if is_photo { - None - } else { - Some(photo::reserve_memory(MAX_MEDIA_UPLOAD_BYTES).await) - }; + // A non-photo body is buffered whole and charges the process-wide budget + // for as long as this function holds it (one 64 MiB unit covers the cap): + // `PREP_SLOTS` bounds how many are in flight, this bounds what they add + // up to. Photos charge their own download cap for the same window — their + // real cost (header probe + decode buffer) is charged again by the + // prepare step right after, where both are actually held together. + let _budget = Some( + photo::reserve_memory(if is_photo { + photo::MAX_PHOTO_DOWNLOAD_BYTES + } else { + MAX_MEDIA_UPLOAD_BYTES + }) + .await, + ); let bytes = match x_media::site::download_media_limited( media_url, limit,