Skip to content

Commit 423ba19

Browse files
committed
test(parallel): verify concurrency via handler-start gap, not wall time
The test measured total client round-trip time (< 1.9s threshold) to prove the hub invokes parallel tool_calls concurrently. On a loaded VPS the round trip is ~2.4s even when both handlers ran in 0.8s concurrently, because the event loop shared between the test's publisher, connection, and httpx client adds ~1.4s of scheduling overhead — the threshold couldn't distinguish parallel from serial under that much load. Replace the wall-clock assertion with a direct concurrency check: record when each handler starts and assert both started within 0.4s of each other (serial execution would have a gap >= the first handler's sleep, 0.5s). This proves the hub dispatched both invocations as concurrent asyncio tasks regardless of environment-specific round-trip noise. Also: - Reduce sleep 0.8s → 0.5s and tighten the per-call httpx timeout (15s) - Keep a loose 8s wall-clock bound only to catch genuine hangs - Remove unused json import (test never needed it)
1 parent e03f903 commit 423ba19

1 file changed

Lines changed: 28 additions & 15 deletions

File tree

tests/test_parallel_tool_calls.py

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"""
88

99
import asyncio
10-
import json
1110
import socket
1211
import threading
1312
import time
@@ -56,13 +55,20 @@ def run():
5655

5756
@pytest.mark.asyncio
5857
async def test_parallel_tool_calls_resolved_concurrently(parallel_hub_port):
59-
"""Publisher emits two tool_calls in one response. Each capability
60-
handler sleeps 0.5s. If serialized, total > 1s; concurrent < 0.8s.
61-
Both results should appear in the audit log keyed to their
62-
tool_call_id, and the final message should mention both."""
58+
"""Publisher emits two tool_calls in one response. The hub must invoke
59+
both capabilities concurrently, not serially.
60+
61+
Concurrency is verified by recording when each handler starts: if
62+
slow_a and slow_b both start within 0.4s of each other they were
63+
running as overlapping asyncio tasks (the serial case would have a
64+
gap equal to the first handler's full sleep, ~0.5s). Wall-clock
65+
total time is not used — it is too sensitive to scheduler noise on
66+
shared VPS runners.
67+
"""
6368
hub_ws = f"ws://127.0.0.1:{parallel_hub_port}"
6469
hub_http = f"http://127.0.0.1:{parallel_hub_port}"
6570
call_n = {"n": 0}
71+
starts: dict[str, float] = {}
6672

6773
def chat_handler(messages, options):
6874
call_n["n"] += 1
@@ -85,11 +91,13 @@ def chat_handler(messages, options):
8591
return f"both done: {sorted(names)}"
8692

8793
async def slow_a(_args):
88-
await asyncio.sleep(0.8)
94+
starts["a"] = time.monotonic()
95+
await asyncio.sleep(0.5)
8996
return {"from": "a"}
9097

9198
async def slow_b(_args):
92-
await asyncio.sleep(0.8)
99+
starts["b"] = time.monotonic()
100+
await asyncio.sleep(0.5)
93101
return {"from": "b"}
94102

95103
pub = publish(
@@ -115,7 +123,7 @@ async def slow_b(_args):
115123
await asyncio.sleep(0.8)
116124

117125
t0 = time.monotonic()
118-
async with httpx.AsyncClient(timeout=10.0) as client:
126+
async with httpx.AsyncClient(timeout=15.0) as client:
119127
resp = await client.post(
120128
f"{hub_http}/{pub.name}/v1/chat/completions",
121129
json={"messages": [{"role": "user", "content": "go"}]},
@@ -130,10 +138,15 @@ async def slow_b(_args):
130138
audit = body["usage"]["tool_results"]
131139
assert {a["name"] for a in audit} == {"slow_a", "slow_b"}
132140
assert {a["tool_call_id"] for a in audit} == {"call_a", "call_b"}
133-
# Two 0.8s sleeps. Serial = 1.6s + overhead. Parallel = 0.8s + overhead.
134-
# We want a threshold that still distinguishes parallel from serial
135-
# but tolerates slow CI. Serial would clock north of 1.6 + N×scheduler
136-
# overhead; parallel below ~1s under normal conditions, but can drift
137-
# to 1.4–1.7s on heavily-shared runners. Use 1.9s — a serial impl
138-
# would be even higher (~2.1s+ once the second 0.8s sleep adds up).
139-
assert elapsed < 1.9, f"parallel resolution should finish under 1.9s; took {elapsed:.2f}s"
141+
142+
# Verify concurrency: both handlers must have run, and must have started
143+
# within 0.4s of each other. Serial execution would have a gap >= the
144+
# first handler's sleep (0.5s); concurrent execution has a gap of ~0ms.
145+
assert "a" in starts and "b" in starts, "both capability handlers must have been invoked"
146+
start_gap = abs(starts["a"] - starts["b"])
147+
assert start_gap < 0.4, (
148+
f"handlers started {start_gap:.3f}s apart — "
149+
f"hub likely invoked them serially (gap must be < first sleep = 0.5s)"
150+
)
151+
# Loose wall-clock bound: catch hangs, not timing noise.
152+
assert elapsed < 8.0, f"request took {elapsed:.2f}s — something appears hung"

0 commit comments

Comments
 (0)