fix(runtime): unify process and task state handling

This commit is contained in:
somew1nd committed 2026-09-01 11:35:23 -07:00
1 parent cbe8f4f0c6
commit 9cee112cd2
14 files changed
+918 -197

No files matched your search

+122
View File
@@ -14,9 +14,12 @@
"""Unit tests for MCP trace store."""
import glob
import logging
import os
import shutil
import tempfile
import threading
import uuid
import pytest
@@ -91,6 +94,125 @@ def test_read_nonexistent_status(temp_trace_env):
assert trace_store.read_status(non_existent) is None
def test_leftover_atomic_temp_file_does_not_affect_reads(temp_trace_env):
"""A crash between temp-write and replace leaves a .tmp file that readers ignore."""
trace_id = str(uuid.uuid4())
trace_store.init_trace(trace_id, "Crash sim", "Flash")
status_path = trace_store.get_status_path(trace_id)
# Simulate a writer that crashed after writing its temp file.
with open(f"{status_path}.{uuid.uuid4().hex}.tmp", "w", encoding="utf-8") as f:
f.write('{"status": "half-writ')
data = trace_store.read_status(trace_id)
assert data is not None
assert data["status"] == "running"
assert data["task_desc"] == "Crash sim"
def test_failed_atomic_replace_preserves_original(temp_trace_env):
"""If the atomic replace fails, the previous status.json stays intact."""
from unittest.mock import patch
trace_id = str(uuid.uuid4())
trace_store.init_trace(trace_id, "Original", "Flash")
def broken_replace(src, dst):
raise OSError("simulated replace failure")
with patch.object(trace_store, "_replace_with_retry", broken_replace):
with pytest.raises(OSError):
trace_store.write_status(trace_id, {"status": "clobbered"})
data = trace_store.read_status(trace_id)
assert data is not None
assert data["task_desc"] == "Original"
# The failed writer's temp file must not linger.
assert glob.glob(os.path.join(trace_store.get_trace_dir(trace_id), "*.tmp")) == []
def test_corrupt_status_is_quarantined_and_read_returns_none(temp_trace_env, caplog):
trace_id = str(uuid.uuid4())
trace_store.init_trace(trace_id, "Corrupt me", "Flash")
status_path = trace_store.get_status_path(trace_id)
with open(status_path, "w", encoding="utf-8") as f:
f.write('{"status": "running", "trace_id"') # torn write
with caplog.at_level(logging.WARNING, logger="mcp_server.utils.trace_store"):
assert trace_store.read_status(trace_id) is None
assert any("Corrupt status.json" in rec.message for rec in caplog.records)
assert os.path.exists(f"{status_path}.corrupt")
assert not os.path.exists(status_path)
def test_missing_status_stays_silent(temp_trace_env, caplog):
with caplog.at_level(logging.WARNING, logger="mcp_server.utils.trace_store"):
assert trace_store.read_status(str(uuid.uuid4())) is None
assert caplog.records == []
def test_update_on_corrupt_status_logs_and_drops(temp_trace_env, caplog):
trace_id = str(uuid.uuid4())
trace_store.init_trace(trace_id, "Corrupt update", "Flash")
status_path = trace_store.get_status_path(trace_id)
with open(status_path, "w", encoding="utf-8") as f:
f.write("not json at all")
with caplog.at_level(logging.WARNING, logger="mcp_server.utils.trace_store"):
assert trace_store.update_trace_status(trace_id, "completed") is None
assert any("Dropping status update" in rec.message for rec in caplog.records)
# Updating a trace that never existed stays a silent no-op.
caplog.clear()
with caplog.at_level(logging.WARNING, logger="mcp_server.utils.trace_store"):
assert trace_store.update_trace_status(str(uuid.uuid4()), "completed") is None
assert not any("Dropping status update" in rec.message for rec in caplog.records)
def test_concurrent_read_modify_write_does_not_lose_updates(temp_trace_env):
"""Interleaved RMW updates from two writers must not lose each other's fields."""
trace_id = str(uuid.uuid4())
trace_store.init_trace(trace_id, "Concurrency", "Flash")
iterations = 25
failures: list[BaseException] = []
def set_errors():
try:
for i in range(iterations):
assert trace_store.update_trace_status(
trace_id, "running", error=f"err-{i}"
) is not None
except BaseException as exc: # noqa: BLE001 - surfaced in the main thread
failures.append(exc)
def set_serials():
try:
for i in range(iterations):
assert trace_store.update_trace_device_serial(
trace_id, f"serial-{i}"
) is not None
except BaseException as exc: # noqa: BLE001 - surfaced in the main thread
failures.append(exc)
threads = [threading.Thread(target=set_errors), threading.Thread(target=set_serials)]
for t in threads:
t.start()
for t in threads:
t.join(timeout=30.0)
assert failures == []
final = trace_store.read_status(trace_id)
assert final is not None
# Every serialized RMW preserves the other writer's latest field, so both
# final values must survive regardless of which thread finished last.
assert final["error"] == f"err-{iterations - 1}"
assert final["device_serial"] == f"serial-{iterations - 1}"
assert final["status"] == "running"
def test_trace_paths(temp_trace_env):
trace_id = "test-paths-id"
trace_dir = trace_store.get_trace_dir(trace_id)