deer-flow/backend/tests/test_terminal_commit_authority.py
spud 850b3b2547
fix(runtime): attribute terminal commits and isolate lease renewals (#6919)
* fix(runtime): confirm terminal commits and isolate lease renewal

* fix(runtime): diagnose unsupported terminal store contracts

* fix(runtime): attribute atomic claims and close lease lifecycle gaps

* fix(runtime): require explicit terminal completion capability

* fix(runtime): retry renewals that race rollback admission
2026-10-19 15:06:35 +08:00

577 lines
22 KiB
Python

"""Deterministic terminal attribution and independent lease deadline regressions."""
import asyncio
from contextlib import suppress
from datetime import UTC, datetime, timedelta
from unittest.mock import AsyncMock
import pytest
from deerflow.config.run_ownership_config import RunOwnershipConfig
from deerflow.runtime import RunManager, RunStatus
from deerflow.runtime.runs.store.base import RunStore, StatusFinalization
from deerflow.runtime.runs.store.memory import MemoryRunStore
def manager_for(store):
return RunManager(store=store, worker_id="owner", run_ownership_config=RunOwnershipConfig(heartbeat_enabled=True, lease_seconds=30, grace_seconds=0))
async def staged(manager, thread="thread"):
record = await manager.create_or_reject(thread)
record.terminal_commit_pending = True
await manager.set_status(record.run_id, RunStatus.success, persist=False)
return record
async def finish(*tasks):
for task in tasks:
if not task.done():
task.cancel()
for task in tasks:
with suppress(asyncio.CancelledError):
await asyncio.wait_for(task, 1)
@pytest.mark.anyio
@pytest.mark.parametrize("status", [RunStatus.success, RunStatus.error])
async def test_identical_peer_terminal_row_is_not_own_ack(status):
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
await manager.set_status(record.run_id, status, persist=False)
assert (await store.finalize_if_not_cancelled(record.run_id, status=status.value)).finalized
await manager._renew_leases()
assert record.ownership_lost, "an externally committed identical row must fence the unconfirmed writer"
@pytest.mark.anyio
async def test_rejected_cas_has_no_status_or_completion_fallback():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
store.finalize_if_not_cancelled = AsyncMock(return_value=StatusFinalization(finalized=False))
store.update_status = AsyncMock(wraps=store.update_status)
store.update_run_completion = AsyncMock(wraps=store.update_run_completion)
await manager.set_status_if_not_cancelled(record.run_id, RunStatus.success)
await manager.update_run_completion(record.run_id, status="success", total_tokens=7)
assert (await store.get(record.run_id))["status"] == "pending"
store.update_status.assert_not_awaited()
store.update_run_completion.assert_not_awaited()
assert record.ownership_lost
@pytest.mark.anyio
async def test_unacknowledged_completion_cannot_terminalize_or_recreate():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
await manager.update_run_completion(record.run_id, status="success", total_tokens=7)
assert (await store.get(record.run_id))["status"] == "pending"
assert not record.terminal_status_persisted
await store.delete(record.run_id)
await manager.update_run_completion(record.run_id, status="success")
assert await store.get(record.run_id) is None
@pytest.mark.anyio
async def test_committed_cas_waits_for_its_ack_after_renewal_rejection():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
committed, release, rejected = asyncio.Event(), asyncio.Event(), asyncio.Event()
original_finalize, original_renew = store.finalize_if_not_cancelled, store.renew_lease
async def commit_then_hold_ack(*args, **kwargs):
result = await original_finalize(*args, **kwargs)
committed.set()
await release.wait()
return result
async def reject_terminal_renewal(*args, **kwargs):
result = await original_renew(*args, **kwargs)
rejected.set()
return result
store.finalize_if_not_cancelled = commit_then_hold_ack
store.renew_lease = reject_terminal_renewal
commit = asyncio.create_task(manager.set_status_if_not_cancelled(record.run_id, RunStatus.success))
renewal = None
try:
await asyncio.wait_for(committed.wait(), 1)
renewal = asyncio.create_task(manager._renew_leases())
await asyncio.wait_for(rejected.wait(), 0.3)
assert record.terminal_commit_pending
assert not renewal.done(), "the matching durable row cannot replace the held acknowledgment"
release.set()
await asyncio.wait_for(asyncio.gather(commit, renewal), 1)
assert not record.ownership_lost
assert not record.terminal_commit_pending
assert record.terminal_status_persisted
finally:
release.set()
await finish(commit, *([renewal] if renewal else []))
@pytest.mark.anyio
async def test_uncancellable_renewal_does_not_delay_peer_cancel_or_deadline():
store = MemoryRunStore()
manager = manager_for(store)
slow = await manager.create_or_reject("slow")
fast = await manager.create_or_reject("fast")
deadline = (datetime.now(UTC) + timedelta(milliseconds=100)).isoformat()
slow.lease_expires_at = deadline
store._runs[slow.run_id]["lease_expires_at"] = deadline
entered, release, fast_renewed = asyncio.Event(), asyncio.Event(), asyncio.Event()
original = store.renew_lease
calls = 0
async def blocked(*args, **kwargs):
nonlocal calls
if args[0] == slow.run_id:
calls += 1
entered.set()
while not release.is_set():
try:
await release.wait()
except asyncio.CancelledError:
pass
return await original(*args, **kwargs)
result = await original(*args, **kwargs)
fast_renewed.set()
return result
store.renew_lease = blocked
await store.request_cancel(fast.run_id, action="rollback")
cycle = asyncio.create_task(manager._renew_leases())
try:
await asyncio.wait_for(entered.wait(), 1)
await asyncio.wait_for(fast_renewed.wait(), 0.3)
await asyncio.wait_for(asyncio.shield(cycle), 0.4)
assert slow.ownership_lost
assert fast.abort_event.is_set()
assert fast.abort_action == "rollback"
assert not fast.ownership_lost
await manager._renew_leases()
assert calls == 1
release.set()
await manager.stop_heartbeat(timeout=0.1)
assert slow.ownership_lost
assert slow.lease_expires_at == deadline
finally:
release.set()
await finish(cycle)
await manager.stop_heartbeat(timeout=0.1)
@pytest.mark.anyio
async def test_caller_cancelled_cas_has_no_positive_proof():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
entered, release = asyncio.Event(), asyncio.Event()
original = store.finalize_if_not_cancelled
async def commit_without_ack(*args, **kwargs):
await original(*args, **kwargs)
entered.set()
await release.wait()
return StatusFinalization(finalized=True)
store.finalize_if_not_cancelled = commit_without_ack
commit = asyncio.create_task(manager.set_status_if_not_cancelled(record.run_id, RunStatus.success))
try:
await asyncio.wait_for(entered.wait(), 1)
commit.cancel()
with pytest.raises(asyncio.CancelledError):
await commit
await manager._renew_leases()
assert record.ownership_lost
assert not record.terminal_status_persisted
finally:
release.set()
await finish(commit)
@pytest.mark.anyio
async def test_different_attempts_settle_without_borrowing_another_status_ack():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
committed, rejected, release = asyncio.Event(), asyncio.Event(), asyncio.Event()
original = store.finalize_if_not_cancelled
async def hold_success(*args, **kwargs):
result = await original(*args, **kwargs)
if result.finalized:
committed.set()
await release.wait()
else:
rejected.set()
return result
store.finalize_if_not_cancelled = hold_success
success = asyncio.create_task(manager.set_status_if_not_cancelled(record.run_id, RunStatus.success))
failure = None
try:
await asyncio.wait_for(committed.wait(), 1)
failure = asyncio.create_task(manager.set_status_if_not_cancelled(record.run_id, RunStatus.error, error="losing attempt"))
await asyncio.wait_for(rejected.wait(), 1)
assert record.terminal_commit_pending
assert not record.terminal_status_persisted
release.set()
await asyncio.wait_for(asyncio.gather(success, failure), 1)
assert record.status == RunStatus.success
assert not record.ownership_lost
assert record.terminal_status_persisted
assert manager._terminal_attempts == {}
finally:
release.set()
await finish(success, *([failure] if failure else []))
@pytest.mark.anyio
async def test_unknown_commit_fences_at_deadline_and_late_ack_cannot_revive():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
expiry = (datetime.now(UTC) + timedelta(milliseconds=100)).isoformat()
record.lease_expires_at = expiry
store._runs[record.run_id]["lease_expires_at"] = expiry
committed, release = asyncio.Event(), asyncio.Event()
original = store.finalize_if_not_cancelled
async def hold_ack(*args, **kwargs):
result = await original(*args, **kwargs)
committed.set()
await release.wait()
return result
store.finalize_if_not_cancelled = hold_ack
commit = asyncio.create_task(manager.set_status_if_not_cancelled(record.run_id, RunStatus.success))
try:
await asyncio.wait_for(committed.wait(), 1)
await asyncio.wait_for(manager._renew_leases(), 0.5)
assert record.ownership_lost
assert not record.terminal_status_persisted
release.set()
await asyncio.wait_for(commit, 1)
assert record.ownership_lost
assert record.status == RunStatus.error
assert not record.terminal_status_persisted
assert manager._terminal_attempts == {}
finally:
release.set()
await finish(commit)
@pytest.mark.anyio
async def test_legacy_none_status_result_is_unconfirmed_in_heartbeat_mode():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
original = store.update_status
async def no_rowcount(*args, **kwargs):
await original(*args, **kwargs)
store.update_status = no_rowcount
assert await manager.persist_current_status(record.run_id) is False
assert record.ownership_lost
assert not record.terminal_status_persisted
@pytest.mark.anyio
@pytest.mark.parametrize("method", ["update_status", "legacy_finalization", "default_finalization"])
async def test_unsupported_owned_store_contract_fails_closed_with_diagnostic(method, caplog):
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
calls = []
async def legacy_write(run_id, status, *, error=None, stop_reason=None, goal_verdict=None):
calls.append(run_id)
return True
if method == "update_status":
store.update_status = legacy_write
assert await manager.persist_current_status(record.run_id) is False
else:
store.finalize_if_not_cancelled = legacy_write if method == "legacy_finalization" else RunStore.finalize_if_not_cancelled.__get__(store)
await manager.set_status_if_not_cancelled(record.run_id, RunStatus.success)
assert calls == []
assert (await store.get(record.run_id))["status"] == "pending"
assert record.ownership_lost
assert record.terminal_commit_proof is None
assert not record.terminal_status_persisted
assert manager._terminal_attempts == {}
assert "owner_worker_id" in caplog.text
@pytest.mark.anyio
async def test_legacy_completion_cannot_bypass_strict_contract(caplog):
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
await manager.set_status_if_not_cancelled(record.run_id, RunStatus.success)
proof = record.terminal_commit_proof
assert proof is not None
calls = []
async def legacy_completion(run_id, *, status, total_tokens=0):
calls.append(run_id)
return True
store.update_run_completion = legacy_completion
await manager.update_run_completion(record.run_id, status="success", total_tokens=5)
assert calls == []
assert (await store.get(record.run_id)).get("total_tokens", 0) == 0
assert record.terminal_commit_proof is proof
assert not record.ownership_lost
assert "require_terminal" in caplog.text
@pytest.mark.anyio
async def test_subsequent_ticks_renew_fast_run_while_slow_check_is_pending():
store = MemoryRunStore()
manager = manager_for(store)
slow = await manager.create_or_reject("slow")
fast = await manager.create_or_reject("fast")
entered, release, twice = asyncio.Event(), asyncio.Event(), asyncio.Event()
original = store.renew_lease
slow_calls, fast_calls = 0, 0
async def renew(run_id, **kwargs):
nonlocal slow_calls, fast_calls
if run_id == slow.run_id:
slow_calls += 1
entered.set()
await release.wait()
result = await original(run_id, **kwargs)
if run_id == fast.run_id:
fast_calls += 1
if fast_calls == 2:
twice.set()
return result
store.renew_lease = renew
try:
await manager._renew_leases(wait=False)
await asyncio.wait_for(entered.wait(), 1)
async with asyncio.timeout(1):
while fast_calls < 1:
await asyncio.sleep(0)
await asyncio.wait_for(asyncio.shield(manager._lease_checks[fast.run_id]), 1)
await manager._renew_leases(wait=False)
await asyncio.wait_for(twice.wait(), 1)
assert slow_calls == 1
assert not manager._lease_checks[slow.run_id].done()
assert not fast.ownership_lost
finally:
release.set()
await manager.stop_heartbeat(timeout=1)
@pytest.mark.anyio
async def test_shutdown_bounds_uncancellable_io_and_observes_late_exception(caplog):
store = MemoryRunStore()
manager = manager_for(store)
record = await manager.create_or_reject("thread")
entered, release = asyncio.Event(), asyncio.Event()
async def resistant(*args, **kwargs):
entered.set()
while not release.is_set():
try:
await release.wait()
except asyncio.CancelledError:
pass
raise RuntimeError("late Store failure")
store.renew_lease = resistant
try:
await manager._renew_leases(wait=False)
await asyncio.wait_for(entered.wait(), 1)
await asyncio.wait_for(manager.stop_heartbeat(timeout=0.01), 0.3)
assert record.run_id in manager._lease_operations
assert "still draining" in caplog.text
release.set()
async with asyncio.timeout(1):
while manager._lease_operations:
await asyncio.sleep(0)
assert "RuntimeError" in caplog.text
finally:
release.set()
await manager.stop_heartbeat(timeout=1)
@pytest.mark.anyio
async def test_staged_terminal_still_receives_durable_cancellation():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
assert await store.request_cancel(record.run_id, action="rollback") == "rollback"
await manager._renew_leases()
assert record.abort_event.is_set()
assert record.abort_action == "rollback"
assert not record.ownership_lost
@pytest.mark.anyio
async def test_completion_cannot_mutate_an_acknowledged_outcome():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
await manager.set_status_if_not_cancelled(record.run_id, RunStatus.success)
await manager.update_run_completion(record.run_id, status="success", error="different outcome")
assert record.error is None
assert (await store.get(record.run_id)).get("error") is None
assert manager._has_terminal_ack(record)
@pytest.mark.anyio
async def test_exited_unconfirmed_worker_does_not_keep_renewing_forever():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
record.task = asyncio.create_task(asyncio.sleep(0))
await record.task
await manager._renew_leases()
assert record.ownership_lost
assert not record.terminal_commit_pending
@pytest.mark.anyio
async def test_barrier_requires_own_ack_only_for_durable_heartbeat_mode():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
assert not manager.can_release_terminal_barrier(record)
memory_only = RunManager(run_ownership_config=RunOwnershipConfig(heartbeat_enabled=True))
assert memory_only.can_release_terminal_barrier(record)
await manager.set_status_if_not_cancelled(record.run_id, RunStatus.success)
assert manager.can_release_terminal_barrier(record)
@pytest.mark.anyio
async def test_older_interrupted_ack_cannot_overwrite_newer_error_commit():
store = MemoryRunStore()
manager = manager_for(store)
record = await manager.create_or_reject("thread")
committed, release = asyncio.Event(), asyncio.Event()
original = store.update_status
async def hold_interrupted(*args, **kwargs):
result = await original(*args, **kwargs)
if args[1] == "interrupted":
committed.set()
await release.wait()
return result
store.update_status = hold_interrupted
first = asyncio.create_task(manager.set_status(record.run_id, RunStatus.interrupted))
try:
await asyncio.wait_for(committed.wait(), 1)
await manager.set_status(record.run_id, RunStatus.error, error="Rolled back by user")
assert record.status == RunStatus.error
release.set()
await asyncio.wait_for(first, 1)
assert record.status == RunStatus.error
assert (await store.get(record.run_id))["status"] == "error"
assert record.error == "Rolled back by user"
assert record.terminal_status_persisted
assert manager._has_terminal_ack(record)
assert not manager._has_terminal_ack(record, manager._terminal_outcome(record, RunStatus.interrupted, error="Rolled back by user"))
assert not record.ownership_lost
assert manager._terminal_attempts == {}
finally:
release.set()
await finish(first)
@pytest.mark.anyio
async def test_attempt_uses_immutable_verdict_snapshot_for_store_and_proof():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
entered, release = asyncio.Event(), asyncio.Event()
original = store.finalize_if_not_cancelled
verdict = {"satisfied": True}
async def delayed(*args, **kwargs):
entered.set()
await release.wait()
return await original(*args, **kwargs)
store.finalize_if_not_cancelled = delayed
commit = asyncio.create_task(manager.set_status_if_not_cancelled(record.run_id, RunStatus.success, goal_verdict=verdict))
try:
await asyncio.wait_for(entered.wait(), 1)
verdict["satisfied"] = False
release.set()
await asyncio.wait_for(commit, 1)
assert (await store.get(record.run_id))["goal_verdict"] == {"satisfied": True}
assert record.goal_verdict == {"satisfied": True}
assert manager._has_terminal_ack(record)
finally:
release.set()
await finish(commit)
@pytest.mark.anyio
async def test_intermediate_ack_cannot_confirm_newer_pending_error():
store = MemoryRunStore()
manager = manager_for(store)
record = await manager.create_or_reject("thread")
first_committed, second_committed = asyncio.Event(), asyncio.Event()
release_first, release_second = asyncio.Event(), asyncio.Event()
original = store.update_status
async def hold_ack(run_id, status, **kwargs):
result = await original(run_id, status, **kwargs)
if status == "interrupted":
first_committed.set()
await release_first.wait()
elif status == "error":
second_committed.set()
await release_second.wait()
return result
store.update_status = hold_ack
first = asyncio.create_task(manager.set_status(record.run_id, RunStatus.interrupted))
second = None
try:
await asyncio.wait_for(first_committed.wait(), 1)
second = asyncio.create_task(manager.set_status(record.run_id, RunStatus.error, error="Rolled back by user"))
await asyncio.wait_for(second_committed.wait(), 1)
release_first.set()
await asyncio.wait_for(first, 1)
assert record.status == RunStatus.error
assert not record.terminal_status_persisted
assert record.terminal_commit_pending
release_second.set()
await asyncio.wait_for(second, 1)
assert record.status == RunStatus.error
assert record.terminal_status_persisted
assert not record.terminal_commit_pending
assert not record.ownership_lost
finally:
release_first.set()
release_second.set()
await finish(first, *([second] if second else []))
@pytest.mark.anyio
async def test_acknowledged_final_outcome_rejects_late_conflicting_local_status():
store = MemoryRunStore()
manager = manager_for(store)
record = await staged(manager)
await manager.set_status_if_not_cancelled(record.run_id, RunStatus.success)
await manager.set_status(record.run_id, RunStatus.error, error="late cancellation")
await manager.set_status_if_not_cancelled(record.run_id, RunStatus.error, error="late failure")
assert record.status == RunStatus.success
assert (await store.get(record.run_id))["status"] == "success"
assert manager._has_terminal_ack(record)
assert not record.ownership_lost