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-codesubint_fork_proto
parent
43bd6a6410
commit
66dda9e449
|
|
@ -548,6 +548,32 @@ async def subint_forkserver_proc(
|
||||||
# every spawn — it's module-level in `_child` but
|
# every spawn — it's module-level in `_child` but
|
||||||
# cheap enough to re-resolve here.
|
# cheap enough to re-resolve here.
|
||||||
from tractor._child import _actor_child_main
|
from tractor._child import _actor_child_main
|
||||||
|
# XXX, fork inherits the parent's entire memory
|
||||||
|
# image — including `tractor.runtime._state` globals
|
||||||
|
# that encode "this process is the root actor":
|
||||||
|
#
|
||||||
|
# - `_runtime_vars['_is_root']` → True in parent
|
||||||
|
# - pre-populated `_root_mailbox`, `_registry_addrs`
|
||||||
|
# - the parent's `_current_actor` singleton
|
||||||
|
#
|
||||||
|
# A fresh `exec`-based child would start with the
|
||||||
|
# `_state` module's defaults (all falsey / empty).
|
||||||
|
# Replicate that here so the new child-side `Actor`
|
||||||
|
# sees a "cold" runtime — otherwise `Actor.__init__`
|
||||||
|
# takes the `is_root_process() == True` branch and
|
||||||
|
# pre-populates `self.enable_modules`, which then
|
||||||
|
# trips the `assert not self.enable_modules` gate at
|
||||||
|
# the top of `Actor._from_parent()` on the subsequent
|
||||||
|
# parent→child `SpawnSpec` handshake.
|
||||||
|
from tractor.runtime import _state
|
||||||
|
_state._current_actor = None
|
||||||
|
_state._runtime_vars.update({
|
||||||
|
'_is_root': False,
|
||||||
|
'_root_mailbox': (None, None),
|
||||||
|
'_root_addrs': [],
|
||||||
|
'_registry_addrs': [],
|
||||||
|
'_debug_mode': False,
|
||||||
|
})
|
||||||
_actor_child_main(
|
_actor_child_main(
|
||||||
uid=uid,
|
uid=uid,
|
||||||
loglevel=loglevel,
|
loglevel=loglevel,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue