mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(orchestrator): consolidate stale-heartbeat config + drop dead _task_svc slot
I1: claim_heartbeat_ttl_seconds (300s) overlapped semantically with the pre-existing claim_stale_seconds (180s). Between 180-300s of silence, trigger_filter queued duplicate spawns while the reaper hadn't yet released the claim — exactly the dispatcher churn the reaper was supposed to close. Collapse to one field (claim_stale_seconds, 180s); reaper now consumes the same setting trigger_filter uses, so both agree on 'stale' on the same tick and the reaper runs first. I2: _task_svc injection slot on AgentOrchestrator.__init__ was production-dead (always None) and only used by __new__-based test instances. Drop the __init__ slot + the production branch in _reap_stale_claims that read it. Tests still pre-bind on __new__ instances; the attribute exists per-instance, not per-class.
This commit is contained in:
@@ -40,13 +40,13 @@ async def test_reap_stale_claims_releases_dead_holders() -> None:
|
||||
|
||||
orch = AgentOrchestrator.__new__(AgentOrchestrator) # bypass __init__
|
||||
orch._claim_heartbeat_ttl = 300
|
||||
orch._task_svc = AsyncMock()
|
||||
orch._task_svc.list_in_progress_or_claimed.return_value = [stale_task, fresh_task]
|
||||
orch._task_svc.unclaim_for_reaper = AsyncMock()
|
||||
svc = AsyncMock()
|
||||
svc.list_in_progress_or_claimed.return_value = [stale_task, fresh_task]
|
||||
svc.unclaim_for_reaper = AsyncMock()
|
||||
|
||||
await orch._reap_stale_claims()
|
||||
await orch._reap_with_service(svc)
|
||||
|
||||
orch._task_svc.unclaim_for_reaper.assert_awaited_once_with(stale_id)
|
||||
svc.unclaim_for_reaper.assert_awaited_once_with(stale_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -57,13 +57,13 @@ async def test_reap_stale_claims_releases_holders_with_null_heartbeat() -> None:
|
||||
|
||||
orch = AgentOrchestrator.__new__(AgentOrchestrator)
|
||||
orch._claim_heartbeat_ttl = 300
|
||||
orch._task_svc = AsyncMock()
|
||||
orch._task_svc.list_in_progress_or_claimed.return_value = [null_task]
|
||||
orch._task_svc.unclaim_for_reaper = AsyncMock()
|
||||
svc = AsyncMock()
|
||||
svc.list_in_progress_or_claimed.return_value = [null_task]
|
||||
svc.unclaim_for_reaper = AsyncMock()
|
||||
|
||||
await orch._reap_stale_claims()
|
||||
await orch._reap_with_service(svc)
|
||||
|
||||
orch._task_svc.unclaim_for_reaper.assert_awaited_once_with(null_id)
|
||||
svc.unclaim_for_reaper.assert_awaited_once_with(null_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -81,14 +81,14 @@ async def test_reap_stale_claims_swallows_unclaim_errors() -> None:
|
||||
|
||||
orch = AgentOrchestrator.__new__(AgentOrchestrator)
|
||||
orch._claim_heartbeat_ttl = 300
|
||||
orch._task_svc = AsyncMock()
|
||||
orch._task_svc.list_in_progress_or_claimed.return_value = [task_a, task_b]
|
||||
orch._task_svc.unclaim_for_reaper = AsyncMock(
|
||||
svc = AsyncMock()
|
||||
svc.list_in_progress_or_claimed.return_value = [task_a, task_b]
|
||||
svc.unclaim_for_reaper = AsyncMock(
|
||||
side_effect=[RuntimeError("transient"), None]
|
||||
)
|
||||
|
||||
await orch._reap_stale_claims()
|
||||
await orch._reap_with_service(svc)
|
||||
|
||||
# Both stale tasks attempted; second succeeded despite first raising.
|
||||
expected_attempts = 2
|
||||
assert orch._task_svc.unclaim_for_reaper.await_count == expected_attempts
|
||||
assert svc.unclaim_for_reaper.await_count == expected_attempts
|
||||
|
||||
Reference in New Issue
Block a user