From e9dd3aece20d79256da782142912902c080c83d4 Mon Sep 17 00:00:00 2001 From: YoursFunny Date: Thu, 24 Sep 2026 03:30:30 +0800 Subject: [PATCH] perf(send): write the fallback temp file on a blocking thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit download_to_temp wrote the whole buffered body — up to MAX_MEDIA_UPLOAD_BYTES, 50 MiB — with a sync write_all on whatever executor thread ran the prep task; six PREP slots could stall six runtime threads at once on a slow volume (Docker volume, SD card), pausing unrelated handlers and chat-action refreshes with them. The write now runs in spawn_blocking, the pattern the photo decode in the same file already uses; the failure classes are unchanged (write error = retryable resource exhaustion, panic = permanent, worker bugs must be loud). --- crates/xmedia-bot/src/send/upload.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/xmedia-bot/src/send/upload.rs b/crates/xmedia-bot/src/send/upload.rs index 6dcd96a..62dd1ea 100644 --- a/crates/xmedia-bot/src/send/upload.rs +++ b/crates/xmedia-bot/src/send/upload.rs @@ -123,14 +123,25 @@ async fn download_to_temp( .map_err(|e| FallbackError::Permanent { message: format!("temp file failed: {e}"), })?; - use std::io::Write; - // A write failure is resource exhaustion far more often than a broken temp + // The write runs on a blocking thread: up to 50 MiB of sync disk I/O on + // an executor thread would stall whatever else that worker runs (six prep + // tasks could stall six threads at once on a slow volume). A write + // failure is resource exhaustion far more often than a broken temp // dir (ENOSPC / EDQUOT), and that clears on its own — worth an attempt // instead of dropping the post on the first try. Creating the file (above) // stays permanent: a temp dir that cannot be created at all is a // deployment fault that should fail loudly and immediately. `Retryable` // carries no message, so the cause is logged here. - file.as_file_mut().write_all(&bytes).map_err(|e| { + let (written, file, bytes) = tokio::task::spawn_blocking(move || { + use std::io::Write; + let written = file.as_file_mut().write_all(&bytes); + (written, file, bytes) + }) + .await + .map_err(|e| FallbackError::Permanent { + message: format!("upload write worker panicked: {e}"), + })?; + written.map_err(|e| { log::error!("temp file write failed: {e}"); FallbackError::Retryable { delay_seconds: retry_delay_seconds(0),