2
0
Fork 0
mirror of https://git.asonix.dog/asonix/pict-rs synced 2024-12-22 11:21:24 +00:00

Timeout all operations while holding semaphore

This ensures that the semaphore is always guaranteed to be released,
even if a processing operation stalls
This commit is contained in:
asonix 2024-12-09 19:16:29 -06:00
parent 8a551fa65d
commit ac9a3773d1
2 changed files with 79 additions and 30 deletions

View file

@ -2,6 +2,7 @@ mod ffmpeg;
mod magick; mod magick;
use crate::{ use crate::{
bytes_stream::BytesStream,
details::Details, details::Details,
error::{Error, UploadError}, error::{Error, UploadError},
formats::{ImageFormat, InputProcessableFormat, InternalVideoFormat, ProcessableFormat}, formats::{ImageFormat, InputProcessableFormat, InternalVideoFormat, ProcessableFormat},
@ -196,33 +197,16 @@ async fn process<S: Store + 'static>(
let guard = MetricsGuard::guard(); let guard = MetricsGuard::guard();
let permit = crate::process_semaphore().acquire().await?; let permit = crate::process_semaphore().acquire().await?;
let identifier = input_identifier(state, output_format, hash.clone(), original_details).await?; let bytes = do_process(
state,
let input_details = crate::ensure_details_identifier(state, &identifier).await?; output_format,
variant_args,
let input_format = input_details hash.clone(),
.internal_format() original_details,
.processable_format() )
.expect("Already verified format is processable"); .with_timeout(Duration::from_secs(state.config.media.process_timeout * 4))
.await
let format = input_format.process_to(output_format); .map_err(|_| UploadError::ProcessTimeout)??;
let quality = match format {
ProcessableFormat::Image(format) => state.config.media.image.quality_for(format),
ProcessableFormat::Animation(format) => state.config.media.animation.quality_for(format),
};
let stream = state.store.to_stream(&identifier, None, None).await?;
let bytes =
crate::magick::process_image_command(state, variant_args, input_format, format, quality)
.await?
.drive_with_stream(stream)
.into_bytes_stream()
.instrument(tracing::info_span!(
"Reading processed image to BytesStream"
))
.await?;
drop(permit); drop(permit);
@ -258,6 +242,47 @@ async fn process<S: Store + 'static>(
Ok((details, identifier)) as Result<(Details, Arc<str>), Error> Ok((details, identifier)) as Result<(Details, Arc<str>), Error>
} }
async fn do_process<S>(
state: &State<S>,
output_format: InputProcessableFormat,
variant_args: Vec<String>,
hash: Hash,
original_details: &Details,
) -> Result<BytesStream, Error>
where
S: Store + 'static,
{
let identifier = input_identifier(state, output_format, hash.clone(), original_details).await?;
let input_details = crate::ensure_details_identifier(state, &identifier).await?;
let input_format = input_details
.internal_format()
.processable_format()
.expect("Already verified format is processable");
let format = input_format.process_to(output_format);
let quality = match format {
ProcessableFormat::Image(format) => state.config.media.image.quality_for(format),
ProcessableFormat::Animation(format) => state.config.media.animation.quality_for(format),
};
let stream = state.store.to_stream(&identifier, None, None).await?;
let bytes =
crate::magick::process_image_command(state, variant_args, input_format, format, quality)
.await?
.drive_with_stream(stream)
.into_bytes_stream()
.instrument(tracing::info_span!(
"Reading processed image to BytesStream"
))
.await?;
Ok(bytes)
}
pub(crate) async fn ensure_motion_identifier<S>( pub(crate) async fn ensure_motion_identifier<S>(
state: &State<S>, state: &State<S>,
hash: Hash, hash: Hash,

View file

@ -8,7 +8,7 @@ use crate::{
details::Details, details::Details,
error::{Error, UploadError}, error::{Error, UploadError},
formats::InternalFormat, formats::InternalFormat,
future::{WithMetrics, WithPollTimer}, future::{WithMetrics, WithPollTimer, WithTimeout},
repo::{Alias, ArcRepo, DeleteToken, Hash}, repo::{Alias, ArcRepo, DeleteToken, Hash},
state::State, state::State,
store::Store, store::Store,
@ -58,6 +58,32 @@ where
let permit = crate::process_semaphore().acquire().await?; let permit = crate::process_semaphore().acquire().await?;
let res = process_ingest_bytestream(state, bytes, upload_query)
.with_timeout(Duration::from_secs(state.config.media.process_timeout * 4))
.await
.map_err(|_| UploadError::ProcessTimeout)?;
drop(permit);
res
}
async fn process_ingest_bytestream<S>(
state: &State<S>,
bytes: BytesStream,
upload_query: &UploadQuery,
) -> Result<
(
InternalFormat,
Arc<str>,
Details,
Rc<RefCell<hasher::State>>,
),
Error,
>
where
S: Store,
{
tracing::trace!("Validating bytes"); tracing::trace!("Validating bytes");
let (input_type, process_read) = let (input_type, process_read) =
validate::validate_bytes_stream(state, bytes, &upload_query.limits) validate::validate_bytes_stream(state, bytes, &upload_query.limits)
@ -120,8 +146,6 @@ where
.with_poll_timer("details-from-bytes-stream") .with_poll_timer("details-from-bytes-stream")
.await?; .await?;
drop(permit);
Ok((input_type, identifier, details, hash_state)) Ok((input_type, identifier, details, hash_state))
} }