fix(tests): make the mcp task service suite deterministic on Windows (#5995)

Follow-up to #5940/#5941: with the hang gone, 12 tests further down the
file fail on native Windows. One disease, several signatures: the tests
patch _CANCELLATION_DRAIN_TIMEOUT_SECONDS to 0.01, but on Windows +
CPython 3.12 a 10ms wait_for over an already-created task times out
immediately (Windows timer granularity, ~15.6ms default tick), so
instant claims are abandoned to the background handoff and every
claim-completes-in-the-foreground premise breaks - abandoned fresh
claims (single_flight), cancellation landing after run_once already
returned (repeated_outer), releases that never happen in the
foreground (poll_retry / notification_failure).

- raise the patched drain 0.01 -> 0.05 in the five affected tests:
  clears the granularity tick with headroom, keeps every existing 0.2s
  bound valid, and the gate-blocked-claim abandonment the single-flight
  tests exercise still drains-timeout as designed
- loosen test_run_once_releases_claim_when_driver_is_missing_or_fails'
  strict next_poll_at > now + 5s to >=: Windows datetime.now() can
  return the same tick for the whole run_once, making the strict
  comparison a clock artifact; the backoff floor is the invariant

On Windows the file now passes 126/126 in ~20s (was 12 failed). Linux
semantics unchanged: claims complete in microseconds inside a 50ms
drain there.

Fixes #5993
This commit is contained in:
xiaodu55 2026-09-28 22:47:29 +08:00 committed by GitHub
parent b87ebd9402
commit 8a3a309d1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1671,7 +1671,9 @@ async def test_run_once_releases_claim_when_driver_is_missing_or_fails():
assert "No MCP task driver registered" in released["task-1"]["error"]
assert released["task-2"]["error"] == "network down"
assert released["task-1"]["next_poll_at"] == now + timedelta(seconds=5)
assert released["task-2"]["next_poll_at"] > now + timedelta(seconds=5)
# Windows datetime.now() can return the same tick for the whole run_once,
# so the retry timestamp may equal now + 5s exactly; only the backoff floor matters.
assert released["task-2"]["next_poll_at"] >= now + timedelta(seconds=5)
@pytest.mark.asyncio
@ -2616,7 +2618,7 @@ async def test_cancelled_hung_claim_returns_then_releases_delayed_result(monkeyp
@pytest.mark.asyncio
@pytest.mark.parametrize("phase", ["poll", "cancel", "notification"])
async def test_single_flight_claim_releases_late_uncancelled_claim(phase, monkeypatch):
monkeypatch.setattr(service_module, "_CANCELLATION_DRAIN_TIMEOUT_SECONDS", 0.01)
monkeypatch.setattr(service_module, "_CANCELLATION_DRAIN_TIMEOUT_SECONDS", 0.05)
class BlockingClaimRepository:
def __init__(self):
@ -2739,7 +2741,7 @@ async def test_single_flight_claim_skip_logs_unresolved_owner(caplog):
@pytest.mark.asyncio
@pytest.mark.parametrize("phase", ["poll", "cancel", "notification"])
async def test_single_flight_claim_releases_owner_before_stuck_release(phase, monkeypatch):
monkeypatch.setattr(service_module, "_CANCELLATION_DRAIN_TIMEOUT_SECONDS", 0.01)
monkeypatch.setattr(service_module, "_CANCELLATION_DRAIN_TIMEOUT_SECONDS", 0.05)
class BlockingClaimAndReleaseRepository:
def __init__(self):
@ -3695,7 +3697,7 @@ async def test_ordinary_batch_release_completion_same_tick_is_terminal(monkeypat
@pytest.mark.asyncio
@pytest.mark.parametrize("phase", ["poll", "cancel", "notification"])
async def test_repeated_outer_cancellation_keeps_one_ordinary_release(phase, monkeypatch):
monkeypatch.setattr(service_module, "_CANCELLATION_DRAIN_TIMEOUT_SECONDS", 0.01)
monkeypatch.setattr(service_module, "_CANCELLATION_DRAIN_TIMEOUT_SECONDS", 0.05)
repo = OrdinaryReleaseBatchRepository(phase=phase, outcome="success")
drivers = McpTaskDriverRegistry()
if phase == "poll":
@ -3904,7 +3906,7 @@ async def test_same_tick_outer_claim_cancellation_wins_and_preserves_args():
async def test_poll_retry_release_hang_does_not_block_run_once(monkeypatch):
import app.mcp_tasks.service as service_module
monkeypatch.setattr(service_module, "_CANCELLATION_DRAIN_TIMEOUT_SECONDS", 0.01, raising=False)
monkeypatch.setattr(service_module, "_CANCELLATION_DRAIN_TIMEOUT_SECONDS", 0.05, raising=False)
class HangingReleaseRepo(FakeRepository):
def __init__(self):
@ -3942,7 +3944,7 @@ async def test_poll_retry_release_hang_does_not_block_run_once(monkeypatch):
async def test_notification_failure_release_hang_does_not_block(monkeypatch):
import app.mcp_tasks.service as service_module
monkeypatch.setattr(service_module, "_CANCELLATION_DRAIN_TIMEOUT_SECONDS", 0.01, raising=False)
monkeypatch.setattr(service_module, "_CANCELLATION_DRAIN_TIMEOUT_SECONDS", 0.05, raising=False)
class HangingNotificationReleaseRepo:
def __init__(self, records):