"""Inbound mode end to end: a panel dials a node and runs a task on it. Everything else about this feature can pass unit tests while the thing itself does not connect — which is exactly how this subsystem has failed before. These tests stand up a real listener on a real socket, dial it with the real connector, and assert on the state the scheduler ends up in. """ from __future__ import annotations import asyncio import hashlib import os import socket import sqlite3 import threading from dataclasses import replace from types import SimpleNamespace import pytest import pytest_asyncio ENGINE, MODEL, OP = "indextts", "IndexTTS-2", "tts" def _worker_modules(): """Resolve app modules at test runtime, after isolation fixtures run.""" from worker import registry, tls from worker.identity import WorkerKeypair from worker.inbound.artifacts import ArtifactStore from worker.inbound.connection_log import ConnectionLog from worker.inbound.connection_string import format_connection, parse_connection from worker.inbound.connector import NodeConnection from worker.inbound.keys import KeyStore from worker.inbound.listener import NodeListener from worker.pool import WorkerPool from worker.scheduler import Scheduler from worker.transport.client import WorkerClient, WorkerConfig from worker.transport.server import WorkerServicer return SimpleNamespace( ArtifactStore=ArtifactStore, ConnectionLog=ConnectionLog, KeyStore=KeyStore, NodeConnection=NodeConnection, NodeListener=NodeListener, Scheduler=Scheduler, WorkerClient=WorkerClient, WorkerConfig=WorkerConfig, WorkerKeypair=WorkerKeypair, WorkerPool=WorkerPool, WorkerServicer=WorkerServicer, format_connection=format_connection, parse_connection=parse_connection, registry=registry, tls=tls, ) @pytest.fixture def db(tmp_path, monkeypatch): from worker import registry as reg db_globals = reg.db_conn.__wrapped__.__globals__ path = str(tmp_path / "userdata.db") with sqlite3.connect(path) as conn: conn.executescript(db_globals["_BASE_SCHEMA"]) monkeypatch.setitem(db_globals, "DB_PATH", path) return path def _free_port() -> int: with socket.socket() as sock: sock.bind(("127.0.0.1", 0)) return sock.getsockname()[1] def _capabilities(): return [ { "engine": ENGINE, "model_id": MODEL, "operations": [OP], "supported": True, "installed": True, "downloaded": True, "resident": False, "derived_concurrency": 1, "repo_ids": ["test/repo"], } ] class _ObservedConnectionLog: """ConnectionLog with concrete async signals instead of wall-clock waits.""" def __init__(self, *, now): self._inner = _worker_modules().ConnectionLog(now=now) self.rejected_event = asyncio.Event() self.closed_event = asyncio.Event() def __getattr__(self, name): return getattr(self._inner, name) def rejected(self, *, peer, detail): self._inner.rejected(peer=peer, detail=detail) self.rejected_event.set() def closed(self, session_id, *, detail=""): self._inner.closed(session_id, detail=detail) self.closed_event.set() class _InboundHarness: """A node listening on loopback, plus the panel that dials it.""" def __init__(self, tmp_path): worker = _worker_modules() self.worker = worker # Node side. self.keys = worker.KeyStore(str(tmp_path / "inbound-keys.json")) self.clock = [100.0] self.log = _ObservedConnectionLog(now=lambda: self.clock[0]) self.artifacts = worker.ArtifactStore(str(tmp_path / "staged")) self.keypair = worker.WorkerKeypair.generate() self.credentials = worker.tls.generate_self_signed( hostnames=["localhost", "127.0.0.1"] ) self.executed: list[str] = [] self.listener = worker.NodeListener( keys=self.keys, log=self.log, artifacts=self.artifacts, client_factory=self._client, credentials=self.credentials, ) # Panel side. self.pool = worker.WorkerPool() self.scheduler = worker.Scheduler(self.pool, persist=False) self.servicer = worker.WorkerServicer( self.scheduler, self.pool, artifact_dir=str(tmp_path / "artifacts") ) self.connection = None self.connector_task = None self.panel_key_id = "" self.port = 0 def _client(self, artifacts, key_id): async def execute(assignment, **kwargs): self.executed.append(assignment.ref.task_id) return {"result_json": "{}", "payload": b"", "meta": {}} return self._client_for(artifacts, key_id, execute) def _client_for(self, artifacts, key_id, execute): return self.worker.WorkerClient( self.worker.WorkerConfig( endpoint="", cert_fingerprint="", certificate_pem=b"", keypair=self.keypair, worker_id=self.keys.worker_id_for(key_id), enrollment_token="", max_concurrent_tasks=1, capabilities=_capabilities(), host={ "hostname": "gpu-node", "os": "linux", "arch": "x86_64", "gpus": [], }, ), execute=execute, artifacts=artifacts, on_registered=lambda wid: self.keys.remember_worker_id(key_id, wid), ) async def start_node(self): self.port = await self.listener.start(host="127.0.0.1", port=_free_port()) return self.port async def connect_panel(self, secret=None, *, wait=True): if secret is None: issued = self.keys.issue("Test panel") secret = issued.secret self.panel_key_id = issued.key.key_id else: from worker.identity import hash_secret self.panel_key_id = hash_secret(secret)[:12] text = self.worker.format_connection( host="127.0.0.1", port=self.port, secret=secret, fingerprint=self.credentials.fingerprint, ) connection = self.worker.parse_connection(text) self.connection = self.worker.NodeConnection(self.servicer, connection) self.connector_task = asyncio.create_task(self.connection.run_forever()) if wait: def worker_is_activated(): if len(self.pool) != 1: return False live = next(iter(self.pool)) return ( live.record.schedulable and not live.registration_pending and live.supports(engine=ENGINE, model_id=MODEL, operation=OP) ) await _until(worker_is_activated) return self.connection async def stop(self): if self.connection is not None: # Fixture teardown is a local transport cleanup, not a request to # prove that the remote node durably acknowledged shutdown. A # test may deliberately leave that node unavailable. close = getattr(self.connection, "close", None) if callable(close): await close() else: await self.connection.stop() if self.connector_task is not None: self.connector_task.cancel() await asyncio.gather(self.connector_task, return_exceptions=True) await self.listener.stop() async def _until(predicate, timeout=5.0): deadline = asyncio.get_running_loop().time() + timeout while asyncio.get_running_loop().time() < deadline: if predicate(): return True await asyncio.sleep(0.02) raise AssertionError("condition never became true") @pytest_asyncio.fixture async def inbound(tmp_path, db): h = _InboundHarness(tmp_path) await h.start_node() try: yield h finally: await h.stop() @pytest.mark.asyncio async def test_a_panel_that_dials_a_node_ends_up_with_a_schedulable_worker(inbound): """The whole feature in one assertion: paste a string, get a usable GPU.""" await inbound.connect_panel() assert len(inbound.pool) == 1 worker = next(iter(inbound.pool)) assert worker.record.schedulable is True assert worker.registration_pending is False assert worker.capacity.can_accept(ENGINE, MODEL) # Capabilities crossed the inverted stream, so the scheduler can actually # pick this worker rather than merely knowing it exists. assert worker.supports(engine=ENGINE, model_id=MODEL, operation=OP) @pytest.mark.asyncio async def test_reconnect_claims_the_same_running_inbound_execution(inbound): issued = inbound.keys.issue("Test panel") execution_started = asyncio.Event() release_execution = asyncio.Event() executions = [] async def execute(assignment, **_kwargs): executions.append(assignment.ref.task_id) execution_started.set() await release_execution.wait() return {"result_json": "{}", "payload": b"", "meta": {}} inbound.listener._servicer._client_factory = ( lambda artifacts, key_id: inbound._client_for(artifacts, key_id, execute) ) await inbound.connect_panel(secret=issued.secret) task = inbound.scheduler.submit(operation=OP, engine=ENGINE, model_id=MODEL) assignment = inbound.scheduler.next_assignment() assert assignment is not None assert await inbound.servicer.dispatch(assignment) await asyncio.wait_for(execution_started.wait(), timeout=2) inbound.connector_task.cancel() await asyncio.gather(inbound.connector_task, return_exceptions=True) await _until(lambda: len(inbound.pool) == 0) await inbound.connect_panel(secret=issued.secret) live = next(iter(inbound.pool)) assert live.capacity.active_tasks == 1 release_execution.set() await _until(lambda: task.state.value == "completed") assert executions == [task.task_id] @pytest.mark.asyncio async def test_reconnect_redelivers_an_inbound_result_not_yet_acknowledged( inbound, monkeypatch ): issued = inbound.keys.issue("Test panel") await inbound.connect_panel(secret=issued.secret) result_seen = asyncio.Event() hold_result = asyncio.Event() blocked_once = False real_handle = inbound.servicer._handle async def interrupt_first_result(session, message): nonlocal blocked_once if message.WhichOneof("payload") == "result" and not blocked_once: blocked_once = True result_seen.set() await hold_result.wait() return await real_handle(session, message) monkeypatch.setattr(inbound.servicer, "_handle", interrupt_first_result) task = inbound.scheduler.submit(operation=OP, engine=ENGINE, model_id=MODEL) assignment = inbound.scheduler.next_assignment() assert assignment is not None assert await inbound.servicer.dispatch(assignment) await asyncio.wait_for(result_seen.wait(), timeout=2) inbound.connector_task.cancel() await asyncio.gather(inbound.connector_task, return_exceptions=True) await _until(lambda: len(inbound.pool) == 0) hold_result.set() await inbound.connect_panel(secret=issued.secret) await _until(lambda: task.state.value == "completed") protocol = inbound.listener._servicer._protocols[inbound.panel_key_id] await _until(lambda: not protocol._pending) assert inbound.executed == [task.task_id] @pytest.mark.asyncio async def test_lost_result_ack_refetch_keeps_the_committed_inbound_artifact( inbound, monkeypatch ): from worker.inbound.artifacts import KeyedArtifactTransport cleanup_done = asyncio.Event() real_cleanup = KeyedArtifactTransport.result_acked_async async def observe_cleanup(transport, artifacts): await real_cleanup(transport, artifacts) cleanup_done.set() monkeypatch.setattr(KeyedArtifactTransport, "result_acked_async", observe_cleanup) issued = inbound.keys.issue("Test panel") payload = b"rendered audio" * 24_000 async def execute(assignment, **_kwargs): inbound.executed.append(assignment.ref.task_id) return { "result_json": "{}", "payload": payload, "meta": {"filename": "result.wav", "content_type": "audio/wav"}, } inbound.listener._servicer._client_factory = ( lambda artifacts, key_id: inbound._client_for(artifacts, key_id, execute) ) await inbound.connect_panel(secret=issued.secret) ack_dropped = asyncio.Event() real_handle = inbound.servicer._handle dropped = False async def lose_first_result_ack(session, message): nonlocal dropped await real_handle(session, message) if message.WhichOneof("payload") != "result" or dropped: return for index, queued in enumerate(session.outbox._queue): if queued.WhichOneof("payload") == "result_ack": del session.outbox._queue[index] dropped = True ack_dropped.set() break monkeypatch.setattr(inbound.servicer, "_handle", lose_first_result_ack) task = inbound.scheduler.submit(operation=OP, engine=ENGINE, model_id=MODEL) assignment = inbound.scheduler.next_assignment() assert assignment is not None assert await inbound.servicer.dispatch(assignment) await asyncio.wait_for(ack_dropped.wait(), timeout=5) assert task.state.value == "completed" committed = task.result_ref assert committed and open(committed, "rb").read() == payload protocol = inbound.listener._servicer._protocols[inbound.panel_key_id] pending = next(iter(protocol._pending.values())) artifact_id = pending.artifacts[0].artifact_id assert inbound.artifacts.open_result( artifact_id, key_id=inbound.panel_key_id ) is not None inbound.connector_task.cancel() await asyncio.gather(inbound.connector_task, return_exceptions=True) await _until(lambda: len(inbound.pool) == 0) await inbound.connect_panel(secret=issued.secret) await _until(lambda: not protocol._pending) assert open(committed, "rb").read() == payload # The ACK removes the pending item before awaiting filesystem cleanup. # Observe completion of that asynchronous operation, not its queue marker. await asyncio.wait_for(cleanup_done.wait(), timeout=5) assert inbound.artifacts.open_result( artifact_id, key_id=inbound.panel_key_id ) is None assert inbound.executed == [task.task_id] @pytest.mark.asyncio async def test_a_panel_refuses_a_node_whose_tls_certificate_misses_the_pin(inbound): secret = inbound.keys.issue("Test panel").secret connection = inbound.worker.parse_connection( inbound.worker.format_connection( host="127.0.0.1", port=inbound.port, secret=secret, fingerprint=inbound.credentials.fingerprint, ) ) impostor_pin = "0" * 64 assert impostor_pin != inbound.credentials.fingerprint dialer = inbound.worker.NodeConnection( inbound.servicer, replace(connection, fingerprint=impostor_pin) ) with pytest.raises(RuntimeError, match="certificate fingerprint"): await asyncio.wait_for(dialer._connect_once(), timeout=2.0) assert len(inbound.pool) == 0 @pytest.mark.asyncio async def test_the_node_is_enrolled_by_its_own_key_not_by_the_api_key(inbound): """The API key admits a panel; identity stays with the node's keypair. If these were conflated, anyone who copied the key could impersonate the machine to a panel that had already trusted it.""" await inbound.connect_panel() stored = inbound.worker.registry.list_workers() assert len(stored) == 1 assert stored[0].key_id == inbound.keypair.key_id @pytest.mark.asyncio async def test_two_panels_can_use_one_node_at_the_same_time(tmp_path, db, inbound): """The reason inbound exists. Outbound is 1:1 by construction, so this is the case it can never serve.""" second = _InboundHarness(tmp_path / "second") # A second panel, its own scheduler and registry view, same node. second.listener = inbound.listener second.port = inbound.port await inbound.connect_panel() alice = next(iter(inbound.pool)).worker_id bob_secret = inbound.keys.issue("Bob").secret connection = inbound.worker.parse_connection( inbound.worker.format_connection( host="127.0.0.1", port=inbound.port, secret=bob_secret, fingerprint=inbound.credentials.fingerprint, ) ) bob = inbound.worker.NodeConnection(second.servicer, connection) task = asyncio.create_task(bob.run_forever()) try: await _until(lambda: len(second.pool) == 1) assert next(iter(second.pool)).worker_id == alice # Both sessions are live on the node at once, which is what a # one-at-a-time design would have prevented. assert len(inbound.log.snapshot()["sessions"]) == 2 finally: await bob.stop() task.cancel() await asyncio.gather(task, return_exceptions=True) @pytest.mark.asyncio async def test_a_panel_with_no_key_never_reaches_the_worker_pool(inbound): await inbound.connect_panel(secret="ovnode_" + "z" * 40, wait=False) await asyncio.wait_for(inbound.log.rejected_event.wait(), timeout=2.0) assert len(inbound.pool) == 0 kinds = [e["kind"] for e in inbound.log.snapshot()["events"]] assert "rejected" in kinds @pytest.mark.asyncio async def test_terminal_panel_registration_refusal_is_not_retried( inbound, monkeypatch ): from worker.transport.client import TerminalRegistrationError connection = inbound.worker.NodeConnection( inbound.servicer, SimpleNamespace(host="gpu-node") ) attempts = 0 async def refuse_once(): nonlocal attempts attempts += 1 raise TerminalRegistrationError("AUTH_FAILED: registration rejected") monkeypatch.setattr(connection, "_connect_once", refuse_once) with pytest.raises(TerminalRegistrationError, match="AUTH_FAILED"): await asyncio.wait_for(connection.run_forever(), timeout=0.1) assert attempts == 1 assert connection.last_error.startswith("AUTH_FAILED") @pytest.mark.asyncio async def test_inbound_worker_id_persistence_failure_is_terminal( inbound, monkeypatch ): from worker.transport.client import TerminalRegistrationError attempts = 0 def fail_persistence(_key_id, _worker_id): nonlocal attempts attempts += 1 raise OSError("disk full") monkeypatch.setattr(inbound.keys, "remember_worker_id", fail_persistence) connection = await inbound.connect_panel(wait=False) with pytest.raises(TerminalRegistrationError, match="LOCAL_STATE"): await asyncio.wait_for(inbound.connector_task, timeout=2) assert attempts == 1 assert len(inbound.pool) == 0 assert inbound.servicer._sessions == {} assert connection.last_error.startswith("LOCAL_STATE") @pytest.mark.asyncio async def test_registration_confirmation_timeout_discards_the_provisional_session( monkeypatch, ): """A node silent after Register must release its handshake and retry.""" from worker.inbound import connector as connector_module from worker.protocol.gen import worker_v1_pb2 as pb class Stream: def __init__(self): self.reads = 0 async def read(self): self.reads += 1 if self.reads == 1: return pb.WorkerMessage(register=pb.RegisterRequest()) await asyncio.Event().wait() stream = Stream() class Stub: def Attach(self, _frames, metadata=()): return stream class Channel: async def __aenter__(self): return object() async def __aexit__(self, *_args): return None discarded = [] servicer = SimpleNamespace( discard_unopened_session=lambda worker_id, *, session_token="": discarded.append( (worker_id, session_token) ) ) connection = connector_module.NodeConnection( servicer, SimpleNamespace( host="gpu-node", endpoint="gpu-node:7444", secret="ovnode_test", ), ) response = pb.RegisterResponse(worker_id="worker-1", session_token="session-1") monkeypatch.setattr(connector_module, "_fetch_pinned_certificate", lambda _c: b"cert") monkeypatch.setattr(connector_module.pb_grpc, "NodeServiceStub", lambda _c: Stub()) monkeypatch.setattr( connector_module, "_REGISTRATION_CONFIRMATION_TIMEOUT_SECONDS", 0.01 ) monkeypatch.setattr(connection, "_channel", lambda _certificate: Channel()) async def register(_request): return response monkeypatch.setattr(connection, "_register", register) with pytest.raises(RuntimeError, match="did not confirm registration"): await asyncio.wait_for(connection._connect_once(), timeout=1.0) assert discarded == [("worker-1", "session-1")] assert connection.worker_id == "" @pytest.mark.asyncio async def test_missing_exact_registration_session_is_never_reported_connected( monkeypatch, ): from worker.inbound import connector as connector_module from worker.protocol.gen import worker_v1_pb2 as pb frames = iter( [ pb.WorkerMessage(register=pb.RegisterRequest()), pb.WorkerMessage(heartbeat=pb.Heartbeat()), ] ) class Stream: async def read(self): return next(frames) class Stub: def Attach(self, _frames, metadata=()): return Stream() class Channel: async def __aenter__(self): return object() async def __aexit__(self, *_args): return None servicer = SimpleNamespace( discard_unopened_session=lambda *_args, **_kwargs: None, session_for=lambda *_args, **_kwargs: None, ) connection = connector_module.NodeConnection( servicer, SimpleNamespace( host="gpu-node", endpoint="gpu-node:7444", secret="ovnode_test", ), ) response = pb.RegisterResponse(worker_id="worker-1", session_token="session-1") monkeypatch.setattr(connector_module, "_fetch_pinned_certificate", lambda _c: b"cert") monkeypatch.setattr(connector_module.pb_grpc, "NodeServiceStub", lambda _c: Stub()) monkeypatch.setattr(connection, "_channel", lambda _certificate: Channel()) async def register(_request): return response monkeypatch.setattr(connection, "_register", register) with pytest.raises(RuntimeError, match="session went away"): await connection._connect_once() assert connection.worker_id == "" assert connection._stub is None @pytest.mark.asyncio async def test_attach_ends_when_its_incoming_reader_stops(tmp_path, monkeypatch): """A dead reader must not leave a node advertising a healthy session.""" from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() class FakeClient: def prepare_inbound_session(self): pass def build_register_request(self): return pb.RegisterRequest() async def accept_registration(self, _response): pass def heartbeat_message(self): return pb.WorkerMessage(heartbeat=pb.Heartbeat()) def start_heartbeat(self, _response): return asyncio.create_task(asyncio.sleep(60)) async def next_outbound(self): await asyncio.Event().wait() async def stop(self): pass class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return () async def abort(self, code, message): raise AssertionError(f"unexpected abort: {code}: {message}") servicer = worker.NodeListener( keys=worker.KeyStore(str(tmp_path / "keys.json")), log=worker.ConnectionLog(), artifacts=worker.ArtifactStore(str(tmp_path / "staged")), client_factory=lambda _artifacts, _key_id: FakeClient(), credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer monkeypatch.setattr(servicer, "_authenticate", lambda _context: ("panel-a", "A")) monkeypatch.setattr(servicer._keys, "is_active", lambda _key_id: True) async def frames(): yield pb.ServerMessage(registered=pb.RegisterResponse()) stream = servicer.Attach(frames(), Context()) first = await anext(stream) assert first.WhichOneof("payload") == "register" confirmation = await anext(stream) assert confirmation.WhichOneof("payload") == "heartbeat" with pytest.raises(StopAsyncIteration): await asyncio.wait_for(anext(stream), timeout=2) @pytest.mark.asyncio async def test_first_attach_key_persistence_does_not_block_other_sessions( tmp_path, monkeypatch ): from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") auth_started = threading.Event() release_auth = threading.Event() auth_thread = [] real_save = keys._save_locked def blocked_save(): auth_thread.append(threading.current_thread()) auth_started.set() assert release_auth.wait(timeout=2) real_save() monkeypatch.setattr(keys, "_save_locked", blocked_save) class FakeClient: def prepare_inbound_session(self): pass def build_register_request(self): return pb.RegisterRequest() async def stop(self): pass class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def abort(self, code, message): raise AssertionError(f"unexpected abort: {code}: {message}") servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=worker.ArtifactStore(str(tmp_path / "staged")), client_factory=lambda *_args: FakeClient(), credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer async def frames(): if False: yield stream = servicer.Attach(frames(), Context()) opening = asyncio.create_task(anext(stream)) while not auth_started.is_set(): await asyncio.sleep(0) try: await asyncio.wait_for(asyncio.sleep(0), timeout=0.1) assert not opening.done() assert auth_thread[0] is not threading.current_thread() finally: release_auth.set() assert (await opening).WhichOneof("payload") == "register" await stream.aclose() @pytest.mark.asyncio async def test_first_attach_capability_probe_does_not_block_other_sessions( tmp_path, monkeypatch ): from worker import agent, capabilities from worker.inbound import listener as listener_module from worker.inbound import service as inbound_service worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") node = inbound_service.InboundNode() node._keys = keys monkeypatch.setattr( agent, "_paths", lambda: { "root": str(tmp_path / "worker"), "worker_key": str(tmp_path / "worker" / "worker-key.json"), }, ) probe_started = threading.Event() release_probe = threading.Event() probe_thread = [] def blocked_discover(*, include_unavailable): assert include_unavailable is True probe_thread.append(threading.current_thread()) probe_started.set() assert release_probe.wait(timeout=2) return [] monkeypatch.setattr(capabilities, "discover", blocked_discover) monkeypatch.setattr(capabilities, "describe_gpus", lambda: []) class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def abort(self, code, message): raise AssertionError(f"unexpected abort: {code}: {message}") servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=worker.ArtifactStore(str(tmp_path / "staged")), client_factory=node._client_factory, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer async def frames(): if False: yield stream = servicer.Attach(frames(), Context()) opening = asyncio.create_task(anext(stream)) while not probe_started.is_set(): await asyncio.sleep(0) try: await asyncio.wait_for(asyncio.sleep(0), timeout=0.1) assert not opening.done() assert probe_thread[0] is not threading.current_thread() finally: release_probe.set() assert (await opening).WhichOneof("payload") == "register" await stream.aclose() @pytest.mark.asyncio @pytest.mark.parametrize("failure_point", ["prepare", "register"]) async def test_attach_setup_failure_releases_the_key_for_a_retry( tmp_path, monkeypatch, failure_point ): from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() calls = 0 stopped = 0 class BrokenClient: def prepare_inbound_session(self): if failure_point == "prepare": raise RuntimeError("client prepare failed") def build_register_request(self): if failure_point == "register": raise RuntimeError("client register setup failed") return pb.RegisterRequest() async def stop(self): nonlocal stopped stopped += 1 def broken_factory(_artifacts, _key_id): nonlocal calls calls += 1 return BrokenClient() class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return () async def abort(self, code, message): raise AssertionError(f"unexpected abort: {code}: {message}") log = worker.ConnectionLog() servicer = worker.NodeListener( keys=worker.KeyStore(str(tmp_path / "keys.json")), log=log, artifacts=worker.ArtifactStore(str(tmp_path / "staged")), client_factory=broken_factory, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer monkeypatch.setattr(servicer, "_authenticate", lambda _context: ("panel-a", "A")) async def frames(): if False: yield for _ in range(2): with pytest.raises(StopAsyncIteration): await anext(servicer.Attach(frames(), Context())) assert calls == 2 assert stopped == 2 assert not servicer._attached_keys assert not servicer._revocations assert not servicer._protocols assert not servicer._clients assert log.snapshot()["sessions"] == [] @pytest.mark.asyncio async def test_revocation_ends_an_attach_stalled_before_registration(tmp_path): from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") waiting_for_registration = asyncio.Event() stopped = asyncio.Event() class FakeClient: def prepare_inbound_session(self): pass def build_register_request(self): return pb.RegisterRequest() async def stop(self): stopped.set() class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def abort(self, code, message): raise AssertionError(f"unexpected abort: {code}: {message}") servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=worker.ArtifactStore(str(tmp_path / "staged")), client_factory=lambda _artifacts, _key_id: FakeClient(), credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer async def frames(): waiting_for_registration.set() await asyncio.Event().wait() yield pb.ServerMessage() stream = servicer.Attach(frames(), Context()) assert (await anext(stream)).WhichOneof("payload") == "register" pending = asyncio.create_task(anext(stream)) await asyncio.wait_for(waiting_for_registration.wait(), timeout=1) assert servicer.revoke_key(issued.key.key_id) is True goodbye = await asyncio.wait_for(pending, timeout=1) assert goodbye.WhichOneof("payload") == "goodbye" with pytest.raises(StopAsyncIteration): await anext(stream) assert stopped.is_set() assert issued.key.key_id not in servicer._protocols assert not servicer._attached_keys @pytest.mark.asyncio async def test_revocation_during_registration_is_not_confirmed(tmp_path): from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") confirmation_built = False stopped = asyncio.Event() servicer = None class FakeClient: def prepare_inbound_session(self): pass def build_register_request(self): return pb.RegisterRequest() async def accept_registration(self, _response): assert servicer.revoke_key(issued.key.key_id) is True def heartbeat_message(self): nonlocal confirmation_built confirmation_built = True return pb.WorkerMessage(heartbeat=pb.Heartbeat()) async def stop(self): stopped.set() class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def abort(self, code, message): raise AssertionError(f"unexpected abort: {code}: {message}") servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=worker.ArtifactStore(str(tmp_path / "staged")), client_factory=lambda _artifacts, _key_id: FakeClient(), credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer async def frames(): yield pb.ServerMessage(registered=pb.RegisterResponse()) stream = servicer.Attach(frames(), Context()) assert (await anext(stream)).WhichOneof("payload") == "register" assert (await anext(stream)).WhichOneof("payload") == "goodbye" with pytest.raises(StopAsyncIteration): await anext(stream) assert confirmation_built is False assert stopped.is_set() assert issued.key.key_id not in servicer._protocols @pytest.mark.asyncio async def test_a_revoked_key_stops_working_without_disturbing_the_others(inbound): alice = inbound.keys.issue("Alice") inbound.keys.revoke(alice.key.key_id) await inbound.connect_panel(secret=alice.secret, wait=False) await asyncio.wait_for(inbound.log.rejected_event.wait(), timeout=2.0) assert len(inbound.pool) == 0 @pytest.mark.asyncio async def test_key_revoked_during_registration_is_never_confirmed( inbound, monkeypatch ): """Revocation wins even after admission but before identity is durable.""" from worker.transport.client import TerminalRegistrationError real_remember = inbound.keys.remember_worker_id def revoke_before_persist(key_id, worker_id): assert inbound.listener.revoke_key(key_id) is True real_remember(key_id, worker_id) monkeypatch.setattr(inbound.keys, "remember_worker_id", revoke_before_persist) await inbound.connect_panel(wait=False) with pytest.raises(TerminalRegistrationError, match="LOCAL_STATE"): await asyncio.wait_for(inbound.connector_task, timeout=2) assert len(inbound.pool) == 0 assert inbound.servicer._sessions == {} assert inbound.keys.worker_id_for(inbound.panel_key_id) == "" @pytest.mark.asyncio async def test_revoking_a_live_key_ends_every_session_it_authorized(inbound): await inbound.connect_panel() assert len(inbound.pool) == 1 assert inbound.listener.revoke_key(inbound.panel_key_id) is True await asyncio.wait_for(inbound.log.closed_event.wait(), timeout=2) await _until(lambda: len(inbound.pool) == 0) assert inbound.log.snapshot()["sessions"] == [] @pytest.mark.asyncio async def test_revoking_a_live_key_cancels_its_running_executor(inbound): issued = inbound.keys.issue("Panel") execution_started = asyncio.Event() execution_cancelled = asyncio.Event() async def execute(_assignment, **_kwargs): execution_started.set() try: await asyncio.Event().wait() except asyncio.CancelledError: execution_cancelled.set() raise inbound.listener._servicer._client_factory = ( lambda artifacts, key_id: inbound._client_for(artifacts, key_id, execute) ) await inbound.connect_panel(secret=issued.secret) task = inbound.scheduler.submit(operation=OP, engine=ENGINE, model_id=MODEL) assignment = inbound.scheduler.next_assignment() assert assignment is not None assert await inbound.servicer.dispatch(assignment) await asyncio.wait_for(execution_started.wait(), timeout=2) assert inbound.listener.revoke_key(issued.key.key_id) is True await asyncio.wait_for(execution_cancelled.wait(), timeout=2) await asyncio.wait_for(inbound.log.closed_event.wait(), timeout=2) @pytest.mark.asyncio async def test_revoking_a_disconnected_key_cancels_its_retained_executor(inbound): issued = inbound.keys.issue("Panel") execution_started = asyncio.Event() execution_cancelled = asyncio.Event() async def execute(_assignment, **_kwargs): execution_started.set() try: await asyncio.Event().wait() except asyncio.CancelledError: execution_cancelled.set() raise inbound.listener._servicer._client_factory = ( lambda artifacts, key_id: inbound._client_for(artifacts, key_id, execute) ) await inbound.connect_panel(secret=issued.secret) inbound.scheduler.submit(operation=OP, engine=ENGINE, model_id=MODEL) assignment = inbound.scheduler.next_assignment() assert assignment is not None assert await inbound.servicer.dispatch(assignment) await asyncio.wait_for(execution_started.wait(), timeout=2) inbound.connector_task.cancel() await asyncio.gather(inbound.connector_task, return_exceptions=True) await _until(lambda: not inbound.listener._servicer._attached_keys) assert issued.key.key_id in inbound.listener._servicer._protocols assert execution_cancelled.is_set() is False assert inbound.listener.revoke_key(issued.key.key_id) is True assert issued.key.key_id not in inbound.listener._servicer._protocols await asyncio.wait_for(execution_cancelled.wait(), timeout=2) @pytest.mark.asyncio async def test_terminal_panel_refusal_cancels_retained_inbound_execution(inbound): from worker import registry from worker.transport.client import TerminalRegistrationError issued = inbound.keys.issue("Panel") execution_started = asyncio.Event() execution_cancelled = asyncio.Event() client_box = {} async def execute(_assignment, **_kwargs): execution_started.set() try: await asyncio.Event().wait() except asyncio.CancelledError: execution_cancelled.set() raise def client_factory(artifacts, key_id): client = inbound._client_for(artifacts, key_id, execute) client_box["client"] = client return client inbound.listener._servicer._client_factory = client_factory await inbound.connect_panel(secret=issued.secret) worker_id = next(iter(inbound.pool)).worker_id inbound.scheduler.submit(operation=OP, engine=ENGINE, model_id=MODEL) assignment = inbound.scheduler.next_assignment() assert assignment is not None assert await inbound.servicer.dispatch(assignment) await asyncio.wait_for(execution_started.wait(), timeout=2) inbound.connector_task.cancel() await asyncio.gather(inbound.connector_task, return_exceptions=True) await _until(lambda: not inbound.listener._servicer._attached_keys) assert execution_cancelled.is_set() is False assert registry.revoke(worker_id) is True await inbound.connect_panel(secret=issued.secret, wait=False) with pytest.raises(TerminalRegistrationError, match="AUTH_FAILED"): await asyncio.wait_for(inbound.connector_task, timeout=2) await _until(lambda: not inbound.listener._servicer._attached_keys) assert issued.key.key_id not in inbound.listener._servicer._protocols assert client_box["client"]._running == {} await asyncio.wait_for(execution_cancelled.wait(), timeout=2) @pytest.mark.asyncio async def test_revocation_stops_a_result_fetch_before_more_bytes_leave(tmp_path): import grpc from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") artifacts = worker.ArtifactStore(str(tmp_path / "staged")) result = await artifacts.publish( pb.TaskRef(task_id="task", attempt_id="attempt"), b"a" * (listener_module._FETCH_CHUNK_BYTES * 2 + 1), {"filename": "result.wav"}, key_id=issued.key.key_id, ) staged = artifacts.open_result(result.artifact_id, key_id=issued.key.key_id) servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=artifacts, client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer class Aborted(RuntimeError): pass class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def abort(self, code, message): assert code == grpc.StatusCode.UNAUTHENTICATED raise Aborted(message) stream = servicer.FetchResult( pb.ArtifactRef(artifact_id=result.artifact_id), Context() ) first = await anext(stream) assert len(first.data) == listener_module._FETCH_CHUNK_BYTES assert servicer.revoke_key(issued.key.key_id) is True with pytest.raises(Aborted, match="revoked"): await anext(stream) await _until( lambda: artifacts.open_result( result.artifact_id, key_id=issued.key.key_id ) is None ) assert not os.path.exists(staged.path) @pytest.mark.asyncio async def test_blocked_result_read_does_not_stall_key_revocation( tmp_path, monkeypatch ): from threading import Event, Timer from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") artifacts = worker.ArtifactStore(str(tmp_path / "staged")) result = await artifacts.publish( pb.TaskRef(task_id="task", attempt_id="attempt"), b"rendered audio", {"filename": "result.wav"}, key_id=issued.key.key_id, ) servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=artifacts, client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer read_started = Event() release_read = Event() real_open = open class BlockedReader: def __init__(self, handle): self._handle = handle def read(self, size): read_started.set() if not release_read.wait(timeout=10): raise TimeoutError("test did not release the artifact read") return self._handle.read(size) def close(self): self._handle.close() def blocked_open(*args, **kwargs): return BlockedReader(real_open(*args, **kwargs)) class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def abort(self, _code, message): raise RuntimeError(message) monkeypatch.setattr(listener_module, "open", blocked_open, raising=False) stream = servicer.FetchResult( pb.ArtifactRef(artifact_id=result.artifact_id), Context() ) watchdog = Timer(5, release_read.set) watchdog.start() fetching = asyncio.create_task(anext(stream)) async def wait_for_read(): while not read_started.is_set(): await asyncio.sleep(0) try: await asyncio.wait_for(wait_for_read(), timeout=1) assert servicer.revoke_key(issued.key.key_id) is True cleanup = servicer._key_retirements[issued.key.key_id] assert not release_read.is_set(), "result reading blocked the gRPC event loop" finally: release_read.set() watchdog.cancel() with pytest.raises(asyncio.CancelledError): await fetching await cleanup @pytest.mark.asyncio async def test_revocation_discards_a_partial_input_upload(tmp_path): import grpc from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") artifacts = worker.ArtifactStore(str(tmp_path / "staged")) servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=artifacts, client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer ref = pb.ArtifactRef(artifact_id="input", filename="reference.wav") class Aborted(RuntimeError): pass class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def abort(self, code, message): assert code == grpc.StatusCode.UNAUTHENTICATED raise Aborted(message) async def chunks(): yield pb.ArtifactChunk(ref=ref, offset=0, data=b"first", last=False) assert servicer.revoke_key(issued.key.key_id) is True yield pb.ArtifactChunk(ref=ref, offset=5, data=b"second", last=True) with pytest.raises(Aborted, match="revoked"): await servicer.PushInput(chunks(), Context()) assert not any(files for _root, _dirs, files in os.walk(artifacts._root)) @pytest.mark.asyncio async def test_blocked_input_write_does_not_stall_key_revocation( tmp_path, monkeypatch ): from threading import Event, Timer from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") artifacts = worker.ArtifactStore(str(tmp_path / "staged")) servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=artifacts, client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer write_started = Event() release_write = Event() real_write_all = listener_module._write_all def blocked_write(handle, payload): write_started.set() if not release_write.wait(timeout=10): raise TimeoutError("test did not release the artifact write") real_write_all(handle, payload) ref = pb.ArtifactRef( artifact_id="input", filename="reference.wav", size_bytes=5, sha256=hashlib.sha256(b"audio").hexdigest(), ) async def chunks(): yield pb.ArtifactChunk(ref=ref, offset=0, data=b"audio", last=True) class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def abort(self, _code, message): raise RuntimeError(message) monkeypatch.setattr(listener_module, "_write_all", blocked_write) # Deadlock escape for the regression path. The assertion below checks # ordering, not runner speed: revocation must finish before this releases # the blocked write. watchdog = Timer(5, release_write.set) watchdog.start() uploading = asyncio.create_task(servicer.PushInput(chunks(), Context())) async def wait_for_write(): while not write_started.is_set(): await asyncio.sleep(0) try: await asyncio.wait_for(wait_for_write(), timeout=1) assert servicer.revoke_key(issued.key.key_id) is True cleanup = servicer._key_retirements[issued.key.key_id] assert not release_write.is_set(), "input writing blocked the gRPC event loop" finally: release_write.set() watchdog.cancel() with pytest.raises(asyncio.CancelledError): await uploading await cleanup assert artifacts._reserved_input_bytes == 0 assert not any(files for _root, _dirs, files in os.walk(artifacts._root)) @pytest.mark.asyncio async def test_input_admission_and_mkdir_do_not_block_the_listener_loop( tmp_path, monkeypatch ): from threading import Event, Timer from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") artifacts = worker.ArtifactStore(str(tmp_path / "staged")) servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=artifacts, client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer admission_started = Event() release_admission = Event() real_begin = artifacts.begin_input def blocked_begin(*args, **kwargs): admission_started.set() if not release_admission.wait(timeout=10): raise TimeoutError("test did not release input admission") return real_begin(*args, **kwargs) monkeypatch.setattr(artifacts, "begin_input", blocked_begin) payload = b"audio" ref = pb.ArtifactRef( artifact_id="input", filename="reference.wav", size_bytes=len(payload), sha256=hashlib.sha256(payload).hexdigest(), ) async def chunks(): yield pb.ArtifactChunk(ref=ref, offset=0, data=payload, last=True) class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def abort(self, _code, message): raise RuntimeError(message) watchdog = Timer(5, release_admission.set) watchdog.start() uploading = asyncio.create_task(servicer.PushInput(chunks(), Context())) try: await asyncio.wait_for( asyncio.to_thread(admission_started.wait), timeout=1 ) assert not release_admission.is_set(), ( "input admission blocked the gRPC event loop" ) finally: release_admission.set() watchdog.cancel() ack = await uploading assert ack.committed is True @pytest.mark.asyncio async def test_artifact_untrack_cleanup_does_not_block_the_listener_loop( tmp_path, monkeypatch ): from threading import Event, Timer from worker.inbound import listener as listener_module worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") artifacts = worker.ArtifactStore(str(tmp_path / "staged")) servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=artifacts, client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer cleanup_started = Event() release_cleanup = Event() real_retry = artifacts.retry_result_acks def blocked_retry(key_id): cleanup_started.set() if not release_cleanup.wait(timeout=10): raise TimeoutError("test did not release ACK retry cleanup") real_retry(key_id) monkeypatch.setattr(artifacts, "retry_result_acks", blocked_retry) async def chunks(): if False: yield None class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def abort(self, _code, message): raise RuntimeError(message) watchdog = Timer(5, release_cleanup.set) watchdog.start() uploading = asyncio.create_task(servicer.PushInput(chunks(), Context())) try: await asyncio.wait_for( asyncio.to_thread(cleanup_started.wait), timeout=1 ) assert not release_cleanup.is_set(), "input cleanup blocked the gRPC event loop" finally: release_cleanup.set() watchdog.cancel() ack = await uploading assert ack.error.code == "INPUT_INCOMPLETE" @pytest.mark.asyncio async def test_revocation_cancels_a_stalled_input_rpc_and_removes_its_partial(tmp_path): from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") artifacts = worker.ArtifactStore(str(tmp_path / "staged")) servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=artifacts, client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer ref = pb.ArtifactRef(artifact_id="input", filename="reference.wav") stalled = asyncio.Event() class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def abort(self, _code, message): raise RuntimeError(message) async def chunks(): yield pb.ArtifactChunk(ref=ref, offset=0, data=b"first", last=False) stalled.set() await asyncio.Event().wait() upload = asyncio.create_task(servicer.PushInput(chunks(), Context())) await asyncio.wait_for(stalled.wait(), timeout=1) assert any(files for _root, _dirs, files in os.walk(artifacts._root)) assert await servicer.revoke_key_and_wait(issued.key.key_id) is True with pytest.raises(asyncio.CancelledError): await upload assert not any(files for _root, _dirs, files in os.walk(artifacts._root)) assert servicer._artifact_tasks == {} @pytest.mark.asyncio async def test_revocation_cancels_a_backpressured_result_fetch(tmp_path): from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") artifacts = worker.ArtifactStore(str(tmp_path / "staged")) result = await artifacts.publish( pb.TaskRef(task_id="task", attempt_id="attempt"), b"result bytes", {"filename": "result.wav"}, key_id=issued.key.key_id, ) staged = artifacts.open_result(result.artifact_id, key_id=issued.key.key_id) other = await artifacts.publish( pb.TaskRef(task_id="other-task", attempt_id="other-attempt"), b"other panel result", {"filename": "other.wav"}, key_id="other-panel", ) servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=artifacts, client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer backpressured = asyncio.Event() class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def abort(self, _code, message): raise RuntimeError(message) async def consume(): async for _chunk in servicer.FetchResult( pb.ArtifactRef(artifact_id=result.artifact_id), Context() ): backpressured.set() await asyncio.Event().wait() fetch = asyncio.create_task(consume()) await asyncio.wait_for(backpressured.wait(), timeout=1) assert await servicer.revoke_key_and_wait(issued.key.key_id) is True with pytest.raises(asyncio.CancelledError): await fetch await asyncio.sleep(0) assert not os.path.exists(staged.path) assert artifacts.open_result(result.artifact_id, key_id=issued.key.key_id) is None assert artifacts.open_result(other.artifact_id, key_id="other-panel") is not None assert servicer._artifact_tasks == {} @pytest.mark.asyncio async def test_failed_durable_revoke_still_retires_disconnected_work( tmp_path, monkeypatch ): worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") stopped = asyncio.Event() class RetainedClient: async def stop(self): stopped.set() artifacts = worker.ArtifactStore(str(tmp_path / "staged")) servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=artifacts, client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer retained = RetainedClient() servicer._protocols[issued.key.key_id] = retained monkeypatch.setattr( keys, "_save_locked", lambda: (_ for _ in ()).throw(OSError("disk full")), ) with pytest.raises(OSError, match="disk full"): await servicer.revoke_key_and_wait(issued.key.key_id) assert stopped.is_set() assert issued.key.key_id not in servicer._protocols assert not keys.is_active(issued.key.key_id) @pytest.mark.asyncio async def test_the_owner_can_see_who_connected_and_kick_them(inbound): await inbound.connect_panel() sessions = inbound.log.snapshot()["sessions"] assert len(sessions) == 1 assert sessions[0]["label"] == "Test panel" assert inbound.log.kick(sessions[0]["session_id"]) is True # The kick has to land on an idle session too, which is the case a # loop that only wakes on outbound traffic would never notice. await asyncio.wait_for(inbound.log.closed_event.wait(), timeout=2.0) assert inbound.log.snapshot()["sessions"] == [] # And it has to STAY landed for a moment. The panel redials on its own, so # without a cooldown the person is back within two seconds and the button # appears to do nothing — which is what it did on hardware, where the log # read disconnected and connected in the same breath. assert inbound.log.cooling_down(sessions[0]["key_id"]) is True await asyncio.wait_for(inbound.log.rejected_event.wait(), timeout=2.0) assert inbound.log.snapshot()["sessions"] == [], ( "the kicked panel came straight back" ) @pytest.mark.asyncio @pytest.mark.parametrize("initiator", ["panel", "node_owner"]) async def test_explicit_inbound_disconnect_cancels_retained_execution( inbound, initiator ): execution_started = asyncio.Event() execution_cancelled = asyncio.Event() async def execute(_assignment, **_kwargs): execution_started.set() try: await asyncio.Event().wait() except asyncio.CancelledError: execution_cancelled.set() raise inbound.listener._servicer._client_factory = ( lambda artifacts, key_id: inbound._client_for(artifacts, key_id, execute) ) await inbound.connect_panel() inbound.scheduler.submit(operation=OP, engine=ENGINE, model_id=MODEL) assignment = inbound.scheduler.next_assignment() assert assignment is not None assert await inbound.servicer.dispatch(assignment) await asyncio.wait_for(execution_started.wait(), timeout=2) if initiator == "panel": await asyncio.wait_for(inbound.connection.stop(), timeout=2) else: session = inbound.log.snapshot()["sessions"][0] assert inbound.log.kick(session["session_id"]) is True await asyncio.wait_for(execution_cancelled.wait(), timeout=2) await _until( lambda: inbound.panel_key_id not in inbound.listener._servicer._protocols ) @pytest.mark.asyncio async def test_an_input_pushed_before_the_assignment_is_there_when_the_task_asks( inbound, tmp_path ): """Inbound reverses the artifact direction, so ordering is a real hazard: an assignment that overtakes its own inputs fails on a file that is merely late.""" from worker.protocol.gen import worker_v1_pb2 as pb await inbound.connect_panel() source = tmp_path / "reference.wav" source.write_bytes(b"reference audio bytes") declared = await inbound.connection.push_input( pb.ArtifactRef(artifact_id="ref-1", filename="reference.wav"), str(source) ) assert declared.sha256 destination = tmp_path / "staged-copy.wav" await inbound.artifacts.stage_in( declared, str(destination), key_id=inbound.panel_key_id ) assert destination.read_bytes() == b"reference audio bytes" @pytest.mark.asyncio async def test_a_pushed_input_that_does_not_match_its_checksum_is_refused( inbound, tmp_path ): """A truncated or corrupted input that gets staged anyway becomes a render that succeeds against the wrong audio.""" from worker.protocol.gen import worker_v1_pb2 as pb await inbound.connect_panel() source = tmp_path / "reference.wav" source.write_bytes(b"reference audio bytes") ref = pb.ArtifactRef(artifact_id="ref-2", filename="reference.wav", sha256="0" * 64) # Declared hash wins over the computed one only if the node checks; force # the mismatch by pinning a wrong hash on the way in. original = inbound.connection.push_input async def corrupted(_ref, path): return await original(ref, path) with pytest.raises(RuntimeError, match="checksum|did not accept"): # push_input recomputes the hash, so drive PushInput directly with a # ref whose declared hash cannot match. await _push_with_declared_hash(inbound, ref, str(source)) @pytest.mark.asyncio async def test_an_offset_mismatch_removes_the_partial_input(inbound): from worker.inbound.listener import KEY_METADATA_KEY from worker.protocol.gen import worker_v1_pb2 as pb await inbound.connect_panel() ref = pb.ArtifactRef(artifact_id="offset-mismatch", filename="reference.wav") async def chunks(): yield pb.ArtifactChunk(ref=ref, offset=0, data=b"first", last=False) yield pb.ArtifactChunk(ref=ref, offset=99, data=b"second", last=True) ack = await inbound.connection._stub.PushInput( chunks(), metadata=((KEY_METADATA_KEY, inbound.connection._connection.secret),), ) assert ack.error.code == "OFFSET_MISMATCH" assert not any(files for _root, _dirs, files in os.walk(inbound.artifacts._root)) @pytest.mark.asyncio async def test_input_push_enforces_declared_and_actual_size_limits( inbound, monkeypatch ): from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb await inbound.connect_panel() monkeypatch.setattr(listener_module, "MAX_INPUT_ARTIFACT_BYTES", 8) metadata = ( (listener_module.KEY_METADATA_KEY, inbound.connection._connection.secret), ) async def push(ref, data): async def chunks(): yield pb.ArtifactChunk(ref=ref, offset=0, data=data, last=True) return await inbound.connection._stub.PushInput(chunks(), metadata=metadata) declared_oversize = await push( pb.ArtifactRef( artifact_id="declared-oversize", filename="reference.wav", size_bytes=9, ), b"x", ) streamed_oversize = await push( pb.ArtifactRef( artifact_id="streamed-oversize", filename="reference.wav" ), b"123456789", ) declared_short = await push( pb.ArtifactRef( artifact_id="declared-short", filename="reference.wav", size_bytes=8, ), b"1234", ) assert declared_oversize.error.code == "INPUT_TOO_LARGE" assert streamed_oversize.error.code == "INPUT_TOO_LARGE" assert declared_short.error.code == "INPUT_SIZE_MISMATCH" assert not any(files for _root, _dirs, files in os.walk(inbound.artifacts._root)) @pytest.mark.asyncio async def test_cancelled_result_fetch_removes_its_partial_destination(tmp_path): from worker.inbound.connector import NodeConnection from worker.protocol.gen import worker_v1_pb2 as pb fetch_waiting = asyncio.Event() class Stub: def FetchResult(self, _request, metadata=()): # noqa: N802 async def chunks(): yield pb.ResultChunk(offset=0, data=b"partial", last=False) fetch_waiting.set() await asyncio.Event().wait() return chunks() connector = object.__new__(NodeConnection) connector._stub = Stub() connector._connection = SimpleNamespace(secret="panel-key") destination = tmp_path / "result.bin" fetch = asyncio.create_task( connector.fetch_result(pb.ArtifactRef(artifact_id="result"), str(destination)) ) await asyncio.wait_for(fetch_waiting.wait(), timeout=1) assert destination.exists() fetch.cancel() with pytest.raises(asyncio.CancelledError): await fetch assert not destination.exists() @pytest.mark.asyncio async def test_input_push_hashes_and_streams_bounded_blocks_off_the_loop( tmp_path, monkeypatch ): from worker.inbound import connector as connector_module from worker.protocol.gen import worker_v1_pb2 as pb path = tmp_path / "reference.wav" payload = b"reference audio" * 200_000 path.write_bytes(payload) real_open = open read_started = threading.Event() allow_read = threading.Event() read_sizes = [] block_lock = threading.Lock() has_blocked = False class ObservedHandle: def __init__(self, handle): self._handle = handle def __enter__(self): self._handle.__enter__() return self def __exit__(self, *args): return self._handle.__exit__(*args) def fileno(self): return self._handle.fileno() def read(self, size=-1): nonlocal has_blocked read_sizes.append(size) assert 0 < size <= connector_module._PUSH_CHUNK_BYTES with block_lock: should_block = not has_blocked has_blocked = True if should_block: read_started.set() assert allow_read.wait(timeout=2) return self._handle.read(size) def close(self): return self._handle.close() def observed_open(file, mode="r", *args, **kwargs): handle = real_open(file, mode, *args, **kwargs) return ObservedHandle(handle) if file == str(path) and mode == "rb" else handle class Stub: async def PushInput(self, chunks, metadata=()): # noqa: N802 received = bytearray() async for chunk in chunks: received.extend(chunk.data) assert bytes(received) == payload return pb.ResultAck(committed=True) connection = object.__new__(connector_module.NodeConnection) connection._stub = Stub() connection._connection = SimpleNamespace(secret="panel-key") monkeypatch.setattr(connector_module, "open", observed_open, raising=False) loop_was_responsive = asyncio.Event() async def observe_loop(): while not allow_read.is_set(): if read_started.is_set(): loop_was_responsive.set() await asyncio.sleep(0) observer = asyncio.create_task(observe_loop()) release = threading.Timer(0.2, allow_read.set) release.start() try: declared = await connection.push_input( pb.ArtifactRef(artifact_id="reference"), str(path) ) finally: allow_read.set() release.cancel() observer.cancel() await asyncio.gather(observer, return_exceptions=True) assert loop_was_responsive.is_set() assert read_sizes and all( 0 < size <= connector_module._PUSH_CHUNK_BYTES for size in read_sizes ) assert declared.size_bytes == len(payload) assert declared.sha256 == hashlib.sha256(payload).hexdigest() @pytest.mark.asyncio async def test_input_push_refuses_a_staged_file_replaced_after_hash(tmp_path): from worker.inbound import connector as connector_module from worker.protocol.gen import worker_v1_pb2 as pb path = tmp_path / "reference.wav" path.write_bytes(b"original reference") sent = [] class Stub: async def PushInput(self, chunks, metadata=()): # noqa: N802 path.write_bytes(b"replacement reference is longer") async for chunk in chunks: sent.append(chunk) return pb.ResultAck(committed=True) connection = object.__new__(connector_module.NodeConnection) connection._stub = Stub() connection._connection = SimpleNamespace(secret="panel-key") with pytest.raises(RuntimeError, match="staged task input changed"): await connection.push_input( pb.ArtifactRef(artifact_id="reference"), str(path) ) assert sent == [] @pytest.mark.asyncio async def test_staged_artifacts_are_isolated_by_panel_key(tmp_path): from worker.protocol.gen import worker_v1_pb2 as pb store = _worker_modules().ArtifactStore(str(tmp_path / "staged")) result = await store.publish( pb.TaskRef(task_id="t1", attempt_id="a1"), b"alice audio", {"filename": "out.wav"}, key_id="alice", ) assert store.open_result(result.artifact_id, key_id="bob") is None store.result_acked(result.artifact_id, key_id="bob") assert store.open_result(result.artifact_id, key_id="alice") is not None incoming = pb.ArtifactRef(artifact_id="shared-id", filename="ref.wav") payload = b"alice reference" path = store.begin_input(incoming, key_id="alice") with open(path, "wb") as handle: handle.write(payload) store.commit_input( incoming, path, hashlib.sha256(payload).hexdigest(), len(payload), key_id="alice", ) with pytest.raises(RuntimeError, match="did not send input"): await store.stage_in(incoming, str(tmp_path / "bob.wav"), key_id="bob") await store.stage_in(incoming, str(tmp_path / "alice.wav"), key_id="alice") assert (tmp_path / "alice.wav").read_bytes() == b"alice reference" bob_result = await store.publish( pb.TaskRef(task_id="t2", attempt_id="a2"), b"bob audio", {"filename": "bob.wav"}, key_id="bob", ) store.purge_key("alice") assert store.open_result(result.artifact_id, key_id="alice") is None assert store.open_result(bob_result.artifact_id, key_id="bob") is not None with pytest.raises(RuntimeError, match="did not send input"): await store.stage_in(incoming, str(tmp_path / "purged.wav"), key_id="alice") @pytest.mark.asyncio async def test_result_ack_retries_a_transient_delete_failure(tmp_path, monkeypatch): from worker.inbound import artifacts as artifacts_module from worker.protocol.gen import worker_v1_pb2 as pb store = artifacts_module.ArtifactStore(str(tmp_path / "staged")) result = await store.publish( pb.TaskRef(task_id="task", attempt_id="attempt"), b"rendered audio", {"filename": "result.wav"}, key_id="panel", ) staged = store.open_result(result.artifact_id, key_id="panel") real_remove = artifacts_module.os.remove attempts = 0 def transient_remove(path): nonlocal attempts if path == staged.path and attempts == 0: attempts += 1 raise PermissionError("result is still open") return real_remove(path) monkeypatch.setattr(artifacts_module.os, "remove", transient_remove) store.result_acked(result.artifact_id, key_id="panel") assert store.open_result(result.artifact_id, key_id="panel") is staged assert os.path.exists(staged.path) store.retry_result_acks("panel") assert store.open_result(result.artifact_id, key_id="panel") is None assert not os.path.exists(staged.path) @pytest.mark.asyncio async def test_result_ack_deletion_does_not_block_the_attach_loop( inbound, monkeypatch ): from threading import Event, Timer from worker.protocol.gen import worker_v1_pb2 as pb from worker.transport.client import PendingResult await inbound.connect_panel() protocol = inbound.listener._servicer._protocols[inbound.panel_key_id] artifact = await inbound.artifacts.publish( pb.TaskRef(task_id="task", attempt_id="attempt"), b"rendered audio", {"filename": "result.wav"}, key_id=inbound.panel_key_id, ) staged = inbound.artifacts.open_result( artifact.artifact_id, key_id=inbound.panel_key_id ) assert staged is not None ref = pb.TaskRef( task_id="task", attempt_id="attempt", session_epoch=protocol._epoch ) protocol._pending[protocol._key(ref)] = PendingResult( ref=ref, result_json="{}", inline_payload=b"", artifacts=[artifact], ) cleanup_started = Event() release_cleanup = Event() real_acked = inbound.artifacts.result_acked def blocked_acked(artifact_id, *, key_id): cleanup_started.set() if not release_cleanup.wait(timeout=10): raise TimeoutError("test did not release result ACK cleanup") real_acked(artifact_id, key_id=key_id) monkeypatch.setattr(inbound.artifacts, "result_acked", blocked_acked) watchdog = Timer(5, release_cleanup.set) watchdog.start() handling = asyncio.create_task( protocol.handle_server_message( pb.ServerMessage(result_ack=pb.ResultAckMessage(ref=ref)) ) ) try: await asyncio.wait_for( asyncio.to_thread(cleanup_started.wait), timeout=1 ) assert not release_cleanup.is_set(), ( "result cleanup blocked the gRPC event loop" ) finally: release_cleanup.set() watchdog.cancel() await handling assert not os.path.exists(staged.path) @pytest.mark.asyncio async def test_result_acks_prune_every_artifact_and_kind_directory(tmp_path): from worker.inbound.artifacts import ArtifactStore from worker.protocol.gen import worker_v1_pb2 as pb root = tmp_path / "staged" store = ArtifactStore(str(root)) results = [ await store.publish( pb.TaskRef(task_id=f"task-{index}", attempt_id=f"attempt-{index}"), b"rendered audio", {"filename": "result.wav"}, key_id="panel", ) for index in range(10) ] for result in results: store.result_acked(result.artifact_id, key_id="panel") assert list(root.iterdir()) == [] assert store._orphaned_directories == set() @pytest.mark.asyncio async def test_failed_empty_directory_prune_is_retried(tmp_path, monkeypatch): from worker.inbound import artifacts as artifacts_module from worker.protocol.gen import worker_v1_pb2 as pb root = tmp_path / "staged" store = artifacts_module.ArtifactStore(str(root)) result = await store.publish( pb.TaskRef(task_id="task", attempt_id="attempt"), b"rendered audio", {"filename": "result.wav"}, key_id="panel", ) artifact_dir = os.path.dirname( store.open_result(result.artifact_id, key_id="panel").path ) real_rmdir = artifacts_module.os.rmdir failed = False def transient_rmdir(path): nonlocal failed if path == artifact_dir and not failed: failed = True raise PermissionError("directory is transiently locked") return real_rmdir(path) monkeypatch.setattr(artifacts_module.os, "rmdir", transient_rmdir) store.result_acked(result.artifact_id, key_id="panel") assert artifact_dir in store._orphaned_directories store.purge() assert list(root.iterdir()) == [] assert store._orphaned_directories == set() @pytest.mark.asyncio async def test_restart_removes_unindexed_staging_generations(tmp_path): from worker.inbound.artifacts import ArtifactStore from worker.protocol.gen import worker_v1_pb2 as pb root = tmp_path / "staged" first = ArtifactStore(str(root)) result = await first.publish( pb.TaskRef(task_id="task", attempt_id="attempt"), b"unacknowledged result", {"filename": "result.wav"}, key_id="panel", ) result_path = first.open_result(result.artifact_id, key_id="panel").path incoming = pb.ArtifactRef(artifact_id="input", filename="reference.wav") payload = b"staged input" input_path = first.begin_input(incoming, key_id="panel") with open(input_path, "wb") as handle: handle.write(payload) first.commit_input( incoming, input_path, hashlib.sha256(payload).hexdigest(), len(payload), key_id="panel", ) restarted = ArtifactStore(str(root)) assert not os.path.exists(result_path) assert not os.path.exists(input_path) assert restarted._orphaned_paths == set() @pytest.mark.asyncio async def test_repeated_authenticated_input_calls_hit_the_per_key_disk_quota( tmp_path, ): from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") artifacts = worker.ArtifactStore( str(tmp_path / "staged"), max_input_bytes_per_key=10, max_input_bytes_total=100, ) servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=artifacts, client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def push(artifact_id): payload = b"123456" ref = pb.ArtifactRef( artifact_id=artifact_id, filename="reference.wav", size_bytes=len(payload), ) async def chunks(): yield pb.ArtifactChunk(ref=ref, offset=0, data=payload, last=True) return await servicer.PushInput(chunks(), Context()) first = await push("first") refused = await push("fresh-id") assert first.committed is True assert refused.committed is False assert refused.error.code == "INPUT_QUOTA_EXCEEDED" assert artifacts._committed_input_bytes == 6 assert artifacts._reserved_input_bytes == 0 @pytest.mark.asyncio async def test_lost_input_ack_reuses_verified_bytes_at_exact_quota(tmp_path): from worker.inbound import listener as listener_module from worker.protocol.gen import worker_v1_pb2 as pb worker = _worker_modules() keys = worker.KeyStore(str(tmp_path / "keys.json")) issued = keys.issue("Panel") payload = b"123456" artifacts = worker.ArtifactStore( str(tmp_path / "staged"), max_input_bytes_per_key=len(payload), max_input_bytes_total=len(payload), max_inputs_per_key=1, max_inputs_total=1, ) servicer = worker.NodeListener( keys=keys, log=worker.ConnectionLog(), artifacts=artifacts, client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), )._servicer ref = pb.ArtifactRef( artifact_id="same-input", filename="reference.wav", size_bytes=len(payload), sha256=hashlib.sha256(payload).hexdigest(), ) class Context: def peer(self): return "ipv4:10.0.0.2:45000" def invocation_metadata(self): return ((listener_module.KEY_METADATA_KEY, issued.secret),) async def push(): async def chunks(): yield pb.ArtifactChunk(ref=ref, offset=0, data=payload, last=True) return await servicer.PushInput(chunks(), Context()) assert (await push()).committed is True retried = await push() assert retried.committed is True assert retried.bytes_received == len(payload) assert len(artifacts._in) == 1 assert artifacts._committed_input_bytes == len(payload) assert artifacts._reserved_input_bytes == 0 @pytest.mark.asyncio async def test_repeated_unacked_results_hit_the_per_key_disk_quota(tmp_path): from worker.inbound.artifacts import ArtifactQuotaExceeded, ArtifactStore from worker.protocol.gen import worker_v1_pb2 as pb store = ArtifactStore( str(tmp_path / "staged"), max_result_bytes_per_key=10, max_result_bytes_total=100, ) repeated_ref = pb.TaskRef(task_id="same-task", attempt_id="same-attempt") first = await store.publish( repeated_ref, b"123456", {"filename": "result.wav"}, key_id="panel", ) with pytest.raises(ArtifactQuotaExceeded, match="panel"): await store.publish( repeated_ref, b"abcdef", {"filename": "result.wav"}, key_id="panel", ) assert len(store._out) == 1 assert store._committed_result_bytes == 6 assert store._reserved_result_bytes == 0 store.result_acked(first.artifact_id, key_id="panel") replacement = await store.publish( repeated_ref, b"abcdef", {"filename": "result.wav"}, key_id="panel", ) assert store.open_result(replacement.artifact_id, key_id="panel") is not None assert store._committed_result_bytes == 6 @pytest.mark.asyncio async def test_parallel_results_reserve_against_one_global_disk_quota(tmp_path): from worker.inbound.artifacts import ArtifactQuotaExceeded, ArtifactStore from worker.protocol.gen import worker_v1_pb2 as pb store = ArtifactStore( str(tmp_path / "staged"), max_result_bytes_per_key=10, max_result_bytes_total=6, ) async def publish(key_id): return await store.publish( pb.TaskRef(task_id=key_id, attempt_id="attempt"), b"123456", {"filename": "result.wav"}, key_id=key_id, ) outcomes = await asyncio.gather( publish("alice"), publish("bob"), return_exceptions=True ) admitted = [value for value in outcomes if isinstance(value, pb.ArtifactRef)] refused = [ value for value in outcomes if isinstance(value, ArtifactQuotaExceeded) ] assert len(admitted) == len(refused) == 1 assert store._committed_result_bytes == 6 assert store._reserved_result_bytes == 0 @pytest.mark.asyncio async def test_unacked_result_count_quota_releases_on_ack_purge_and_ttl( tmp_path, monkeypatch ): from worker.inbound import artifacts as artifacts_module from worker.protocol.gen import worker_v1_pb2 as pb store = artifacts_module.ArtifactStore( str(tmp_path / "staged"), max_results_per_key=1, max_results_total=1, ) ref = pb.TaskRef(task_id="task", attempt_id="attempt") async def publish(): return await store.publish( ref, b"result", {"filename": "result.wav"}, key_id="panel" ) first = await publish() with pytest.raises(artifacts_module.ArtifactQuotaExceeded): await publish() store.result_acked(first.artifact_id, key_id="panel") second = await publish() store.purge_key("panel") assert store.open_result(second.artifact_id, key_id="panel") is None await publish() monkeypatch.setattr(artifacts_module, "_STALE_SECONDS", -1) replacement = await publish() assert store.open_result(replacement.artifact_id, key_id="panel") is not None assert store._committed_result_bytes == len(b"result") def test_parallel_keys_reserve_against_one_global_input_quota(tmp_path): from concurrent.futures import ThreadPoolExecutor from threading import Barrier from worker.inbound.artifacts import ArtifactQuotaExceeded, ArtifactStore from worker.protocol.gen import worker_v1_pb2 as pb store = ArtifactStore( str(tmp_path / "staged"), max_input_bytes_per_key=10, max_input_bytes_total=10, ) barrier = Barrier(3) def reserve(key_id): barrier.wait() try: return store.begin_input( pb.ArtifactRef( artifact_id=f"{key_id}-input", filename="reference.wav", size_bytes=6, ), key_id=key_id, ) except ArtifactQuotaExceeded as exc: return exc with ThreadPoolExecutor(max_workers=2) as pool: futures = [pool.submit(reserve, key_id) for key_id in ("alice", "bob")] barrier.wait() outcomes = [future.result(timeout=2) for future in futures] admitted = [value for value in outcomes if isinstance(value, str)] refused = [ value for value in outcomes if isinstance(value, ArtifactQuotaExceeded) ] assert len(admitted) == len(refused) == 1 assert store._reserved_input_bytes == 6 store.discard_input(admitted[0]) assert store._reserved_input_bytes == 0 def test_input_quota_accounting_releases_retries_purges_and_ttl( tmp_path, monkeypatch ): from worker.inbound import artifacts as artifacts_module from worker.protocol.gen import worker_v1_pb2 as pb store = artifacts_module.ArtifactStore( str(tmp_path / "staged"), max_input_bytes_per_key=12, max_input_bytes_total=12, ) ref = pb.ArtifactRef(artifact_id="input", filename="reference.wav") payload = b"123456" digest = hashlib.sha256(payload).hexdigest() def upload(reference): temporary = store.begin_input( reference, key_id="panel", reserve_bytes=len(payload) ) with open(temporary, "wb") as handle: handle.write(payload) return store.commit_input( reference, temporary, digest, len(payload), key_id="panel" ) committed = upload(ref) assert upload(ref) == committed assert store._committed_input_bytes == 6 assert store._reserved_input_bytes == 0 store.purge_key("panel") assert store._committed_input_bytes == 0 committed = upload(ref) monkeypatch.setattr(artifacts_module, "_STALE_SECONDS", -1) replacement = store.begin_input( pb.ArtifactRef(artifact_id="new", filename="reference.wav"), key_id="panel", reserve_bytes=12, ) assert not os.path.exists(committed) assert store._committed_input_bytes == 0 assert store._reserved_input_bytes == 12 store.discard_input(replacement) def test_restart_releases_crash_surviving_input_bytes_from_the_quota(tmp_path): from worker.inbound.artifacts import ArtifactStore from worker.protocol.gen import worker_v1_pb2 as pb root = tmp_path / "staged" first = ArtifactStore( str(root), max_input_bytes_per_key=6, max_input_bytes_total=6 ) ref = pb.ArtifactRef(artifact_id="input", filename="reference.wav") payload = b"123456" temporary = first.begin_input(ref, key_id="panel", reserve_bytes=6) with open(temporary, "wb") as handle: handle.write(payload) first.commit_input( ref, temporary, hashlib.sha256(payload).hexdigest(), len(payload), key_id="panel", ) restarted = ArtifactStore( str(root), max_input_bytes_per_key=6, max_input_bytes_total=6 ) reservation = restarted.begin_input( pb.ArtifactRef(artifact_id="replacement", filename="reference.wav"), key_id="panel", reserve_bytes=6, ) assert restarted._committed_input_bytes == 0 assert restarted._reserved_input_bytes == 6 restarted.discard_input(reservation) def test_artifact_store_persists_its_root_directory_entry(tmp_path, monkeypatch): from worker.inbound import artifacts as artifacts_module fsynced = [] real_fsync_parent = artifacts_module._fsync_parent_directory def record_fsync(directory): fsynced.append(os.path.abspath(directory)) return real_fsync_parent(directory) monkeypatch.setattr( artifacts_module, "_fsync_parent_directory", record_fsync ) root = tmp_path / "staged" artifacts_module.ArtifactStore(str(root)) assert str(tmp_path) in fsynced @pytest.mark.asyncio async def test_cancelled_result_publish_drains_write_before_removing_file( tmp_path, monkeypatch ): from threading import Event from worker.inbound import artifacts as artifacts_module from worker.protocol.gen import worker_v1_pb2 as pb real_open = open write_started = Event() allow_write = Event() write_finished = Event() class BlockedHandle: def __init__(self, handle): self._handle = handle def __enter__(self): self._handle.__enter__() return self def __exit__(self, *args): try: return self._handle.__exit__(*args) finally: write_finished.set() def write(self, payload): write_started.set() if not allow_write.wait(timeout=2): raise TimeoutError("test did not release the staged result write") return self._handle.write(payload) def __getattr__(self, name): return getattr(self._handle, name) def blocked_open(*args, **kwargs): return BlockedHandle(real_open(*args, **kwargs)) async def wait_until_set(event): while not event.is_set(): await asyncio.sleep(0) monkeypatch.setattr(artifacts_module, "open", blocked_open, raising=False) store = artifacts_module.ArtifactStore(str(tmp_path / "staged")) publish = asyncio.create_task( store.publish( pb.TaskRef(task_id="task", attempt_id="attempt"), b"rendered audio", {"filename": "result.wav"}, key_id="panel", ) ) await asyncio.wait_for(wait_until_set(write_started), timeout=1) publish.cancel() await asyncio.sleep(0) cancellation_is_draining = not publish.done() allow_write.set() with pytest.raises(asyncio.CancelledError): await publish await asyncio.wait_for(wait_until_set(write_finished), timeout=1) assert cancellation_is_draining, "cancellation must wait for the active disk write" assert store._out == {} assert not any(files for _root, _dirs, files in os.walk(store._root)) @pytest.mark.asyncio async def test_result_publish_sweep_does_not_block_the_listener_loop( tmp_path, monkeypatch ): from threading import Event, Timer from worker.inbound import artifacts as artifacts_module from worker.protocol.gen import worker_v1_pb2 as pb store = artifacts_module.ArtifactStore(str(tmp_path / "staged")) sweep_started = Event() release_sweep = Event() real_sweep = store._sweep_locked def blocked_sweep(*args, **kwargs): sweep_started.set() if not release_sweep.wait(timeout=10): raise TimeoutError("test did not release the staging sweep") real_sweep(*args, **kwargs) monkeypatch.setattr(store, "_sweep_locked", blocked_sweep) watchdog = Timer(5, release_sweep.set) watchdog.start() publish = asyncio.create_task( store.publish( pb.TaskRef(task_id="task", attempt_id="attempt"), b"rendered audio", {"filename": "result.wav"}, key_id="panel", ) ) try: await asyncio.wait_for(asyncio.to_thread(sweep_started.wait), timeout=1) assert not release_sweep.is_set(), ( "artifact sweeping blocked the gRPC event loop" ) finally: release_sweep.set() watchdog.cancel() assert (await publish).artifact_id @pytest.mark.asyncio @pytest.mark.parametrize("failure_point", ["file", "directory"]) async def test_result_publish_fsync_failure_exposes_no_artifact( tmp_path, monkeypatch, failure_point ): from worker.inbound import artifacts as artifacts_module from worker.protocol.gen import worker_v1_pb2 as pb def fail_fsync(_value): raise OSError("fsync failed") if failure_point == "file": import stat real_fsync = artifacts_module.os.fsync def fail_file_fsync(descriptor): if stat.S_ISREG(os.fstat(descriptor).st_mode): fail_fsync(descriptor) return real_fsync(descriptor) monkeypatch.setattr(artifacts_module.os, "fsync", fail_file_fsync) else: real_fsync_parent = artifacts_module._fsync_parent_directory fsync_calls = 0 def fail_post_replace_fsync(directory): nonlocal fsync_calls fsync_calls += 1 if fsync_calls == 4: fail_fsync(directory) return real_fsync_parent(directory) monkeypatch.setattr( artifacts_module, "_fsync_parent_directory", fail_post_replace_fsync, ) store = artifacts_module.ArtifactStore(str(tmp_path / "staged")) with pytest.raises(OSError, match="fsync failed"): await store.publish( pb.TaskRef(task_id="task", attempt_id="attempt"), b"rendered audio", {"filename": "result.wav"}, key_id="panel", ) assert store._out == {} assert not any(files for _root, _dirs, files in os.walk(store._root)) @pytest.mark.asyncio async def test_parallel_input_retry_cannot_truncate_a_staged_reader( tmp_path, monkeypatch ): from threading import Event from worker.inbound import artifacts as artifacts_module from worker.protocol.gen import worker_v1_pb2 as pb payload = b"immutable reference audio" digest = hashlib.sha256(payload).hexdigest() store = artifacts_module.ArtifactStore(str(tmp_path / "staged")) ref = pb.ArtifactRef(artifact_id="input", filename="reference.wav") first = store.begin_input(ref, key_id="panel") with open(first, "wb") as handle: handle.write(payload) committed = store.commit_input( ref, first, digest, len(payload), key_id="panel" ) copy_opened = Event() allow_copy = Event() def blocked_copyfile(source, destination): with open(source, "rb") as source_handle: copy_opened.set() if not allow_copy.wait(timeout=2): raise TimeoutError("test did not release the staged input copy") with open(destination, "wb") as destination_handle: destination_handle.write(source_handle.read()) async def wait_until_set(event): while not event.is_set(): await asyncio.sleep(0) monkeypatch.setattr(artifacts_module.shutil, "copyfile", blocked_copyfile) destination = tmp_path / "executor-input.wav" staging = asyncio.create_task( store.stage_in(ref, str(destination), key_id="panel") ) await asyncio.wait_for(wait_until_set(copy_opened), timeout=1) retry = store.begin_input(ref, key_id="panel") assert retry != committed retry_handle = open(retry, "wb") try: # Keep the retry destination open and empty while the existing reader # consumes its source. A stable shared path would be truncated here. allow_copy.set() await staging assert destination.read_bytes() == payload retry_handle.write(payload) finally: retry_handle.close() retry_committed = store.commit_input( ref, retry, digest, len(payload), key_id="panel" ) assert retry_committed == committed files = [ os.path.join(root, name) for root, _directories, names in os.walk(store._root) for name in names ] assert files == [committed] @pytest.mark.asyncio async def test_duplicate_input_validation_never_blocks_other_panel_admission( tmp_path, monkeypatch ): from threading import Event, Timer from worker.inbound import artifacts as artifacts_module from worker.protocol.gen import worker_v1_pb2 as pb payload = b"immutable reference audio" digest = hashlib.sha256(payload).hexdigest() store = artifacts_module.ArtifactStore(str(tmp_path / "staged")) ref = pb.ArtifactRef(artifact_id="input", filename="reference.wav") first = store.begin_input(ref, key_id="panel", reserve_bytes=len(payload)) with open(first, "wb") as handle: handle.write(payload) committed = store.commit_input( ref, first, digest, len(payload), key_id="panel" ) retry = store.begin_input(ref, key_id="panel", reserve_bytes=len(payload)) with open(retry, "wb") as handle: handle.write(payload) validation_started = Event() release_validation = Event() real_matches = artifacts_module._file_matches def blocked_matches(path, expected_digest, expected_size): if path == committed: validation_started.set() if not release_validation.wait(timeout=10): raise TimeoutError("test did not release duplicate validation") return real_matches(path, expected_digest, expected_size) monkeypatch.setattr(artifacts_module, "_file_matches", blocked_matches) watchdog = Timer(5, release_validation.set) watchdog.start() committing = asyncio.create_task( store.commit_input_async( ref, retry, digest, len(payload), key_id="panel" ) ) async def wait_for_validation(): while not validation_started.is_set(): await asyncio.sleep(0) try: await asyncio.wait_for(wait_for_validation(), timeout=1) other = store.begin_input( pb.ArtifactRef(artifact_id="other", filename="reference.wav"), key_id="other-panel", reserve_bytes=1, ) store.discard_input(other) assert not release_validation.is_set(), ( "duplicate hashing blocked the gRPC event loop" ) finally: release_validation.set() watchdog.cancel() assert await committing == committed def test_input_commit_fsync_failure_exposes_no_input(tmp_path, monkeypatch): import stat from worker.inbound import artifacts as artifacts_module from worker.protocol.gen import worker_v1_pb2 as pb payload = b"reference audio" digest = hashlib.sha256(payload).hexdigest() store = artifacts_module.ArtifactStore(str(tmp_path / "staged")) ref = pb.ArtifactRef(artifact_id="input", filename="reference.wav") temporary = store.begin_input(ref, key_id="panel") with open(temporary, "wb") as handle: handle.write(payload) real_fsync = artifacts_module.os.fsync def fail_fsync(descriptor): if stat.S_ISREG(os.fstat(descriptor).st_mode): raise OSError("fsync failed") return real_fsync(descriptor) monkeypatch.setattr(artifacts_module.os, "fsync", fail_fsync) with pytest.raises(OSError, match="fsync failed"): store.commit_input( ref, temporary, digest, len(payload), key_id="panel" ) assert store._in == {} assert not any(files for _root, _dirs, files in os.walk(store._root)) @pytest.mark.asyncio async def test_adopted_input_final_is_durable_and_not_swept_as_an_orphan( tmp_path, monkeypatch ): import stat from worker.inbound import artifacts as artifacts_module from worker.protocol.gen import worker_v1_pb2 as pb payload = b"crash-surviving reference" digest = hashlib.sha256(payload).hexdigest() store = artifacts_module.ArtifactStore(str(tmp_path / "staged")) ref = pb.ArtifactRef(artifact_id="input", filename="reference.wav") final = store._input_final_path(ref, digest, key_id="panel") os.makedirs(os.path.dirname(final), exist_ok=True) with open(final, "wb") as handle: handle.write(payload) store._orphaned_paths.add(final) temporary = store.begin_input(ref, key_id="panel") with open(temporary, "wb") as handle: handle.write(payload) real_remove = artifacts_module.os.remove final_remove_attempts = 0 def transient_orphan_remove(path): nonlocal final_remove_attempts if path == final: final_remove_attempts += 1 if final_remove_attempts == 1: raise PermissionError("the crash-surviving final is still open") return real_remove(path) durability_events = [] real_fsync = artifacts_module.os.fsync def record_fsync(descriptor): kind = "file" if stat.S_ISREG(os.fstat(descriptor).st_mode) else "directory" durability_events.append(kind) return real_fsync(descriptor) monkeypatch.setattr(artifacts_module.os, "remove", transient_orphan_remove) monkeypatch.setattr(artifacts_module.os, "fsync", record_fsync) assert ( store.commit_input( ref, temporary, digest, len(payload), key_id="panel" ) == final ) assert final not in store._orphaned_paths file_fsync = durability_events.index("file") # The directory half of the barrier exists only where the platform has one. # `_fsync_parent_directory` returns immediately without `os.O_DIRECTORY`, # which Windows does not define — so gate on the same condition the product # uses rather than on the OS name, and the two cannot drift apart. if hasattr(os, "O_DIRECTORY"): assert "directory" in durability_events[file_fsync + 1 :] # Any later write runs the orphan sweep. The adopted final must no longer # be a deletion candidate once its durability barrier has succeeded. await store.publish( pb.TaskRef(task_id="task", attempt_id="attempt"), b"result", {"filename": "result.wav"}, key_id="panel", ) assert final_remove_attempts == 1 destination = tmp_path / "staged-input.wav" await store.stage_in(ref, str(destination), key_id="panel") assert destination.read_bytes() == payload @pytest.mark.asyncio async def test_cancelled_input_staging_drains_copy_before_returning( tmp_path, monkeypatch ): from threading import Event from worker.inbound import artifacts as artifacts_module from worker.protocol.gen import worker_v1_pb2 as pb real_copyfile = artifacts_module.shutil.copyfile copy_started = Event() allow_copy = Event() copy_finished = Event() def blocked_copyfile(source, destination): copy_started.set() if not allow_copy.wait(timeout=2): raise TimeoutError("test did not release the staged input copy") try: return real_copyfile(source, destination) finally: copy_finished.set() async def wait_until_set(event): while not event.is_set(): await asyncio.sleep(0) monkeypatch.setattr(artifacts_module.shutil, "copyfile", blocked_copyfile) store = artifacts_module.ArtifactStore(str(tmp_path / "staged")) ref = pb.ArtifactRef(artifact_id="input", filename="reference.wav") payload = b"reference audio" source = store.begin_input(ref, key_id="panel") with open(source, "wb") as handle: handle.write(payload) store.commit_input( ref, source, hashlib.sha256(payload).hexdigest(), len(payload), key_id="panel", ) destination = tmp_path / "input.part" staging = asyncio.create_task( store.stage_in(ref, str(destination), key_id="panel") ) await asyncio.wait_for(wait_until_set(copy_started), timeout=1) staging.cancel() await asyncio.sleep(0) cancellation_is_draining = not staging.done() allow_copy.set() with pytest.raises(asyncio.CancelledError): await staging await asyncio.wait_for(wait_until_set(copy_finished), timeout=1) assert cancellation_is_draining, "cancellation must wait for the active disk copy" destination.unlink() assert not destination.exists() async def _push_with_declared_hash(inbound, ref, path): """Push bytes while declaring a hash that does not describe them.""" from worker.inbound.listener import KEY_METADATA_KEY from worker.protocol.gen import worker_v1_pb2 as pb stub = inbound.connection._stub data = open(path, "rb").read() async def chunks(): yield pb.ArtifactChunk(ref=ref, offset=0, data=data, last=True) ack = await stub.PushInput( chunks(), metadata=((KEY_METADATA_KEY, inbound.connection._connection.secret),) ) if not ack.committed: raise RuntimeError(ack.error.message or "refused") @pytest.mark.asyncio async def test_a_different_machine_cannot_re_adopt_an_enrolled_workers_identity( inbound, tmp_path ): """The re-adoption path exists so a node that lost the id this panel gave it can still reconnect on proof of key possession. It must not become a way for a *different* key to inherit an enrolled worker: an attacker holding only the API key would otherwise take over the trusted machine's identity. """ from worker.protocol.gen import worker_v1_pb2 as pb await inbound.connect_panel() enrolled = inbound.worker.registry.list_workers()[0] # A second machine: valid key, valid self-signature, wrong identity. impostor = inbound.worker.WorkerKeypair.generate() challenge, nonce = b"c" * 32, b"n" * 32 forged = pb.RegisterRequest( envelope=pb.Envelope(sequence=0), worker_id=enrolled.id, public_key=impostor.public_bytes(), challenge=challenge, nonce=nonce, challenge_signature=impostor.sign( __import__("worker.identity", fromlist=["identity"]).challenge_message( challenge=challenge, worker_id=enrolled.id, session_epoch=0, nonce=nonce ) ), ) assert ( inbound.worker.NodeConnection._proves_key_possession(forged, enrolled) is False ) @pytest.mark.asyncio async def test_the_node_keeps_sending_heartbeats_after_it_registers( inbound, monkeypatch ): """A session that goes quiet is declared dead and flaps forever. Found on hardware, not here: the inbound Attach handler started the read pump and the outbound loop but never the heartbeat loop that the outbound path starts in `_connect_once`. The node registered, said nothing more, was declared dead ~90 seconds later, reconnected, and repeated — while every test in this file finished inside three seconds, comfortably within the grace window that hid it. So this test asserts on the frames themselves rather than on liveness: it watches the node's own outbox for a heartbeat, which is the thing that was missing, and does not depend on how long the grace window happens to be. """ # The interval the panel advertises, shortened so this asserts on a real # emitted frame in a second rather than waiting out the production value. from worker.transport import server as server_module monkeypatch.setattr(server_module, "_HEARTBEAT_INTERVAL_SECONDS", 1) seen = [] client_box = {} original = inbound._client def capture(artifacts, key_id): client = original(artifacts, key_id) client_box["client"] = client real_send = client._send async def spy(message, **kwargs): if message.WhichOneof("payload") == "heartbeat": seen.append(message) return await real_send(message, **kwargs) client._send = spy return client inbound.listener._servicer._client_factory = capture await inbound.connect_panel() # Drive the loop rather than waiting out a real interval: the bug is a # missing task, not a slow one, so what matters is that something is # scheduled to produce these at all. await _until(lambda: len(seen) >= 2, timeout=15.0) assert len(seen) >= 2, "the node registered and then never sent a heartbeat" @pytest.mark.asyncio async def test_a_staged_result_comes_back_whole_when_its_ref_declares_a_size( inbound, tmp_path ): """A real result ref carries size_bytes, and that must not be read as an offset. Found on hardware: FetchResult seeked to `request.size_bytes` as if it were a resume point, so it started at EOF, yielded nothing, and the fetch failed with "the result ended before its final chunk" — while the finished render sat on the node's disk. Every earlier test called publish/stage directly and never exercised FetchResult with a populated ref, which is why it survived. """ from worker.protocol.gen import worker_v1_pb2 as pb await inbound.connect_panel() payload = b"rendered audio bytes" * 1000 ref = await inbound.artifacts.publish( pb.TaskRef(task_id="t1", attempt_id="a1"), payload, {"filename": "out.wav"}, key_id=inbound.panel_key_id, ) assert ref.size_bytes == len(payload), "the ref must declare the real size" destination = tmp_path / "fetched.wav" await inbound.connection.fetch_result(ref, str(destination)) assert destination.read_bytes() == payload @pytest.mark.asyncio async def test_result_pull_stops_at_its_runtime_byte_cap(tmp_path): from worker.inbound.connection_string import Connection from worker.protocol.gen import worker_v1_pb2 as pb connection = _worker_modules().NodeConnection( object(), Connection( host="127.0.0.1", port=7444, secret="ovnode_" + "s" * 40, fingerprint="a" * 64, ), ) class Stub: async def FetchResult(self, _request, metadata=()): yield pb.ResultChunk(offset=0, data=b"too large", last=True) connection._stub = Stub() destination = tmp_path / "partial.wav" with pytest.raises(RuntimeError, match="larger than"): await connection.fetch_result( pb.ArtifactRef(artifact_id="a1"), str(destination), max_bytes=4 ) assert not destination.exists() @pytest.mark.asyncio async def test_cancelled_result_pull_drains_off_loop_write_before_unlink( tmp_path, monkeypatch ): from threading import Event, Timer from worker.inbound import connector as connector_module from worker.inbound.connection_string import Connection from worker.protocol.gen import worker_v1_pb2 as pb connection = _worker_modules().NodeConnection( object(), Connection( host="127.0.0.1", port=7444, secret="ovnode_" + "s" * 40, fingerprint="a" * 64, ), ) class Stub: async def FetchResult(self, _request, metadata=()): yield pb.ResultChunk(offset=0, data=b"rendered audio", last=True) connection._stub = Stub() write_started = Event() release_write = Event() real_write_all = connector_module._write_all def blocked_write(handle, payload): write_started.set() if not release_write.wait(timeout=10): raise TimeoutError("test did not release the fetched-result write") real_write_all(handle, payload) monkeypatch.setattr(connector_module, "_write_all", blocked_write) destination = tmp_path / "partial.wav" watchdog = Timer(5, release_write.set) watchdog.start() fetching = asyncio.create_task( connection.fetch_result( pb.ArtifactRef(artifact_id="a1"), str(destination) ) ) async def wait_for_write(): while not write_started.is_set(): await asyncio.sleep(0) try: await asyncio.wait_for(wait_for_write(), timeout=1) fetching.cancel() await asyncio.sleep(0) assert not fetching.done(), "cancellation abandoned an active file write" assert not release_write.is_set(), ( "fetched-result writing blocked the gRPC event loop" ) finally: release_write.set() watchdog.cancel() with pytest.raises(asyncio.CancelledError): await fetching assert not destination.exists() @pytest.mark.asyncio async def test_repasting_a_key_for_a_connected_machine_redials_it(inbound, monkeypatch): """Re-pasting must replace the live session, not report success against it. Found on hardware: `add` saved the new string and then short-circuited because a connection to that endpoint already existed. A wrong key therefore overwrote a working one, answered 200 with connected=true from the stale session, and only failed after a restart — by which point nothing pointed back at the paste that caused it. """ from worker.inbound import service as inbound_service await inbound.connect_panel() outbound = inbound_service.OutboundNodes(inbound.keys) saved = [] monkeypatch.setattr(outbound, "saved", lambda: list(saved)) monkeypatch.setattr( outbound, "_save", lambda entries: saved.clear() or saved.extend(entries) ) first = inbound.worker.format_connection( host="127.0.0.1", port=inbound.port, secret=inbound.keys.issue("One").secret, fingerprint=inbound.credentials.fingerprint, ) await outbound.add(first, inbound.servicer) original = outbound._connections[f"127.0.0.1:{inbound.port}"] second = inbound.worker.format_connection( host="127.0.0.1", port=inbound.port, secret=inbound.keys.issue("Two").secret, fingerprint=inbound.credentials.fingerprint, ) await outbound.add(second, inbound.servicer) endpoint = f"127.0.0.1:{inbound.port}" assert saved == [endpoint], "settings must persist only the non-secret endpoint" parsed_second = inbound.worker.parse_connection(second) assert inbound.keys.connection_secret(endpoint) == parsed_second.secret assert inbound.keys.connection_fingerprint(endpoint) == parsed_second.fingerprint assert outbound._connections[f"127.0.0.1:{inbound.port}"] is not original, ( "the old session must be replaced, not reused" ) await outbound.stop() @pytest.mark.asyncio async def test_wrong_replacement_key_preserves_the_working_connection( inbound, monkeypatch ): from worker.inbound import service as inbound_service from worker.inbound.connector import InboundConnectionError await inbound.connect_panel() outbound = inbound_service.OutboundNodes(inbound.keys) saved = [] monkeypatch.setattr(outbound, "saved", lambda: list(saved)) monkeypatch.setattr( outbound, "_save", lambda entries: saved.clear() or saved.extend(entries) ) issued = inbound.keys.issue("Working") working_text = inbound.worker.format_connection( host="127.0.0.1", port=inbound.port, secret=issued.secret, fingerprint=inbound.credentials.fingerprint, ) await outbound.add(working_text, inbound.servicer) endpoint = f"127.0.0.1:{inbound.port}" await _until(lambda: bool(outbound._connections[endpoint].worker_id)) original = outbound._connections[endpoint] original_task = outbound._tasks[endpoint] original_saved = list(saved) wrong_text = inbound.worker.format_connection( host="127.0.0.1", port=inbound.port, secret="ovnode_" + "x" * 43, fingerprint=inbound.credentials.fingerprint, ) with pytest.raises(InboundConnectionError): await outbound.add(wrong_text, inbound.servicer) assert outbound._connections[endpoint] is original assert outbound._tasks[endpoint] is original_task assert not original_task.done() assert saved == original_saved assert inbound.keys.connection_secret(endpoint) == issued.secret await outbound.stop() @pytest.mark.asyncio async def test_offline_retained_replacement_preserves_old_credentials( inbound, monkeypatch ): from worker.inbound import service as inbound_service from worker.inbound.connector import RemoteShutdownUnavailable await inbound.connect_panel() outbound = inbound_service.OutboundNodes(inbound.keys) saved = [] monkeypatch.setattr(outbound, "saved", lambda: list(saved)) monkeypatch.setattr( outbound, "_save", lambda entries: saved.clear() or saved.extend(entries) ) old = inbound.keys.issue("Old") old_text = inbound.worker.format_connection( host="127.0.0.1", port=inbound.port, secret=old.secret, fingerprint=inbound.credentials.fingerprint, ) await outbound.add(old_text, inbound.servicer) endpoint = f"127.0.0.1:{inbound.port}" await _until(lambda: bool(outbound._connections[endpoint].worker_id)) original = outbound._connections[endpoint] original_task = outbound._tasks[endpoint] original_task.cancel() await asyncio.gather(original_task, return_exceptions=True) assert original._remote_protocol_retained is True replacement = inbound.keys.issue("Replacement") replacement_text = inbound.worker.format_connection( host="127.0.0.1", port=inbound.port, secret=replacement.secret, fingerprint=inbound.credentials.fingerprint, ) with pytest.raises(RemoteShutdownUnavailable): await outbound.add(replacement_text, inbound.servicer) assert outbound._connections[endpoint] is original assert outbound._tasks[endpoint] is original_task assert saved == [endpoint] assert inbound.keys.connection_secret(endpoint) == old.secret await outbound.stop() @pytest.mark.asyncio async def test_concurrent_replacements_leave_live_and_durable_keys_in_step( inbound, monkeypatch ): from worker.inbound import service as inbound_service await inbound.connect_panel() outbound = inbound_service.OutboundNodes(inbound.keys) saved = [] monkeypatch.setattr(outbound, "saved", lambda: list(saved)) monkeypatch.setattr( outbound, "_save", lambda entries: saved.clear() or saved.extend(entries) ) endpoint = f"127.0.0.1:{inbound.port}" def connection_text(label): issued = inbound.keys.issue(label) return issued, inbound.worker.format_connection( host="127.0.0.1", port=inbound.port, secret=issued.secret, fingerprint=inbound.credentials.fingerprint, ) initial, initial_text = connection_text("Initial") await outbound.add(initial_text, inbound.servicer) await _until(lambda: bool(outbound._connections[endpoint].worker_id)) first, first_text = connection_text("First") second, second_text = connection_text("Second") await asyncio.gather( outbound.add(first_text, inbound.servicer), outbound.add(second_text, inbound.servicer), ) live = outbound._connections[endpoint] assert live._connection.secret == second.secret assert inbound.keys.connection_secret(endpoint) == second.secret assert saved == [endpoint] assert initial.secret != first.secret != second.secret await outbound.stop() @pytest.mark.asyncio async def test_shutdown_disconnect_before_goodbye_fails_without_forgetting_state(): from worker.inbound.connection_string import Connection from worker.inbound.connector import NodeConnection, RemoteShutdownUnavailable connection = NodeConnection( object(), Connection( host="127.0.0.1", port=7444, secret="ovnode_" + "s" * 40, fingerprint="a" * 64, ), ) connection._active_session = object() connection._remote_protocol_retained = True connection._session_closed.clear() stopping = asyncio.create_task(connection.stop()) message = await asyncio.wait_for(connection._outbox.get(), timeout=1) assert message.WhichOneof("payload") == "shutdown" connection._active_session = None connection._session_closed.set() with pytest.raises(RemoteShutdownUnavailable, match="disconnected"): await stopping assert connection._remote_protocol_retained is True assert not connection._stop.is_set() @pytest.mark.asyncio @pytest.mark.parametrize("operation", ["drop", "stop"]) async def test_outbound_teardown_waits_for_connector_cleanup(operation): """Removal must not return while the old worker is still schedulable.""" from worker.inbound import service as inbound_service endpoint = "gpu-node:7444" outbound = inbound_service.OutboundNodes() connector_started = asyncio.Event() cleanup_started = asyncio.Event() release_cleanup = asyncio.Event() worker_is_schedulable = True class Connection: async def stop(self): pass async def run_connector(): nonlocal worker_is_schedulable connector_started.set() try: await asyncio.Event().wait() finally: cleanup_started.set() await release_cleanup.wait() worker_is_schedulable = False connector_task = asyncio.create_task(run_connector()) await connector_started.wait() outbound._connections[endpoint] = Connection() outbound._tasks[endpoint] = connector_task if operation == "drop": teardown = asyncio.create_task(outbound._drop(endpoint)) else: teardown = asyncio.create_task(outbound.stop()) await asyncio.wait_for(cleanup_started.wait(), timeout=1) try: assert not teardown.done() assert worker_is_schedulable finally: release_cleanup.set() await asyncio.gather(teardown, connector_task, return_exceptions=True) assert not worker_is_schedulable assert endpoint not in outbound._connections assert endpoint not in outbound._tasks @pytest.mark.asyncio async def test_failed_connection_removal_still_fences_the_live_session( tmp_path, monkeypatch ): from worker.inbound import service as inbound_service endpoint = "gpu-node:7444" secret = "ovnode_" + "s" * 40 store = _worker_modules().KeyStore(str(tmp_path / "keys.json")) store.remember_connection_secret(endpoint, secret, "a" * 64) outbound = inbound_service.OutboundNodes(store) outbound._servicer = object() saved = [endpoint] stopped = asyncio.Event() redialled = [] class Connection: async def stop(self): stopped.set() async def connector(): await asyncio.Event().wait() task = asyncio.create_task(connector()) outbound._connections[endpoint] = Connection() outbound._tasks[endpoint] = task monkeypatch.setattr(outbound, "saved", lambda: list(saved)) monkeypatch.setattr( outbound, "_save", lambda entries: saved.clear() or saved.extend(entries) ) monkeypatch.setattr( store, "forget_connection_secret", lambda _endpoint: (_ for _ in ()).throw(OSError("disk full")), ) async def capture_redial(connection, servicer, **_kwargs): redialled.append((connection, servicer)) monkeypatch.setattr(outbound, "_dial", capture_redial) with pytest.raises(OSError, match="disk full"): await outbound.remove(endpoint) assert stopped.is_set() assert task.done() assert saved == [endpoint] assert store.connection_secret(endpoint) == secret assert len(redialled) == 1 assert redialled[0][0].endpoint == endpoint assert redialled[0][0].secret == secret assert redialled[0][1] is outbound._servicer assert endpoint not in outbound._connections assert endpoint not in outbound._tasks @pytest.mark.asyncio async def test_cancelled_replacement_restores_live_and_durable_generation( tmp_path, monkeypatch ): from worker.inbound import connector as connector_module from worker.inbound import service as inbound_service from worker.inbound.connection_string import format_connection endpoint = "gpu-node:7444" old_secret = "ovnode_" + "o" * 40 new_secret = "ovnode_" + "n" * 40 fingerprint = "a" * 64 store = _worker_modules().KeyStore(str(tmp_path / "keys.json")) store.remember_connection_secret(endpoint, old_secret, fingerprint) outbound = inbound_service.OutboundNodes(store) saved = [endpoint] monkeypatch.setattr(outbound, "saved", lambda: list(saved)) monkeypatch.setattr( outbound, "_save", lambda entries: saved.clear() or saved.extend(entries) ) class OldConnection: async def stop(self): pass async def old_connector(): await asyncio.Event().wait() old_task = asyncio.create_task(old_connector()) outbound._connections[endpoint] = OldConnection() outbound._tasks[endpoint] = old_task candidate_waiting = asyncio.Event() made = [] class Candidate: def __init__(self, _servicer, connection): self._connection = connection self.closed = False made.append(self) async def probe(self): pass async def run_forever(self): await asyncio.Event().wait() async def wait_until_registered(self, _task): candidate_waiting.set() await asyncio.Event().wait() async def close(self): self.closed = True monkeypatch.setattr(connector_module, "NodeConnection", Candidate) replacement = format_connection( host="gpu-node", port=7444, secret=new_secret, fingerprint=fingerprint, ) adding = asyncio.create_task(outbound.add(replacement, object())) await asyncio.wait_for(candidate_waiting.wait(), timeout=1) adding.cancel() with pytest.raises(asyncio.CancelledError): await adding assert old_task.done() assert made[1].closed is True assert outbound._connections[endpoint]._connection.secret == old_secret assert not outbound._tasks[endpoint].done() assert store.connection_secret(endpoint) == old_secret assert store.connection_fingerprint(endpoint) == fingerprint assert saved == [endpoint] await outbound.stop() @pytest.mark.asyncio async def test_cancelled_removal_drains_rollback_and_redials_previous_generation( tmp_path, monkeypatch ): from worker.inbound import service as inbound_service endpoint = "gpu-node:7444" secret = "ovnode_" + "s" * 40 fingerprint = "a" * 64 store = _worker_modules().KeyStore(str(tmp_path / "keys.json")) store.remember_connection_secret(endpoint, secret, fingerprint) outbound = inbound_service.OutboundNodes(store) outbound._servicer = object() saved = [endpoint] monkeypatch.setattr(outbound, "saved", lambda: list(saved)) monkeypatch.setattr( outbound, "_save", lambda entries: saved.clear() or saved.extend(entries) ) class Connection: async def stop(self): pass async def connector(): await asyncio.Event().wait() connector_task = asyncio.create_task(connector()) outbound._connections[endpoint] = Connection() outbound._tasks[endpoint] = connector_task monkeypatch.setattr( store, "forget_connection_secret", lambda _endpoint: (_ for _ in ()).throw(asyncio.CancelledError()), ) rollback_started = asyncio.Event() release_rollback = asyncio.Event() redialled = [] async def blocked_redial(connection, servicer, **_kwargs): redialled.append((connection, servicer)) rollback_started.set() await release_rollback.wait() outbound._connections[endpoint] = Connection() monkeypatch.setattr(outbound, "_dial", blocked_redial) removing = asyncio.create_task(outbound.remove(endpoint)) await asyncio.wait_for(rollback_started.wait(), timeout=1) removing.cancel() await asyncio.sleep(0) assert not removing.done(), "a second cancellation abandoned rollback" release_rollback.set() with pytest.raises(asyncio.CancelledError): await removing assert connector_task.done() assert len(redialled) == 1 assert redialled[0][0].secret == secret assert redialled[0][1] is outbound._servicer assert endpoint in outbound._connections assert store.connection_secret(endpoint) == secret assert store.connection_fingerprint(endpoint) == fingerprint assert saved == [endpoint] @pytest.mark.asyncio async def test_failed_replacement_surfaces_credential_rollback_failure( tmp_path, monkeypatch ): from worker.inbound import connector as connector_module from worker.inbound import service as inbound_service from worker.inbound.connection_string import format_connection from worker.inbound.connector import ( InboundConnectionError, InboundConnectionRollbackError, ) endpoint = "gpu-node:7444" old_secret = "ovnode_" + "o" * 40 new_secret = "ovnode_" + "n" * 40 fingerprint = "a" * 64 store = _worker_modules().KeyStore(str(tmp_path / "keys.json")) store.remember_connection_secret(endpoint, old_secret, fingerprint) outbound = inbound_service.OutboundNodes(store) saved = [endpoint] monkeypatch.setattr(outbound, "saved", lambda: list(saved)) monkeypatch.setattr( outbound, "_save", lambda entries: saved.clear() or saved.extend(entries) ) class Existing: async def stop(self): pass async def connector(): await asyncio.Event().wait() old_task = asyncio.create_task(connector()) outbound._connections[endpoint] = Existing() outbound._tasks[endpoint] = old_task class Probe: def __init__(self, *_args): pass async def probe(self): pass monkeypatch.setattr(connector_module, "NodeConnection", Probe) async def fail_candidate(connection, _servicer, **_kwargs): raise InboundConnectionError( f"candidate {connection.secret[-1]} failed readiness" ) monkeypatch.setattr(outbound, "_dial", fail_candidate) real_remember = store.remember_connection_secret def fail_old_restore(target, secret, stored_fingerprint): if secret == old_secret: raise OSError("credential fsync failed") return real_remember(target, secret, stored_fingerprint) monkeypatch.setattr(store, "remember_connection_secret", fail_old_restore) replacement = format_connection( host="gpu-node", port=7444, secret=new_secret, fingerprint=fingerprint, ) with pytest.raises(InboundConnectionRollbackError, match="remains stopped") as caught: await outbound.add(replacement, object()) assert isinstance(caught.value.__cause__, InboundConnectionError) assert store.connection_secret(endpoint) == new_secret assert endpoint not in outbound._connections assert endpoint not in outbound._tasks assert old_task.done() assert saved == [endpoint] @pytest.mark.asyncio async def test_failed_removal_surfaces_durable_rollback_failure( tmp_path, monkeypatch ): from worker.inbound import service as inbound_service from worker.inbound.connector import InboundConnectionRollbackError endpoint = "gpu-node:7444" secret = "ovnode_" + "s" * 40 fingerprint = "a" * 64 store = _worker_modules().KeyStore(str(tmp_path / "keys.json")) store.remember_connection_secret(endpoint, secret, fingerprint) outbound = inbound_service.OutboundNodes(store) outbound._servicer = object() saved = [endpoint] monkeypatch.setattr(outbound, "saved", lambda: list(saved)) save_calls = 0 def fail_removal_save(entries): nonlocal save_calls save_calls += 1 if save_calls == 1: raise OSError("settings commit failed") saved.clear() saved.extend(entries) monkeypatch.setattr(outbound, "_save", fail_removal_save) class Existing: async def stop(self): pass async def connector(): await asyncio.Event().wait() old_task = asyncio.create_task(connector()) outbound._connections[endpoint] = Existing() outbound._tasks[endpoint] = old_task monkeypatch.setattr( store, "remember_connection_secret", lambda *_args: (_ for _ in ()).throw(OSError("credential restore failed")), ) with pytest.raises(InboundConnectionRollbackError, match="remains stopped") as caught: await outbound.remove(endpoint) assert isinstance(caught.value.__cause__, OSError) assert store.connection_secret(endpoint) == "" assert endpoint not in outbound._connections assert endpoint not in outbound._tasks assert old_task.done() assert saved == [endpoint] @pytest.mark.asyncio async def test_saved_endpoint_reloads_its_key_from_protected_storage(tmp_path, monkeypatch): from worker.inbound import service as inbound_service store = _worker_modules().KeyStore(str(tmp_path / "keys.json")) endpoint = "10.0.0.2:7444" secret = "ovnode_" + "s" * 40 fingerprint = "a" * 64 store.remember_connection_secret(endpoint, secret, fingerprint) outbound = inbound_service.OutboundNodes(store) monkeypatch.setattr(outbound, "saved", lambda: [endpoint]) dialled = [] async def capture(connection, _servicer): dialled.append(connection) monkeypatch.setattr(outbound, "_dial", capture) await outbound.start_all(object()) assert len(dialled) == 1 assert dialled[0].endpoint == endpoint assert dialled[0].secret == secret assert dialled[0].fingerprint == fingerprint @pytest.mark.asyncio async def test_repasting_an_identical_terminal_connection_really_redials( tmp_path, monkeypatch ): from worker.inbound import service as inbound_service from worker.inbound.connection_string import format_connection, parse_connection endpoint = "10.0.0.2:7444" secret = "ovnode_" + "s" * 40 fingerprint = "a" * 64 text = format_connection( host="10.0.0.2", port=7444, secret=secret, fingerprint=fingerprint ) parsed = parse_connection(text) store = _worker_modules().KeyStore(str(tmp_path / "keys.json")) store.remember_connection_secret(endpoint, secret, fingerprint) outbound = inbound_service.OutboundNodes(store) monkeypatch.setattr(outbound, "saved", lambda: [endpoint]) class DeadConnection: _connection = parsed async def finished(): return None dead_task = asyncio.create_task(finished()) await dead_task outbound._connections[endpoint] = DeadConnection() outbound._tasks[endpoint] = dead_task dialled = [] async def capture(connection, servicer, *, wait_until_ready=False): dialled.append((connection, servicer, wait_until_ready)) monkeypatch.setattr(outbound, "_dial", capture) servicer = object() await outbound.add(text, servicer) assert dialled == [(parsed, servicer, True)] assert endpoint not in outbound._connections assert endpoint not in outbound._tasks @pytest.mark.asyncio async def test_a_stale_frame_cannot_poison_the_next_attach(inbound): """The outbox must not survive a dead session. Found on hardware. The queue was built once per NodeConnection and reused across reconnects, so a frame left behind by a dying session became the FIRST frame of the next attach. The node requires a registration there, aborted the call, and the two span at full speed — session epoch 2445 inside one second, with the node logging "Locally aborted" on repeat and the panel reporting the worker offline while it was visibly connected. """ from worker.protocol.gen import worker_v1_pb2 as pb await inbound.connect_panel() connection = inbound.connection # Exactly what a torn-down session leaves behind: an unsent frame, still # queued, that would be handed to the next attach as its opening word. stale = pb.ServerMessage(ping=pb.Ping(nonce=7)) connection._outbox.put_nowait(stale) poisoned = connection._outbox task = asyncio.create_task(connection._connect_once()) try: await _until(lambda: connection._outbox is not poisoned) assert connection._outbox is not poisoned, "the dead session's queue was reused" finally: task.cancel() await asyncio.gather(task, return_exceptions=True) @pytest.mark.asyncio async def test_a_nested_input_id_is_accepted_the_way_staging_really_writes_it( inbound, tmp_path ): """Staged inputs are nested, and the node must take them as they come. `task_store.stage_input` mints `inputs/` — a path, not a bare name. The node ran `safe_filename` on it, which rejects anything nested, so every real clone input was refused, the dispatch failed, and the scheduler retried about eighteen times a second while the GPU sat idle and the user watched a spinner. Every earlier test used a flat id like "ref-1" and so never touched the shape production actually produces. """ from worker.protocol.gen import worker_v1_pb2 as pb await inbound.connect_panel() source = tmp_path / "reference.wav" source.write_bytes(b"reference audio bytes") nested = pb.ArtifactRef( artifact_id="inputs/0f1e2d3c4b5a69788796a5b4c3d2e1f0.wav", filename="reference.wav", ) declared = await inbound.connection.push_input(nested, str(source)) destination = tmp_path / "staged.wav" await inbound.artifacts.stage_in( declared, str(destination), key_id=inbound.panel_key_id ) assert destination.read_bytes() == b"reference audio bytes" @pytest.mark.asyncio async def test_a_pushed_input_cannot_escape_the_staging_directory(inbound, tmp_path): """Accepting nested ids must not mean accepting traversal. The id is now hashed rather than used as a path, so it cannot steer placement at all. The declared FILENAME still can, and is still required to be a bare name — that is the containment the old check was really buying, and it must not have been traded away to fix the rejection above. """ from worker.protocol.gen import worker_v1_pb2 as pb await inbound.connect_panel() source = tmp_path / "evil.wav" source.write_bytes(b"payload") hostile = pb.ArtifactRef( artifact_id="../../../../../../tmp/escaped.wav", filename="../../escaped.wav" ) with pytest.raises(RuntimeError, match="bare filename|did not accept"): await inbound.connection.push_input(hostile, str(source)) # And nothing was left behind by the refusal. root = inbound.artifacts._root assert not any("escaped" in name for _, _, files in os.walk(root) for name in files) @pytest.mark.asyncio async def test_an_inbound_only_node_still_unloads_idle_models(monkeypatch, tmp_path): """Requirement: free models nothing has used for ten minutes. The sweep used to live inside the dial-out agent, which an inbound-only node never starts — so a machine lending its GPU to panels that dial IN held several GB of weights forever. That is exactly the cost the sweep exists to avoid, and it was absent in the mode most likely to be a shared box: on hardware, that node's VRAM never came back. """ from worker import agent as agent_module released = {"count": 0} refreshed = {"count": 0} def fake_release(): released["count"] += 1 return ["indextts"] async def fake_refresh(): refreshed["count"] += 1 monkeypatch.setattr(agent_module, "IDLE_SWEEP_INTERVAL_SECONDS", 0.05) import services.model_manager as model_manager import services.tts_backend as tts_backend monkeypatch.setattr(tts_backend, "release_idle_engines", fake_release) monkeypatch.setattr( model_manager, "gpu_pool_stats", lambda: {"running": 0, "queued": 0} ) task = asyncio.create_task(agent_module.idle_unload_loop(fake_refresh)) try: await _until( lambda: released["count"] >= 1 and refreshed["count"] >= 1, timeout=5.0 ) finally: task.cancel() await asyncio.gather(task, return_exceptions=True) assert released["count"] >= 1, "nothing swept idle engines" assert refreshed["count"] >= 1, "freed VRAM was never re-advertised" @pytest.mark.asyncio async def test_enabling_inbound_starts_the_idle_sweep(monkeypatch, tmp_path): """The wiring, not just the loop: the gap was a missing caller.""" from worker import agent as agent_module from worker.inbound import service as inbound_service started = asyncio.Event() received = [] async def idle_unload_sentinel(refresh): received.append(refresh) started.set() await asyncio.Event().wait() monkeypatch.setattr(agent_module, "idle_unload_loop", idle_unload_sentinel) node = inbound_service.InboundNode() monkeypatch.setattr(inbound_service, "bind_host", lambda: "127.0.0.1") monkeypatch.setattr(inbound_service, "bind_port", lambda: 0) monkeypatch.setattr( inbound_service, "paths", lambda: { "keys": str(tmp_path / "k.json"), "staged": str(tmp_path / "s"), "certificate": str(tmp_path / "inbound.crt"), "private_key": str(tmp_path / "inbound.key"), }, ) monkeypatch.setattr(node, "_client_factory", lambda artifacts, key_id: None) await node.start() try: await asyncio.wait_for(started.wait(), timeout=2.0) assert received == [node._listener.refresh_all] finally: await node.stop() assert node._idle_sweep is None @pytest.mark.asyncio async def test_concurrent_inbound_starts_publish_only_one_listener(monkeypatch): from worker.inbound import service as inbound_service node = inbound_service.InboundNode() entered = asyncio.Event() release = asyncio.Event() listener = object() calls = 0 async def staged_start(): nonlocal calls if node._listener is not None: return calls += 1 entered.set() await release.wait() node._listener = listener monkeypatch.setattr(node, "_start", staged_start) first = asyncio.create_task(node.start()) await asyncio.wait_for(entered.wait(), timeout=1) second = asyncio.create_task(node.start()) await asyncio.sleep(0) assert calls == 1 release.set() await asyncio.gather(first, second) assert calls == 1 assert node._listener is listener @pytest.mark.asyncio async def test_cancelled_listener_bind_closes_server_before_losing_its_handle( tmp_path, monkeypatch ): from worker.inbound import listener as listener_module worker = _worker_modules() start_entered = asyncio.Event() release_start = asyncio.Event() stopped = asyncio.Event() class Server: def add_secure_port(self, _bind, _credentials): return 7444 async def start(self): start_entered.set() await release_start.wait() async def stop(self, grace): assert grace == 0 stopped.set() server = Server() monkeypatch.setattr(listener_module.grpc.aio, "server", lambda **_kwargs: server) monkeypatch.setattr( listener_module.pb_grpc, "add_NodeServiceServicer_to_server", lambda *_args: None, ) listener = worker.NodeListener( keys=worker.KeyStore(str(tmp_path / "keys.json")), log=worker.ConnectionLog(), artifacts=worker.ArtifactStore(str(tmp_path / "staged")), client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), ) starting = asyncio.create_task( listener.start(host="127.0.0.1", port=7444) ) await asyncio.wait_for(start_entered.wait(), timeout=1) starting.cancel() await asyncio.sleep(0) assert not starting.done(), "bind cancellation abandoned a starting server" release_start.set() with pytest.raises(asyncio.CancelledError): await starting assert stopped.is_set() assert listener.running is False assert listener.port == 0 @pytest.mark.asyncio async def test_listener_stop_failure_retains_handle_for_retry(tmp_path, monkeypatch): worker = _worker_modules() listener = worker.NodeListener( keys=worker.KeyStore(str(tmp_path / "keys.json")), log=worker.ConnectionLog(), artifacts=worker.ArtifactStore(str(tmp_path / "staged")), client_factory=lambda *_args: None, credentials=worker.tls.generate_self_signed(hostnames=["127.0.0.1"]), ) attempts = 0 class Server: async def stop(self, *, grace): nonlocal attempts assert grace == 1.0 attempts += 1 if attempts == 1: raise OSError("listener stop failed") server = Server() listener._server = server listener._bound_port = 7444 monkeypatch.setattr(listener._servicer, "stop", lambda: asyncio.sleep(0)) with pytest.raises(OSError, match="listener stop failed"): await listener.stop() assert listener._server is server assert listener.port == 7444 await listener.stop() assert listener.running is False assert attempts == 2 @pytest.mark.asyncio async def test_cancelled_node_stop_drains_listener_before_clearing_handle(): from worker.inbound import service as inbound_service node = inbound_service.InboundNode() stop_entered = asyncio.Event() release_stop = asyncio.Event() class Listener: async def stop(self): stop_entered.set() await release_stop.wait() listener = Listener() node._listener = listener stopping = asyncio.create_task(node.stop()) await asyncio.wait_for(stop_entered.wait(), timeout=1) stopping.cancel() await asyncio.sleep(0) assert not stopping.done(), "node cancellation abandoned a live listener" assert node._listener is listener release_stop.set() with pytest.raises(asyncio.CancelledError): await stopping assert node._listener is None