--- model: claude-opus-4-7[1m] service: claude timestamp: 2026-04-22T20:07:23Z git_ref: 797f57c diff_cmd: git log 26fb820..HEAD # all session commits since the destroy-race fix log --- Session-spanning conversation covering the Phase B hardening of the `subint` spawn-backend and an investigation into a proposed `subint_fork` follow-up which turned out to be blocked at the CPython level. This log is a narrative capture of the substantive turns (not every message) and references the concrete code + docs the session produced. Per diff-ref mode the actual code diffs are pointed at via `git log` on each ref rather than duplicated inline. ## Narrative of the substantive turns ### Py3.13 hang / gate tightening Diagnosed a reproducible hang of the `subint` backend under py3.13 (test_spawning tests wedge after root-actor bringup). Root cause: py3.13's vintage of the private `_interpreters` C module has a latent thread/subint-interaction issue that `_interpreters.exec()` silently fails to progress under tractor's multi-trio usage pattern — even though a minimal standalone `threading.Thread` + `_interpreters.exec()` reproducer works fine on the same Python. Empirically py3.14 fixes it. Fix (from this session): tighten the `_has_subints` gate in `tractor.spawn._subint` from "private module importable" to "public `concurrent.interpreters` present" — which is 3.14+ only. This leaves `subint_proc()` unchanged in behavior (we still call the *private* `_interpreters.create('legacy')` etc. under the hood) but refuses to engage on 3.13. Also tightened the matching gate in `tractor.spawn._spawn.try_set_start_method('subint')` and rev'd the corresponding error messages from "3.13+" to "3.14+" with a sentence explaining why. Test-module `pytest.importorskip` switched from `_interpreters` → `concurrent.interpreters` to match. ### `pytest-timeout` dep + `skipon_spawn_backend` marker plumbing Added `pytest-timeout>=2.3` to the `testing` dep group with an inline comment pointing at the `ai/conc-anal/*.md` docs. Applied `@pytest.mark.timeout(30, method='thread')` (the `method='thread'` is load-bearing — `signal`-method `SIGALRM` suffers the same GIL-starvation path that drops `SIGINT` in the class-A hang pattern) to the three known- hanging subint tests cataloged in `subint_sigint_starvation_issue.md`. Separately code-reviewed the user's newly-staged `skipon_spawn_backend` pytest marker implementation in `tractor/_testing/pytest.py`. Found four bugs: 1. `modmark.kwargs.get(reason)` called `.get()` with the *variable* `reason` as the dict key instead of the string `'reason'` — user-supplied `reason=` was never picked up. (User had already fixed this locally via `.get('reason', reason)` by the time my review happened — preserved that fix.) 2. The module-level `pytestmark` branch suppressed per-test marker handling (the `else:` was an `else:` rather than independent iteration). 3. `mod_pytestmark.mark` assumed a single `MarkDecorator` — broke on the valid-pytest `pytestmark = [mark, mark]` list form. 4. Typo: `pytest.Makr` → `pytest.Mark`. Refactored the hook to use `item.iter_markers(name=...)` which walks function + class + module scopes uniformly and handles both `pytestmark` forms natively. ~30 LOC replaced the original ~30 LOC of nested conditionals, all four bugs dissolved. Also updated the marker help string to reflect the variadic `*start_methods` + `reason=` surface. ### `subint_fork_proc` prototype attempt User's hypothesis: the known trio+`fork()` issues (python-trio/trio#1614) could be sidestepped by using a sub-interpreter purely as a launchpad — `os.fork()` from a subint that has never imported trio → child is in a trio-free context. In the child `execv()` back into `python -m tractor._child` and the downstream handshake matches `trio_proc()` identically. Drafted the prototype at `tractor/spawn/_subint.py`'s bottom (originally — later moved to its own submod, see below): launchpad-subint creation, bootstrap code-string with `os.fork()` + `execv()`, driver-thread orchestration, parent-side `ipc_server.wait_for_peer()` dance. Registered `'subint_fork'` as a new `SpawnMethodKey` literal, added `case 'subint' | 'subint_fork':` feature-gate arm in `try_set_start_method()`, added entry in `_methods` dict. ### CPython-level block discovered User tested on py3.14 and saw: ``` Fatal Python error: _PyInterpreterState_DeleteExceptMain: not main interpreter Python runtime state: initialized Current thread 0x00007f6b71a456c0 [subint-fork-lau] (most recent call first): File "