diff --git a/document-parser/infra/local_converter.py b/document-parser/infra/local_converter.py index a126e7b..1a3b701 100644 --- a/document-parser/infra/local_converter.py +++ b/document-parser/infra/local_converter.py @@ -49,7 +49,6 @@ logger = logging.getLogger(__name__) # Thread lock — DoclingConverter is not thread-safe. # Uses a timeout to prevent a frozen conversion from blocking all others. _converter_lock = threading.Lock() -_LOCK_TIMEOUT = 300 # seconds — fail fast rather than wait forever # US Letter page dimensions (points) — fallback when page size is unknown _DEFAULT_PAGE_WIDTH = 612.0 @@ -218,10 +217,10 @@ def _process_content_item( def _convert_sync(file_path: str, options: ConversionOptions) -> ConversionResult: - acquired = _converter_lock.acquire(timeout=_LOCK_TIMEOUT) + acquired = _converter_lock.acquire(timeout=settings.lock_timeout) if not acquired: raise TimeoutError( - f"Could not acquire converter lock within {_LOCK_TIMEOUT}s — " + f"Could not acquire converter lock within {settings.lock_timeout}s — " "a previous conversion may be frozen" ) try: diff --git a/document-parser/infra/settings.py b/document-parser/infra/settings.py index 60cc3c5..496e032 100644 --- a/document-parser/infra/settings.py +++ b/document-parser/infra/settings.py @@ -15,6 +15,7 @@ class Settings: docling_serve_api_key: str | None = None conversion_timeout: int = 900 document_timeout: float = 120.0 # Docling-level per-document timeout (seconds) + lock_timeout: int = 300 # converter lock acquisition timeout (seconds) max_concurrent_analyses: int = 3 default_table_mode: str = "accurate" # "accurate" or "fast" max_page_count: int = 0 # 0 = unlimited (upload validation) @@ -31,6 +32,8 @@ class Settings: errors.append(f"document_timeout must be > 0 (got {self.document_timeout})") if self.conversion_timeout <= 0: errors.append(f"conversion_timeout must be > 0 (got {self.conversion_timeout})") + if self.lock_timeout <= 0: + errors.append(f"lock_timeout must be > 0 (got {self.lock_timeout})") if self.max_concurrent_analyses < 1: errors.append( f"max_concurrent_analyses must be >= 1 (got {self.max_concurrent_analyses})" @@ -58,6 +61,7 @@ class Settings: docling_serve_api_key=os.environ.get("DOCLING_SERVE_API_KEY"), conversion_timeout=int(os.environ.get("CONVERSION_TIMEOUT", "900")), document_timeout=float(os.environ.get("DOCUMENT_TIMEOUT", "120.0")), + lock_timeout=int(os.environ.get("LOCK_TIMEOUT", "300")), max_concurrent_analyses=int(os.environ.get("MAX_CONCURRENT_ANALYSES", "3")), default_table_mode=os.environ.get("DEFAULT_TABLE_MODE", "accurate"), max_page_count=int(os.environ.get("MAX_PAGE_COUNT", "0")), diff --git a/document-parser/tests/test_settings.py b/document-parser/tests/test_settings.py index aa27233..9a683fd 100644 --- a/document-parser/tests/test_settings.py +++ b/document-parser/tests/test_settings.py @@ -15,6 +15,7 @@ class TestSettingsDefaults: assert s.docling_serve_api_key is None assert s.conversion_timeout == 900 assert s.document_timeout == 120.0 + assert s.lock_timeout == 300 assert s.max_page_count == 0 assert s.upload_dir == "./uploads" assert s.db_path == "./data/docling_studio.db" @@ -66,6 +67,12 @@ class TestSettingsValidation: with pytest.raises(ValueError, match="max_file_size must be >= 0"): Settings(max_file_size=-1) + def test_zero_lock_timeout_rejected(self): + import pytest + + with pytest.raises(ValueError, match="lock_timeout must be > 0"): + Settings(lock_timeout=0) + def test_invalid_table_mode_rejected(self): import pytest