fix: lock meta RMW and harden concurrent remix paths
CI / lint-and-test (pull_request) Failing after 9s

Prevent worker/web clobbering of meta variants via flock and merge-by-id,
make filter timeouts thread-local, harden job-id/stem sanitization, migrate
TemplateResponse API, and remove the compare-bg experiment.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Frank Schwenk
2026-07-18 17:44:15 +02:00
parent 9a9b143f08
commit b09f90bfcc
14 changed files with 233 additions and 234 deletions
+18
View File
@@ -24,3 +24,21 @@ def test_write_json_atomic_roundtrip(tmp_path: Path) -> None:
write_json_atomic(path, payload)
assert json.loads(path.read_text(encoding="utf-8")) == payload
assert not path.with_suffix(path.suffix + ".tmp").exists()
def test_file_lock_exclusive(tmp_path: Path) -> None:
from concurrent.futures import ThreadPoolExecutor
from app.io_utils import file_lock
lock_path = tmp_path / "job.lock"
counter = {"n": 0}
def bump() -> None:
with file_lock(lock_path):
current = counter["n"]
counter["n"] = current + 1
with ThreadPoolExecutor(max_workers=8) as pool:
list(pool.map(lambda _: bump(), range(40)))
assert counter["n"] == 40
+10
View File
@@ -21,6 +21,16 @@ def test_validate_job_id_sanitizes_input() -> None:
assert _validate_job_id("photo01") == "photo01"
def test_validate_job_id_rejects_path_like() -> None:
with pytest.raises(HTTPException) as exc:
_validate_job_id("a/b")
assert exc.value.status_code == 400
with pytest.raises(HTTPException):
_validate_job_id("../evil")
with pytest.raises(HTTPException):
_validate_job_id("")
def test_safe_job_file_rejects_wrong_prefix(data_dir: Path) -> None:
stem = "photo01"
paths = pipeline.job_paths(stem)
+54
View File
@@ -13,6 +13,7 @@ from app import pipeline
[
("photo.jpg", "photo"),
("../evil", "evil"),
("a/b", "a_b"),
("", "photo"),
(" spaced ", "spaced"),
("foo@bar", "foo_bar"),
@@ -78,3 +79,56 @@ def test_list_job_ids(data_dir: Path) -> None:
paths = pipeline.job_paths("b")
pipeline.write_status(paths, "processing")
assert sorted(pipeline.list_job_ids()) == ["a", "b"]
def test_parallel_manifest_merge_keeps_all_variants(data_dir: Path) -> None:
"""Concurrent write_manifest callers must not clobber each other's variants."""
from concurrent.futures import ThreadPoolExecutor
paths = pipeline.job_paths("race")
pipeline.write_status(paths, "processing")
pipeline.write_manifest(
paths,
{
"source_stem": "race",
"original_file": "race_original.jpg",
"rembg_file": "race_rembg.png",
"created_at": pipeline.now_iso(),
"variants": [],
},
)
def write_one(i: int) -> None:
pipeline.write_manifest(
paths,
{
"variants": [{"id": f"v{i}", "file": f"race_v{i}.png"}],
},
)
with ThreadPoolExecutor(max_workers=8) as pool:
list(pool.map(write_one, range(20)))
meta = pipeline.read_meta("race")
assert meta is not None
ids = {v["id"] for v in meta["variants"]}
assert ids == {f"v{i}" for i in range(20)}
assert meta["original_file"] == "race_original.jpg"
def test_filter_timeout_is_thread_local() -> None:
import threading
results: dict[str, int] = {}
def worker(name: str, value: int) -> None:
with pipeline.filter_timeout(value):
results[name] = pipeline._active_filter_timeout()
t1 = threading.Thread(target=worker, args=("a", 11))
t2 = threading.Thread(target=worker, args=("b", 22))
t1.start()
t2.start()
t1.join()
t2.join()
assert results == {"a": 11, "b": 22}