Put thumbnail creation inside spawn_blocking()

This can take milliseconds or even several seconds for huge inputs,
while the rule of thumb is <100us between await points.
This commit is contained in:
Lambda 2024-05-24 18:30:59 +00:00 committed by Charles Hall
parent c973485c73
commit 0a92c72566
No known key found for this signature in database
GPG key ID: 7B8E0645816E07CF

View file

@ -118,6 +118,76 @@ impl Service {
}
}
/// Generates a thumbnail from the given image file contents. Returns
/// `Ok(None)` if the input image should be used as-is.
#[tracing::instrument(skip(file), fields(input_size = file.len()))]
fn generate_thumbnail(
file: &[u8],
width: u32,
height: u32,
crop: bool,
) -> Result<Option<Vec<u8>>> {
let Ok(image) = image::load_from_memory(file) else {
return Ok(None);
};
let original_width = image.width();
let original_height = image.height();
if width > original_width || height > original_height {
return Ok(None);
}
let thumbnail = if crop {
image.resize_to_fill(width, height, FilterType::CatmullRom)
} else {
let (exact_width, exact_height) = {
// Copied from image::dynimage::resize_dimensions
let use_width = (u64::from(width) * u64::from(original_height))
<= (u64::from(original_width) * u64::from(height));
let intermediate = if use_width {
u64::from(original_height) * u64::from(width)
/ u64::from(original_width)
} else {
u64::from(original_width) * u64::from(height)
/ u64::from(original_height)
};
if use_width {
if intermediate <= u64::from(::std::u32::MAX) {
(width, intermediate.try_into().unwrap_or(u32::MAX))
} else {
(
(u64::from(width) * u64::from(::std::u32::MAX)
/ intermediate)
.try_into()
.unwrap_or(u32::MAX),
::std::u32::MAX,
)
}
} else if intermediate <= u64::from(::std::u32::MAX) {
(intermediate.try_into().unwrap_or(u32::MAX), height)
} else {
(
::std::u32::MAX,
(u64::from(height) * u64::from(::std::u32::MAX)
/ intermediate)
.try_into()
.unwrap_or(u32::MAX),
)
}
};
image.thumbnail_exact(exact_width, exact_height)
};
let mut thumbnail_bytes = Vec::new();
thumbnail.write_to(
&mut Cursor::new(&mut thumbnail_bytes),
image::ImageFormat::Png,
)?;
Ok(Some(thumbnail_bytes))
}
/// Downloads a file's thumbnail.
///
/// Here's an example on how it works:
@ -171,73 +241,27 @@ impl Service {
let mut file = Vec::new();
File::open(path).await?.read_to_end(&mut file).await?;
let Ok(image) = image::load_from_memory(&file) else {
// Couldn't parse file to generate thumbnail, send original
let thumbnail_result = {
let file = file.clone();
let outer_span = tracing::span::Span::current();
tokio::task::spawn_blocking(move || {
outer_span.in_scope(|| {
Self::generate_thumbnail(&file, width, height, crop)
})
})
.await
.expect("failed to join thumbnailer task")
};
let Some(thumbnail_bytes) = thumbnail_result? else {
return Ok(Some(FileMeta {
content_disposition,
content_type,
file: file.clone(),
file,
}));
};
let original_width = image.width();
let original_height = image.height();
if width > original_width || height > original_height {
return Ok(Some(FileMeta {
content_disposition,
content_type,
file: file.clone(),
}));
}
let thumbnail = if crop {
image.resize_to_fill(width, height, FilterType::CatmullRom)
} else {
let (exact_width, exact_height) = {
// Copied from image::dynimage::resize_dimensions
let use_width = (u64::from(width) * u64::from(original_height))
<= (u64::from(original_width) * u64::from(height));
let intermediate = if use_width {
u64::from(original_height) * u64::from(width)
/ u64::from(original_width)
} else {
u64::from(original_width) * u64::from(height)
/ u64::from(original_height)
};
if use_width {
if intermediate <= u64::from(::std::u32::MAX) {
(width, intermediate.try_into().unwrap_or(u32::MAX))
} else {
(
(u64::from(width) * u64::from(::std::u32::MAX)
/ intermediate)
.try_into()
.unwrap_or(u32::MAX),
::std::u32::MAX,
)
}
} else if intermediate <= u64::from(::std::u32::MAX) {
(intermediate.try_into().unwrap_or(u32::MAX), height)
} else {
(
::std::u32::MAX,
(u64::from(height) * u64::from(::std::u32::MAX)
/ intermediate)
.try_into()
.unwrap_or(u32::MAX),
)
}
};
image.thumbnail_exact(exact_width, exact_height)
};
let mut thumbnail_bytes = Vec::new();
thumbnail.write_to(
&mut Cursor::new(&mut thumbnail_bytes),
image::ImageFormat::Png,
)?;
// Save thumbnail in database so we don't have to generate it
// again next time
let thumbnail_key = self.db.create_file_metadata(