You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: record the surface review in the changelog and handoffs
Adds the changelog entries for the four namespaces, the shared Host, the
loop guards, profile=/main_thread_only and the aio cancellation fix, and
corrects the two earlier entries that still advertised execnet.portal.
The phase C handoff gains a "Surface review" section that wins over
everything below it, and the boundary-protocol handoff is marked
superseded where it promised a Wakener extension point. Also drops the
EXECMODEL_PROFILES entry from the gateway_base shim: that name was added
on this branch, so it never existed pre-Trio and nothing can be
forwarding to it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The public surface had settled commit by commit and was never reviewed as
23
+
a whole. Doing that before the Phase D docs froze it produced the
24
+
following, which **wins over anything below or in
25
+
`handoff-boundary-protocol-rethink.md` that contradicts it**.
26
+
27
+
**Namespaces are now one per concurrency library you drive execnet from**:
28
+
`execnet.sync` (threads; the top-level aliases), `execnet.trio`,
29
+
`execnet.aio`, `execnet.gevent`. `execnet.portal` is gone --
30
+
`execnet._portal` plus the trio-free `execnet._boundary`.
31
+
32
+
| was | is | why |
33
+
|---|---|---|
34
+
|`execnet.portal` public |`execnet._portal` private | it exported `Wakener`/`Mailbox`/`OneShot`/`LoopPortal` but *not*`register_wakener`, so the advertised extension point was unreachable -- and there is no plan to let third parties add event loops at all |
35
+
| Wakener registry (`register_wakener`, lazy module table) | two-branch `make_wakener(Literal["thread","gevent"])`| exactly two backends exist; every other library gets a facade |
36
+
|`wait=` spec key | gone; `Group._wait_backend`, set by the facade | it described the *caller*, which the namespace already says. Worker-side wait was always derived from the profile |
37
+
| gevent via `wait=gevent`|`execnet.gevent.Group`| symmetric with the other surfaces |
38
+
|`execmodel=` spec key |`profile=` (`execmodel=` a permanent alias) | the key selects the *worker profile*; the local execution model it was named after no longer exists |
39
+
|`Group(execmodel=)`, `set_execmodel`, `group.execmodel`| deprecated; only the remote default survives | they had no behavioural effect. **xdist passes `Group(execmodel=...)` as a keyword** -- that must keep working |
40
+
|`main_thread_only` profile | deprecated alias for `thread`|`HybridExec` already gives the first remote_exec the real main thread. Its extra behaviour (refusing a second concurrent remote_exec) was a 1s-timeout deadlock guard, now deleted |
41
+
| one `TrioHost` per `Group`| one shared `execnet.Host` per process, `Group(host=...)` to override | a host is a thread and a loop, not something groups need isolated |
42
+
| blocking inside a running loop hangs | raises, naming `execnet.aio` / `execnet.trio`| worker-side channels stay exempt: exec'd code may run its own loop |
43
+
|`aio.Group`/`Gateway`/`Channel`|`aio.AsyncGroup`/`AsyncGateway`/`AsyncChannel`| matches `execnet.trio`; swapping the import ports the code |
44
+
|`open_popen_gateway`|`open_gateway` (both async surfaces) | it always accepted any spec |
45
+
46
+
Behaviour changes worth a changelog line:
47
+
48
+
- a second concurrent `remote_exec` under `main_thread_only` used to close
49
+
the channel with `MAIN_THREAD_ONLY_DEADLOCK_TEXT`; it now runs on a pool
50
+
thread. `_executetask_complete`, `MAIN_THREAD_ONLY_ADMIT_TIMEOUT` and
51
+
the error text are deleted; `MainExec` became `PrimaryThreadPump`.
52
+
-`execnet.aio` cancellation is now real: a cancelled `receive` cancels
53
+
the host-side operation instead of consuming and discarding an item.
54
+
`send`/`send_eof`/`aclose`/`terminate` are shielded instead.
55
+
- the boundary carriers raise `execnet.TimeoutError`, not the builtin;
56
+
`OneShot` double-resolve is a `RuntimeError`, not an `assert`.
57
+
-`STATUS` answers both `profile` and (legacy) `execmodel`.
58
+
-**Latent livelock fixed**: `execnet.dumps` warned on *every* access, and
59
+
xdist calls it from `serialize_warning_message` -- i.e. from inside
60
+
pytest's warning-recording hook. One DeprecationWarning in a worker
61
+
therefore recorded a warning that recorded a warning, unbounded, and
62
+
wedged the run. The shim warns once per process
63
+
(`execnet._xdist_compat_warned`). Anything that warns in a worker can
64
+
hit this class of bug; keep it in mind.
65
+
66
+
New tests: `testing/test_host.py` (sharing, explicit `Host`, fork, the
67
+
loop guards), `testing/test_boundary.py` (renamed from `test_portal.py`),
68
+
aio cancellation contracts in `testing/test_aio.py`. The `execmodel`
69
+
fixture parametrization collapsed to a single `profile` fixture, so the
70
+
suite is ~540 items rather than ~765.
71
+
72
+
Still open from the review, deliberately not done: the async surfaces have
73
+
no `remote_status()`, no `MultiChannel`, no group iteration, and no
74
+
`RSync`. `AsyncGroup.makegateway` defaults workers to the `thread`
75
+
profile (the coordinator's shape does not dictate the worker's).
76
+
20
77
## Where the repo stands (2026-07-25, after `a69b844`)
21
78
22
79
One protocol engine: `AsyncGateway` (`_trio_gateway.py`). The sync API
|`sync.py` / `trio.py` / `portal.py`| the three public namespaces |
110
+
|`sync.py` / `trio.py` / `aio.py`/ `gevent.py`| the four public namespaces; `_host.py` holds the shared `Host`, `_portal.py`/`_boundary.py` the (private) boundary kit|
|`thread` (default) | side thread |**classic hybrid restored**: primary on the main thread, overflow on pool threads (claim decided during FIFO admission) | sync | thread | — |
|~~`main_thread_only`~~|*deprecated 2026-07-29, aliases to `thread`*|||||
91
148
|`trio` (new) |**main thread**| async sources as tasks — one single thread total; top-level await or async def; sync sources rejected; termination cancels tasks | AsyncChannel | (loop) | — |
92
149
|`gevent` (revived) | side thread | greenlets on a main-thread hub, one per remote_exec | sync | gevent (derived) |`execnet[gevent]`, auto-added by uv provisioning |
93
150
151
+
(The coordinator-side counterpart of the last row is now `execnet.gevent`,
152
+
not `wait=gevent`.)
153
+
94
154
Architecture: `TrioWorkerExec` is a pure FIFO admission pump delegating
95
155
to strategy objects (`WORKER_EXEC_STRATEGIES` in `_trio_worker.py`:
96
156
PoolExec building block, MainExec, HybridExec, GreenletExec; TaskExec
97
157
serves a plain AsyncGateway via its pluggable `_exec_handler` — no sync
98
158
bridge at all in the trio profile). Subinterpreters: future strategy
99
-
slot, not built. `EXECMODEL_PROFILES` (gateway_base) validates
100
-
coordinator-side in makegateway.
159
+
slot, not built. `WORKER_PROFILES` (`_execmodel.py`) validates
160
+
coordinator-side in makegateway, via `resolve_profile`.
101
161
102
162
**Native info/setup** (the pytest fix): `Message.GATEWAY_INFO` (code 10)
103
163
answers `_rinfo()` from the dispatch loop; chdir/nice/env ship in the
@@ -108,9 +168,9 @@ post-start remote_exec setup block are gone.
108
168
109
169
Coordinator-gevent integration: `TrioHost.call_pending` (OneShot from a
0 commit comments