US07-04: Prove Concurrency and Crash Recovery (#86)
This commit was merged in pull request #86.
This commit is contained in:
@@ -17,6 +17,20 @@ import os
|
||||
import subprocess
|
||||
from collections.abc import Iterable
|
||||
|
||||
# A hung exiftool must not hang the worker with it: every call is bounded, and a
|
||||
# call that runs out of time is treated exactly like a failed one — no metadata
|
||||
# answer, nothing marked verified (US07-04). The knob exists because "slow" is a
|
||||
# property of the machine, not of the code: huge files on a slow network volume
|
||||
# legitimately take longer than the default.
|
||||
DEFAULT_TIMEOUT_SECONDS = 120.0
|
||||
|
||||
|
||||
def _timeout() -> float:
|
||||
try:
|
||||
return float(os.environ.get("PHOTO_PIPELINE_EXIFTOOL_TIMEOUT", DEFAULT_TIMEOUT_SECONDS))
|
||||
except ValueError:
|
||||
return DEFAULT_TIMEOUT_SECONDS
|
||||
|
||||
|
||||
def read_keyword_sets(paths: Iterable[str]) -> dict[str, set[str]]:
|
||||
"""Map each path to its lowercased set of ``Keywords`` + ``Subject`` values.
|
||||
@@ -34,8 +48,9 @@ def read_keyword_sets(paths: Iterable[str]) -> dict[str, set[str]]:
|
||||
input="\n".join(paths),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=_timeout(),
|
||||
)
|
||||
except FileNotFoundError:
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
return {}
|
||||
out: dict[str, set[str]] = {}
|
||||
try:
|
||||
@@ -67,9 +82,12 @@ def read_all(path: str) -> dict | None:
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["exiftool", "-m", "-j", "-G0:1", path], capture_output=True, text=True
|
||||
["exiftool", "-m", "-j", "-G0:1", path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=_timeout(),
|
||||
)
|
||||
except FileNotFoundError:
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
return None
|
||||
try:
|
||||
records = json.loads(result.stdout or "[]")
|
||||
@@ -91,4 +109,10 @@ def apply_keywords(path: str, *, add: Iterable[str] = (), remove: Iterable[str]
|
||||
if len(args) == 3:
|
||||
return True
|
||||
args.append(path)
|
||||
return subprocess.run(args, capture_output=True, text=True).returncode == 0
|
||||
try:
|
||||
return subprocess.run(
|
||||
args, capture_output=True, text=True, timeout=_timeout()
|
||||
).returncode == 0
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
# A write that never returned is not a write that succeeded.
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user