Service node ends its call when the loop cancels every task; test order and timing leaks
_serve_once treated a cancelled call future as finished, so an event loop cancelling all tasks at once (shutdown) never sent the worker a cancel and the call's thread kept asyncio.run waiting for SILENCE_S. The cameras audio test now removes its voice flow and device, the unpaired-frame test waits for its first frame before the unpair, and test_connector no longer inherits the controller an app test bound to demand. Claude-Session: https://claude.ai/code/session_01SSi5AvZ7pFmySK8zy3NPMh
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
"""The service node: a server on a worker, restarted when it ends, its url following."""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
|
||||
from fluksio.flow import remote
|
||||
from fluksio.flow.messages import DType, MessageSpec
|
||||
from fluksio.flow.nodes import ServiceNode, service
|
||||
from fluksio.flow.pipeline import Pipeline
|
||||
@@ -99,6 +101,49 @@ def test_a_service_comes_back_and_its_url_follows(monkeypatch):
|
||||
asyncio.run(scenario())
|
||||
|
||||
|
||||
def test_a_service_cancelled_with_its_loop_ends_its_call(monkeypatch):
|
||||
"""A loop that cancels every task at once must not strand the call's thread.
|
||||
|
||||
The call's own future is cancelled with the rest, so it reads done while the
|
||||
thread still waits for the worker, which then keeps the loop's shutdown
|
||||
waiting until the silence bound.
|
||||
"""
|
||||
monkeypatch.setattr(remote, "SILENCE_S", 3.0)
|
||||
|
||||
async def healthy(client, url):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(ServiceNode, "_probe", staticmethod(healthy))
|
||||
agent = Agent()
|
||||
|
||||
async def scenario() -> None:
|
||||
agent.worker = RemoteWorker(
|
||||
name="gpu-worker",
|
||||
labels=["gpu"],
|
||||
send=agent.send_json,
|
||||
loop=asyncio.get_running_loop(),
|
||||
host="192.0.2.5",
|
||||
)
|
||||
hub = RemoteWorkerHub()
|
||||
hub.attach(agent.worker)
|
||||
node = ServiceNode(
|
||||
provides=[MessageSpec(name="url", dtype=DType.STR)],
|
||||
params={"command": "serve", "worker": "gpu"},
|
||||
)
|
||||
node.assign_flow("voice", "parrot")
|
||||
pipeline = Pipeline(nodes=[node])
|
||||
node.hub = hub
|
||||
await node.start()
|
||||
await until(lambda: bool(agent.serves()))
|
||||
await until(lambda: pipeline.state.get("voice.url") == "http://192.0.2.5:8100")
|
||||
# No stop: asyncio.run cancels the node's task and the call together.
|
||||
|
||||
started = time.monotonic()
|
||||
asyncio.run(scenario())
|
||||
assert time.monotonic() - started < 2
|
||||
assert [frame["op"] for frame in agent.sent] == ["serve", "cancel"]
|
||||
|
||||
|
||||
def test_a_service_that_never_gets_healthy_reads_down(monkeypatch):
|
||||
monkeypatch.setattr(service, "BACKOFF", (0.01,))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user