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 bea256d6f1
No known key found for this signature in database
GPG key ID: C65C7A89A8FFC56E
3 changed files with 53 additions and 1 deletions

View file

@ -5,6 +5,8 @@ import platform
import signal import signal
import subprocess import subprocess
import sys import sys
import threading
from queue import Queue
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import IO, Callable, Iterator, Optional from typing import IO, Callable, Iterator, Optional
@ -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()
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, 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

@ -189,7 +189,22 @@ 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 = subprocess.Popen(
args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE, # Always pipe stderr
startupinfo=startupinfo,
start_new_session=True,
)
# Start stderr reader thread
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}'"