fix: add concurrency semaphore for image processing
Limit concurrent image processing tasks to 4 via a tokio::Semaphore on AppState. Acquired before spawn_blocking in both upload_image() and save_deferred_image() to prevent CPU/memory exhaustion from concurrent image uploads.
This commit is contained in:
parent
3675a2cdc6
commit
dfd9814fa5
2 changed files with 17 additions and 0 deletions
|
|
@ -105,6 +105,12 @@ pub(crate) async fn upload_image(
|
||||||
|
|
||||||
let (upload, _source) = payload.into_parts();
|
let (upload, _source) = payload.into_parts();
|
||||||
|
|
||||||
|
let _permit = state
|
||||||
|
.image_semaphore
|
||||||
|
.acquire()
|
||||||
|
.await
|
||||||
|
.map_err(|_| AppError::unexpected("image processing unavailable"))?;
|
||||||
|
|
||||||
let image_data = upload.image;
|
let image_data = upload.image;
|
||||||
let processed = tokio::task::spawn_blocking(move || process_data_url(&image_data))
|
let processed = tokio::task::spawn_blocking(move || process_data_url(&image_data))
|
||||||
.await
|
.await
|
||||||
|
|
@ -235,6 +241,15 @@ pub(crate) async fn save_deferred_image(
|
||||||
let Some(data_url) = data_url.filter(|s| !s.is_empty()) else {
|
let Some(data_url) = data_url.filter(|s| !s.is_empty()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let Ok(_permit) = state.image_semaphore.acquire().await else {
|
||||||
|
tracing::warn!(
|
||||||
|
entity_type,
|
||||||
|
entity_id,
|
||||||
|
"image semaphore closed, skipping deferred image"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let data_url = data_url.to_string();
|
let data_url = data_url.to_string();
|
||||||
let processed = match tokio::task::spawn_blocking(move || process_data_url(&data_url)).await {
|
let processed = match tokio::task::spawn_blocking(move || process_data_url(&data_url)).await {
|
||||||
Ok(Ok(p)) => p,
|
Ok(Ok(p)) => p,
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ pub struct AppState {
|
||||||
pub cup_service: CupService,
|
pub cup_service: CupService,
|
||||||
pub insecure_cookies: bool,
|
pub insecure_cookies: bool,
|
||||||
pub stats_invalidator: StatsInvalidator,
|
pub stats_invalidator: StatsInvalidator,
|
||||||
|
pub image_semaphore: Arc<tokio::sync::Semaphore>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
|
|
@ -172,6 +173,7 @@ impl AppState {
|
||||||
cup_service,
|
cup_service,
|
||||||
insecure_cookies: config.insecure_cookies,
|
insecure_cookies: config.insecure_cookies,
|
||||||
stats_invalidator: config.stats_invalidator,
|
stats_invalidator: config.stats_invalidator,
|
||||||
|
image_semaphore: Arc::new(tokio::sync::Semaphore::new(4)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue