Skip to content

Commit f23af52

Browse files
committed
perf: speed up disk download path with configurable buffering
Use config-driven buffered copy on disk downloads and allow unknown content-length responses so chunked transfers can stream instead of failing early.
1 parent b8169e1 commit f23af52

File tree

1 file changed

+41
-7
lines changed

1 file changed

+41
-7
lines changed

src/bot.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub struct UploadCounters {
6262

6363
const SPEED_SAMPLE_WINDOW: usize = 20;
6464
const STATUS_RESOURCE_REFRESH_INTERVAL: std::time::Duration = std::time::Duration::from_secs(2);
65+
const MIN_DOWNLOAD_CHUNK_BYTES: usize = 64 * 1024;
6566

6667
#[derive(Debug)]
6768
pub struct RuntimeMetrics {
@@ -1220,31 +1221,38 @@ async fn download_and_send_music(
12201221
return Err(anyhow::anyhow!("HTTP {}", response.status()));
12211222
}
12221223

1223-
// Check content length
1224-
let content_length = response.content_length().unwrap_or(0);
1225-
if content_length == 0 {
1224+
// Check content length. None means unknown/chunked transfer and is allowed.
1225+
let content_length = response.content_length();
1226+
if content_length == Some(0) {
12261227
return Err(anyhow::anyhow!("Empty file or unable to get file size"));
12271228
}
12281229

12291230
// Create audio buffer based on storage mode configuration
12301231
let mut audio_buffer = AudioBuffer::new(
12311232
&state.config,
1232-
content_length,
1233+
content_length.unwrap_or(0),
12331234
filename.clone(),
12341235
&state.config.cache_dir,
12351236
)
12361237
.await?;
12371238

12381239
let mut stream = response.bytes_stream();
12391240
let downloaded = if audio_buffer.is_disk() {
1241+
let chunk_bytes = download_chunk_bytes(&state.config);
12401242
let stream = stream.map_err(std::io::Error::other);
1241-
let mut reader = StreamReader::new(stream);
1243+
let mut reader =
1244+
tokio::io::BufReader::with_capacity(chunk_bytes, StreamReader::new(stream));
12421245
let file = audio_buffer
12431246
.disk_file_mut()
12441247
.ok_or_else(|| anyhow::anyhow!("Disk buffer missing file handle"))?;
1245-
tokio::io::copy(&mut reader, file)
1248+
let mut writer = tokio::io::BufWriter::with_capacity(chunk_bytes, file);
1249+
let downloaded = tokio::io::copy_buf(&mut reader, &mut writer)
12461250
.await
1247-
.context("Failed to stream download to disk")?
1251+
.context("Failed to stream download to disk")?;
1252+
tokio::io::AsyncWriteExt::flush(&mut writer)
1253+
.await
1254+
.context("Failed to flush disk writer")?;
1255+
downloaded
12481256
} else {
12491257
let mut downloaded = 0u64;
12501258
while let Some(chunk) = stream.next().await {
@@ -1928,6 +1936,13 @@ fn should_set_upload_pool_idle_timeout(secs: u64) -> bool {
19281936
secs > 0
19291937
}
19301938

1939+
fn download_chunk_bytes(config: &Config) -> usize {
1940+
config
1941+
.download_chunk_size_kb
1942+
.saturating_mul(1024)
1943+
.max(MIN_DOWNLOAD_CHUNK_BYTES)
1944+
}
1945+
19311946
fn append_search_result_line(results: &mut String, index: usize, song_name: &str, artists: &str) {
19321947
use std::fmt::Write;
19331948

@@ -2854,6 +2869,25 @@ mod tests {
28542869
assert!(super::should_set_upload_pool_idle_timeout(60));
28552870
}
28562871

2872+
#[test]
2873+
fn download_chunk_bytes_uses_configured_kib() {
2874+
let mut config = Config::default();
2875+
config.download_chunk_size_kb = 512;
2876+
2877+
assert_eq!(super::download_chunk_bytes(&config), 512 * 1024);
2878+
}
2879+
2880+
#[test]
2881+
fn download_chunk_bytes_clamps_zero_to_minimum() {
2882+
let mut config = Config::default();
2883+
config.download_chunk_size_kb = 0;
2884+
2885+
assert_eq!(
2886+
super::download_chunk_bytes(&config),
2887+
super::MIN_DOWNLOAD_CHUNK_BYTES
2888+
);
2889+
}
2890+
28572891
#[test]
28582892
fn upload_log_level_allows_thresholds() {
28592893
assert!(UploadLogLevel::Info.allows(UploadLogLevel::Error));

0 commit comments

Comments
 (0)