Skip to content

Commit 51b9053

Browse files
fix: attach the bridge session before it can serve messages
The worker created its SyncBridgeGateway inside host.call and only attached it to the sync gateway afterwards on the main thread. A coordinator message arriving in that window (STATUS, CHANNEL_EXEC as the first action on a fresh gateway) dispatched into a gateway whose _trio_session was still None, so the reply fell through to the sync IO stub and killed the session -- the whole test suite under pytest -n 12 showed this as freshly created gateways being dead on arrival. The race predates the B.5 facade (ProtocolSession had the same window). SyncBridgeGateway now attaches itself in __init__, before its serve task can dispatch anything, and the redundant attach calls at the two construction sites are gone. Also drop the apipkg-era unknown-attribute test from the namespace tests -- unknown attributes are plain Python module behaviour now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 33decae commit 51b9053

3 files changed

Lines changed: 8 additions & 14 deletions

File tree

src/execnet/_trio_host.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ def __init__(
220220
self._done_sync = threading.Event()
221221
self._send_closed = False
222222
self._send_lock = threading.Lock()
223+
# Attach before any serving can happen: the first inbound message
224+
# may need to reply through gateway._send, which must already
225+
# route to this session (not the sync IO stub).
226+
sync_gateway._attach_trio_session(self)
223227

224228
# -- engine hooks (run on the host loop) --
225229

@@ -455,11 +459,9 @@ def _make_gateway(self, stream: ByteStream, spec: Any) -> AsyncGateway:
455459
import execnet
456460

457461
sync_gw = execnet.Gateway(_TempIO(self.group.execmodel), spec)
458-
bridge = SyncBridgeGateway(
462+
return SyncBridgeGateway(
459463
stream, id=spec.id, sync_gateway=sync_gw, host=self.host
460464
)
461-
sync_gw._attach_trio_session(bridge)
462-
return bridge
463465

464466
async def _open_via_stream(self, spec: Any) -> ByteStream:
465467
from . import _provision

src/execnet/_trio_worker.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,11 @@ def _run_worker(host: _trio_host.TrioHost, io: Any, id: str, model: ExecModel) -
225225
gateway, trio_exec, main_thread_only = _build_worker_gateway(host, id, model)
226226

227227
async def _start() -> _trio_host.SyncBridgeGateway:
228+
# The bridge attaches itself to the gateway before serving starts,
229+
# so inbound messages can reply through gateway._send right away.
228230
return await host.start_session(gateway, io)
229231

230-
session = host.call(_start)
231-
gateway._attach_trio_session(session)
232+
host.call(_start)
232233

233234
try:
234235
if main_thread_only:

testing/test_namespaces.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,3 @@ def test_import_execnet_does_not_import_trio() -> None:
7474
check=True,
7575
)
7676
assert out.stdout.strip() == "False"
77-
78-
79-
def test_unknown_attribute_raises() -> None:
80-
try:
81-
execnet.does_not_exist
82-
except AttributeError as exc:
83-
assert "does_not_exist" in str(exc)
84-
else:
85-
raise AssertionError("expected AttributeError")

0 commit comments

Comments
 (0)