Use a separate thread to read stderr when needed

This commit is contained in:
Alexis Métaireau 2024-12-18 16:40:18 +01:00
parent d66af442b3
commit 4f4c523450
No known key found for this signature in database
GPG key ID: C65C7A89A8FFC56E
4 changed files with 48 additions and 3 deletions

View file

@ -46,7 +46,8 @@ def print_header(s: str) -> None:
"--debug", "--debug",
"debug", "debug",
flag_value=True, flag_value=True,
help="Run Dangerzone in debug mode, to get logs from gVisor.") help="Run Dangerzone in debug mode, to get logs from gVisor.",
)
@click.version_option(version=get_version(), message="%(version)s") @click.version_option(version=get_version(), message="%(version)s")
@errors.handle_document_errors @errors.handle_document_errors
def cli_main( def cli_main(

View file

@ -5,7 +5,9 @@ import platform
import signal import signal
import subprocess import subprocess
import sys import sys
import threading
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from queue import Queue
from typing import IO, Callable, Iterator, Optional from typing import IO, Callable, Iterator, Optional
import fitz import fitz
@ -92,6 +94,7 @@ class IsolationProvider(ABC):
self.proc_stderr = subprocess.PIPE self.proc_stderr = subprocess.PIPE
else: else:
self.proc_stderr = subprocess.DEVNULL self.proc_stderr = subprocess.DEVNULL
self.stderr_queue: Queue = Queue()
def should_capture_stderr(self) -> bool: def should_capture_stderr(self) -> bool:
return self.debug or getattr(sys, "dangerzone_dev", False) return self.debug or getattr(sys, "dangerzone_dev", False)
@ -363,3 +366,23 @@ class IsolationProvider(ABC):
f"{debug_log}" # no need for an extra newline here f"{debug_log}" # no need for an extra newline here
f"{DOC_TO_PIXELS_LOG_END}" f"{DOC_TO_PIXELS_LOG_END}"
) )
def _stream_stderr(self, stderr: IO[bytes], queue: Queue) -> None:
"""Read stderr in a separate thread to avoid blocking"""
try:
for line in stderr:
queue.put(line)
if self.debug:
log.debug(line.decode().strip())
except (ValueError, IOError) as e:
log.debug(f"Stderr stream closed: {e}")
def start_stderr_thread(self, process: subprocess.Popen) -> None:
"""Start a thread to read stderr from the process"""
if process.stderr:
stderr_thread = threading.Thread(
target=self._stream_stderr,
args=(process.stderr, self.stderr_queue),
daemon=True,
)
stderr_thread.start()

View file

@ -154,7 +154,7 @@ class Container(IsolationProvider):
args, args,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=self.proc_stderr, stderr=subprocess.PIPE,
startupinfo=startupinfo, startupinfo=startupinfo,
# Start the conversion process in a new session, so that we can later on # Start the conversion process in a new session, so that we can later on
# kill the process group, without killing the controlling script. # kill the process group, without killing the controlling script.
@ -189,7 +189,14 @@ class Container(IsolationProvider):
+ command + command
) )
args = [container_runtime] + args args = [container_runtime] + args
return self.exec(args) args_str = " ".join(shlex.quote(s) for s in args)
log.info("> " + args_str)
process = self.exec(args)
# Start stderr reader thread, attaching it to the process
self.start_stderr_thread(process)
return process
def kill_container(self, name: str) -> None: def kill_container(self, name: str) -> None:
"""Terminate a spawned container. """Terminate a spawned container.

View file

@ -66,11 +66,25 @@ class DangerzoneCore(object):
) -> None: ) -> None:
def convert_doc(document: Document) -> None: def convert_doc(document: Document) -> None:
try: try:
# Clear any existing stderr output
while not self.isolation_provider.stderr_queue.empty():
self.isolation_provider.stderr_queue.get_nowait()
self.isolation_provider.convert( self.isolation_provider.convert(
document, document,
ocr_lang, ocr_lang,
stdout_callback, stdout_callback,
) )
# Process any stderr output that was captured
while not self.isolation_provider.stderr_queue.empty():
try:
line = self.isolation_provider.stderr_queue.get_nowait()
if stdout_callback:
stdout_callback(True, line.decode().strip(), -1)
except Exception as e:
log.error(f"Error processing stderr: {e}")
except Exception: except Exception:
log.exception( log.exception(
f"Unexpected error occurred while converting '{document}'" f"Unexpected error occurred while converting '{document}'"