fix: make converter lock timeout configurable via LOCK_TIMEOUT env var (#61)
Replace hardcoded _LOCK_TIMEOUT=300 with settings.lock_timeout, readable from LOCK_TIMEOUT environment variable.
This commit is contained in:
parent
2c254382c8
commit
1657bce1f0
3 changed files with 13 additions and 3 deletions
|
|
@ -49,7 +49,6 @@ logger = logging.getLogger(__name__)
|
||||||
# Thread lock — DoclingConverter is not thread-safe.
|
# Thread lock — DoclingConverter is not thread-safe.
|
||||||
# Uses a timeout to prevent a frozen conversion from blocking all others.
|
# Uses a timeout to prevent a frozen conversion from blocking all others.
|
||||||
_converter_lock = threading.Lock()
|
_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
|
# US Letter page dimensions (points) — fallback when page size is unknown
|
||||||
_DEFAULT_PAGE_WIDTH = 612.0
|
_DEFAULT_PAGE_WIDTH = 612.0
|
||||||
|
|
@ -218,10 +217,10 @@ def _process_content_item(
|
||||||
|
|
||||||
|
|
||||||
def _convert_sync(file_path: str, options: ConversionOptions) -> ConversionResult:
|
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:
|
if not acquired:
|
||||||
raise TimeoutError(
|
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"
|
"a previous conversion may be frozen"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class Settings:
|
||||||
docling_serve_api_key: str | None = None
|
docling_serve_api_key: str | None = None
|
||||||
conversion_timeout: int = 900
|
conversion_timeout: int = 900
|
||||||
document_timeout: float = 120.0 # Docling-level per-document timeout (seconds)
|
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
|
max_concurrent_analyses: int = 3
|
||||||
default_table_mode: str = "accurate" # "accurate" or "fast"
|
default_table_mode: str = "accurate" # "accurate" or "fast"
|
||||||
max_page_count: int = 0 # 0 = unlimited (upload validation)
|
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})")
|
errors.append(f"document_timeout must be > 0 (got {self.document_timeout})")
|
||||||
if self.conversion_timeout <= 0:
|
if self.conversion_timeout <= 0:
|
||||||
errors.append(f"conversion_timeout must be > 0 (got {self.conversion_timeout})")
|
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:
|
if self.max_concurrent_analyses < 1:
|
||||||
errors.append(
|
errors.append(
|
||||||
f"max_concurrent_analyses must be >= 1 (got {self.max_concurrent_analyses})"
|
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"),
|
docling_serve_api_key=os.environ.get("DOCLING_SERVE_API_KEY"),
|
||||||
conversion_timeout=int(os.environ.get("CONVERSION_TIMEOUT", "900")),
|
conversion_timeout=int(os.environ.get("CONVERSION_TIMEOUT", "900")),
|
||||||
document_timeout=float(os.environ.get("DOCUMENT_TIMEOUT", "120.0")),
|
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")),
|
max_concurrent_analyses=int(os.environ.get("MAX_CONCURRENT_ANALYSES", "3")),
|
||||||
default_table_mode=os.environ.get("DEFAULT_TABLE_MODE", "accurate"),
|
default_table_mode=os.environ.get("DEFAULT_TABLE_MODE", "accurate"),
|
||||||
max_page_count=int(os.environ.get("MAX_PAGE_COUNT", "0")),
|
max_page_count=int(os.environ.get("MAX_PAGE_COUNT", "0")),
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class TestSettingsDefaults:
|
||||||
assert s.docling_serve_api_key is None
|
assert s.docling_serve_api_key is None
|
||||||
assert s.conversion_timeout == 900
|
assert s.conversion_timeout == 900
|
||||||
assert s.document_timeout == 120.0
|
assert s.document_timeout == 120.0
|
||||||
|
assert s.lock_timeout == 300
|
||||||
assert s.max_page_count == 0
|
assert s.max_page_count == 0
|
||||||
assert s.upload_dir == "./uploads"
|
assert s.upload_dir == "./uploads"
|
||||||
assert s.db_path == "./data/docling_studio.db"
|
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"):
|
with pytest.raises(ValueError, match="max_file_size must be >= 0"):
|
||||||
Settings(max_file_size=-1)
|
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):
|
def test_invalid_table_mode_rejected(self):
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue