decoder: Cancel file loading on decoder drop

That will probably at least work for scrolling quickly
through images. It will however usually not cancel an ongoing
decoding process.

Issue #155

Part-of: <https://gitlab.gnome.org/Incubator/loupe/-/merge_requests/210>
This commit is contained in:
Sophie Herold 2023-04-27 22:05:44 +02:00 • committed by Marge Bot
parent 6d7623b15a
commit 7c227f75a8
6 changed files with 56 additions and 48 deletions

View file

@ -33,7 +33,15 @@ use rgb::AsPixels;
use std::sync::Arc;
#[derive(Debug)]
pub struct Heif;
pub struct Heif {
cancellable: gio::Cancellable,
}
impl Drop for Heif {
fn drop(&mut self) {
self.cancellable.cancel();
}
}
/// Loads plugins on first use
static LIBHEIF: Lazy<LibHeif> = Lazy::new(LibHeif::new);
@ -45,6 +53,9 @@ impl Heif {
tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
) -> Self {
log::debug!("Loading HEIF");
let cancellable = gio::Cancellable::new();
let cancellable_ = cancellable.clone();
updater.spawn_error_handled(move || {
let file_size = file
.query_info(
@ -54,7 +65,7 @@ impl Heif {
)
.context(gettext("Failed to read image file information"))?
.size();
let buf_reader = file.to_buf_read()?;
let buf_reader = file.to_buf_read(&cancellable)?;
let stream_reader = libheif_rs::StreamReader::new(buf_reader, file_size as u64);
let ctx = HeifContext::read_from_reader(Box::new(stream_reader))
.context(gettext("Failed to decode image"))?;
@ -132,7 +143,9 @@ impl Heif {
Ok(())
});
Heif
Heif {
cancellable: cancellable_,
}
}
}

View file

@ -23,7 +23,7 @@ use crate::util;
use crate::util::gettext::*;
use crate::util::{BufReadSeek, ToBufRead};
use anyhow::{bail, Context};
use anyhow::Context;
use arc_swap::ArcSwap;
use gtk::prelude::*;
use image_rs::{AnimationDecoder, ImageDecoder};
@ -34,18 +34,13 @@ use std::sync::Arc;
#[derive(Debug)]
pub struct ImageRsOther {
thread_handle: Option<std::thread::JoinHandle<()>>,
abort: Arc<std::sync::RwLock<bool>>,
cancellable: gio::Cancellable,
}
impl Drop for ImageRsOther {
fn drop(&mut self) {
if let Some(handle) = &self.thread_handle {
if let Ok(mut abort) = self.abort.write() {
*abort = true;
} else {
// Don't use log in drop because not sure if always still initialized
eprintln!("Unable to write abort signal to decoding thread.");
}
self.cancellable.cancel();
handle.thread().unpark();
}
}
@ -55,8 +50,9 @@ impl ImageRsOther {
fn reader(
file: &gio::File,
format: image_rs::ImageFormat,
cancellable: &gio::Cancellable,
) -> anyhow::Result<image_rs::io::Reader<Box<dyn BufReadSeek>>> {
let buf_read = file.to_buf_read()?;
let buf_read = file.to_buf_read(cancellable)?;
let mut reader = image_rs::io::Reader::new(buf_read);
reader.set_format(format);
Ok(reader)
@ -68,7 +64,7 @@ impl ImageRsOther {
updater: UpdateSender,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
) -> Self {
let abort: Arc<std::sync::RwLock<bool>> = Default::default();
let cancellable = gio::Cancellable::new();
let thread_handle = match format {
image_rs::ImageFormat::Gif
@ -78,17 +74,17 @@ impl ImageRsOther {
format,
updater,
tiles,
abort.clone(),
cancellable.clone(),
)),
_ => {
Self::new_static(file, format, updater, tiles);
Self::new_static(file, format, updater, tiles, cancellable.clone());
None
}
};
ImageRsOther {
thread_handle,
abort,
cancellable,
}
}
@ -97,9 +93,10 @@ impl ImageRsOther {
format: image_rs::ImageFormat,
updater: UpdateSender,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
cancellable: gio::Cancellable,
) {
updater.spawn_error_handled(move || {
let reader = Self::reader(&file, format)?;
let reader = Self::reader(&file, format, &cancellable)?;
let dimensions = reader
.into_dimensions()
.context("Failed to read image dimensions")?;
@ -108,7 +105,7 @@ impl ImageRsOther {
let (icc_profile, dynamic_image) = match format {
image_rs::ImageFormat::Jpeg => {
let reader = file.to_buf_read()?;
let reader = file.to_buf_read(&cancellable)?;
let mut decoder = image_rs::codecs::jpeg::JpegDecoder::new(reader)?;
let _ = decoder.set_limits(image_rs::io::Limits::no_limits());
let icc_profile = decoder.icc_profile();
@ -117,7 +114,7 @@ impl ImageRsOther {
(icc_profile, dynamic_image)
}
image_rs::ImageFormat::Png => {
let reader = file.to_buf_read()?;
let reader = file.to_buf_read(&cancellable)?;
let mut decoder = image_rs::codecs::png::PngDecoder::new(reader)?;
let _ = decoder.set_limits(image_rs::io::Limits::no_limits());
let icc_profile = decoder.icc_profile();
@ -126,7 +123,7 @@ impl ImageRsOther {
(icc_profile, dynamic_image)
}
_ => {
let mut reader = Self::reader(&file, format)?;
let mut reader = Self::reader(&file, format, &cancellable)?;
reader.limits(image_rs::io::Limits::no_limits());
let dynamic_image =
reader.decode().context(gettext("Failed to decode image"))?;
@ -157,7 +154,7 @@ impl ImageRsOther {
format: image_rs::ImageFormat,
updater: UpdateSender,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
abort: Arc<std::sync::RwLock<bool>>,
cancellable: gio::Cancellable,
) -> std::thread::JoinHandle<()> {
updater.clone().spawn_error_handled(move || {
let mut nth_frame = 1;
@ -165,7 +162,7 @@ impl ImageRsOther {
// We are currently decoding for each repetition of the animation
// TODO: Check if/how that can be solved differently
loop {
let buf_reader = file.to_buf_read()?;
let buf_reader = file.to_buf_read(&cancellable)?;
let (frames, animated_format) = match format {
image_rs::ImageFormat::Gif => (
@ -178,7 +175,7 @@ impl ImageRsOther {
(decoder.into_frames(), ImageFormat::AnimatedWebP)
} else {
// Static WebP images need a different decoder
Self::new_static(file, format, updater, tiles);
Self::new_static(file, format, updater, tiles, cancellable);
return Ok(());
}
}
@ -188,7 +185,7 @@ impl ImageRsOther {
(decoder.apng().into_frames(), ImageFormat::AnimatedPng)
} else {
// Static PNG images need a different decoder
Self::new_static(file, format, updater, tiles);
Self::new_static(file, format, updater, tiles, cancellable);
return Ok(());
}
}
@ -247,13 +244,9 @@ impl ImageRsOther {
std::thread::park();
}
if let Ok(abort_state) = abort.read().as_deref() {
if *abort_state {
log::debug!("Terminating decoder thread.");
return Ok(());
}
} else {
bail!("Cannot read decoder thread abort instructions.");
if cancellable.is_cancelled() {
log::debug!("Terminating decoder thread.");
return Ok(());
}
}
}

View file

@ -35,13 +35,13 @@ pub const RSVG_MAX_SIZE: u32 = 32_767;
pub struct Svg {
thread_handle: std::thread::JoinHandle<()>,
current_request: Arc<std::sync::RwLock<Request>>,
cancellable: gio::Cancellable,
}
#[derive(Default, Debug, Clone)]
enum Request {
#[default]
None,
Exit,
Tile(TileRequest),
}
@ -53,12 +53,7 @@ impl Request {
impl Drop for Svg {
fn drop(&mut self) {
if let Ok(mut request) = self.current_request.write() {
*request = Request::Exit;
} else {
// Don't use log in drop because not sure if always still initialized
eprintln!("Unable to write exit request: RwLock is poisoned.");
}
self.cancellable.cancel();
self.thread_handle.thread().unpark();
}
}
@ -71,9 +66,11 @@ impl Svg {
) -> Self {
let current_request: Arc<std::sync::RwLock<Request>> = Default::default();
let request_store = current_request.clone();
let cancellable = gio::Cancellable::new();
let cancellable_ = cancellable.clone();
let thread_handle = updater.spawn_error_handled(move || {
let handle = rsvg::Loader::new().read_file(&file, gio::Cancellable::NONE)?;
let handle = rsvg::Loader::new().read_file(&file, Some(&cancellable))?;
let renderer = rsvg::CairoRenderer::new(&handle);
let (original_width, original_height) = svg_dimensions(&renderer);
@ -92,14 +89,16 @@ impl Svg {
value
};
if cancellable.is_cancelled() {
log::debug!("Terminating SVG decoder thread");
break;
}
match tile_request {
Request::None => {
std::thread::park();
}
Request::Exit => {
log::debug!("Terminating decoder thread.");
break;
}
Request::Tile(tile_request) => {
let tiling = tiles
.get_layer_tiling_or_default(tile_request.zoom, tile_request.viewport);
@ -172,6 +171,7 @@ impl Svg {
Self {
thread_handle,
current_request,
cancellable: cancellable_,
}
}