fix: serialize deployment revision writes

This commit is contained in:
lda
2026-09-09 21:30:47 +07:00 Verified
parent c5087fe037
commit a7018215b7
2 changed files with 47 additions and 13 deletions
+7
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import json
import re
from pathlib import Path
from threading import RLock
from .models import WorkflowArtifact, WorkflowDeployment
@@ -65,6 +66,7 @@ class FileWorkflowArtifactStore(WorkflowArtifactStore):
def __init__(self, root: Path) -> None:
self.root = root
self._lock = RLock()
self.artifacts_dir.mkdir(parents=True, exist_ok=True)
self.deployments_dir.mkdir(parents=True, exist_ok=True)
@@ -111,6 +113,11 @@ class FileWorkflowArtifactStore(WorkflowArtifactStore):
return self.get_artifact(artifact_id, max(versions))
def save_deployment(self, deployment: WorkflowDeployment) -> None:
# Revision advancement is store-owned and must cover the read,
# increment, and write as one process-local transition. Callers
# intentionally continue sending the existing deployment payload
# without a mandatory expected-revision field.
with self._lock:
path = self._deployment_path(deployment.id)
if path.exists():
existing = WorkflowDeployment.model_validate_json(
+27
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import json
from concurrent.futures import ThreadPoolExecutor
import pytest
@@ -67,6 +68,32 @@ def test_file_store_round_trips_deployment(tmp_path) -> None:
assert loaded.binding_map()["context7"] == "context7.personal"
def test_concurrent_deployment_saves_advance_revision_without_lost_updates(
tmp_path,
) -> None:
store = FileWorkflowArtifactStore(tmp_path)
store.save_deployment(
WorkflowDeployment(
id="concurrent.personal",
artifact_id="summarize_docs",
artifact_version=1,
)
)
updates = [
WorkflowDeployment(
id="concurrent.personal",
artifact_id="summarize_docs",
artifact_version=version,
)
for version in range(2, 10)
]
with ThreadPoolExecutor(max_workers=len(updates)) as executor:
list(executor.map(store.save_deployment, updates))
assert store.get_deployment("concurrent.personal").revision == 9
def test_file_store_loads_legacy_artifact_and_rewrites_canonical_shape(
tmp_path,
) -> None: