Skip to content

Commit 63ab7c9

Browse files
committed
Reset post-fork _state in forkserver child
`os.fork()` inherits the parent's entire memory image, including `tractor.runtime._state` globals that encode "this process is the root actor" — `_runtime_vars`'s `_is_root=True`, pre-populated `_root_mailbox` + `_registry_addrs`, and the parent's `_current_actor` singleton. A fresh `exec`-based child starts with those globals at their module-level defaults (all falsey/empty). The forkserver child needs to match that shape BEFORE calling `_actor_child_main()`, otherwise `Actor.__init__()` takes the `is_root_process() == True` branch and pre-populates `self.enable_modules`, which then trips `assert not self.enable_modules` at the top of `Actor._from_parent()` on the subsequent parent→child `SpawnSpec` handshake. Fix: at the start of `_child_target`, null `_state._current_actor` and overwrite `_runtime_vars` with a cold-root blank (`_is_root=False`, empty mailbox/addrs, `_debug_mode=False`) before `_actor_child_main()` runs. Found-via: `test_subint_forkserver_spawn_basic` hitting the `enable_modules` assert on child-side runtime boot. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
1 parent 26914fd commit 63ab7c9

1 file changed

Lines changed: 26 additions & 0 deletions

File tree

tractor/spawn/_subint_forkserver.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,32 @@ def _child_target() -> int:
548548
# every spawn — it's module-level in `_child` but
549549
# cheap enough to re-resolve here.
550550
from tractor._child import _actor_child_main
551+
# XXX, fork inherits the parent's entire memory
552+
# image — including `tractor.runtime._state` globals
553+
# that encode "this process is the root actor":
554+
#
555+
# - `_runtime_vars['_is_root']` → True in parent
556+
# - pre-populated `_root_mailbox`, `_registry_addrs`
557+
# - the parent's `_current_actor` singleton
558+
#
559+
# A fresh `exec`-based child would start with the
560+
# `_state` module's defaults (all falsey / empty).
561+
# Replicate that here so the new child-side `Actor`
562+
# sees a "cold" runtime — otherwise `Actor.__init__`
563+
# takes the `is_root_process() == True` branch and
564+
# pre-populates `self.enable_modules`, which then
565+
# trips the `assert not self.enable_modules` gate at
566+
# the top of `Actor._from_parent()` on the subsequent
567+
# parent→child `SpawnSpec` handshake.
568+
from tractor.runtime import _state
569+
_state._current_actor = None
570+
_state._runtime_vars.update({
571+
'_is_root': False,
572+
'_root_mailbox': (None, None),
573+
'_root_addrs': [],
574+
'_registry_addrs': [],
575+
'_debug_mode': False,
576+
})
551577
_actor_child_main(
552578
uid=uid,
553579
loglevel=loglevel,

0 commit comments

Comments
 (0)