From f2825fae87eae63afd1dc0d85381da536e2944a4 Mon Sep 17 00:00:00 2001 From: goodboy Date: Tue, 10 Feb 2026 20:33:19 -0500 Subject: [PATCH] Fix when root-actor addrs is set as rtvs Move `_root_addrs` assignment to after `async_main()` unblocks (via `.started()`) which now delivers the bind addrs , ensuring correct `UnwrappedAddress` propagation into `._state._runtime_vars`. Previously for non-registrar root actors the `._state._runtime_vars` entries were being set as `Address` values which serialized incorrectly to children/subactors.. This fixes the issue by waiting for the `.ipc.*` stack to bind-and-resolve any randomly allocated addrs (by the OS) until after the initial `Actor` startup is complete. Deats, - primarily, mv `_root_addrs` assignment from before `root_tn.start()` to after, using started(-ed) `accept_addrs` now delivered from `async_main()`.. - update `task_status` type hints to match. - unpack ansd set the `(accept_addrs, reg_addrs)` tuple from `root_tn.start()` call into `._state._runtime_vars` entries. - improve comments distinguishing registrar vs non-registrar init paths, ensure typing reflects wrapped vs. unwrapped addrs. Also, - add a masked `mk_pdb().set_trace()` for debugging `raddrs` values being "off". - add TODO about using UDS on linux for root mailbox - rename `trans_bind_addrs` -> `tpt_bind_addrs` for clarity. - expand comment about random port allocation for non-registrar case (this commit msg was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code --- tractor/_root.py | 66 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/tractor/_root.py b/tractor/_root.py index 2fb7755d..2330449a 100644 --- a/tractor/_root.py +++ b/tractor/_root.py @@ -88,7 +88,8 @@ async def maybe_block_bp( bp_blocked: bool if ( debug_mode - and maybe_enable_greenback + and + maybe_enable_greenback and ( maybe_mod := await debug.maybe_init_greenback( raise_not_found=False, @@ -385,10 +386,13 @@ async def open_root_actor( addr, ) - trans_bind_addrs: list[UnwrappedAddress] = [] + tpt_bind_addrs: list[ + Address # `Address.get_random()` case + |UnwrappedAddress # registrar case `= uw_reg_addrs` + ] = [] - # Create a new local root-actor instance which IS NOT THE - # REGISTRAR + # ------ NON-REGISTRAR ------ + # create a new root-actor instance. if ponged_addrs: if ensure_registry: raise RuntimeError( @@ -415,12 +419,21 @@ async def open_root_actor( # XXX INSTEAD, bind random addrs using the same tpt # proto. for addr in ponged_addrs: - trans_bind_addrs.append( + tpt_bind_addrs.append( + # XXX, these are `Address` NOT `UnwrappedAddress`. + # + # NOTE, in the case of posix/berkley socket + # protos we allocate port=0 such that the system + # allocates a random value at bind time; this + # happens in the `.ipc.*` stack's backend. addr.get_random( bindspace=addr.bindspace, ) ) + # ------ REGISTRAR ------ + # create a new "registry providing" root-actor instance. + # # Start this local actor as the "registrar", aka a regular # actor who manages the local registry of "mailboxes" of # other process-tree-local sub-actors. @@ -429,7 +442,7 @@ async def open_root_actor( # following init steps are taken: # - the tranport layer server is bound to each addr # pair defined in provided registry_addrs, or the default. - trans_bind_addrs = uw_reg_addrs + tpt_bind_addrs = uw_reg_addrs # - it is normally desirable for any registrar to stay up # indefinitely until either all registered (child/sub) @@ -449,20 +462,10 @@ async def open_root_actor( enable_modules=enable_modules, ) # XXX, in case the root actor runtime was actually run from - # `tractor.to_asyncio.run_as_asyncio_guest()` and NOt + # `tractor.to_asyncio.run_as_asyncio_guest()` and NOT # `.trio.run()`. actor._infected_aio = _state._runtime_vars['_is_infected_aio'] - # NOTE, only set the loopback addr for the - # process-tree-global "root" mailbox since all sub-actors - # should be able to speak to their root actor over that - # channel. - raddrs: list[Address] = _state._runtime_vars['_root_addrs'] - raddrs.extend(trans_bind_addrs) - # TODO, remove once we have also removed all usage; - # eventually all (root-)registry apis should expect > 1 addr. - _state._runtime_vars['_root_mailbox'] = raddrs[0] - # Start up main task set via core actor-runtime nurseries. try: # assign process-local actor @@ -499,14 +502,39 @@ async def open_root_actor( # "actor runtime" primitives are SC-compat and thus all # transitively spawned actors/processes must be as # well. - await root_tn.start( + accept_addrs: list[UnwrappedAddress] + reg_addrs: list[UnwrappedAddress] + ( + accept_addrs, + reg_addrs, + ) = await root_tn.start( partial( _runtime.async_main, actor, - accept_addrs=trans_bind_addrs, + accept_addrs=tpt_bind_addrs, parent_addr=None ) ) + # NOTE, only set a local-host addr (i.e. like + # `lo`-loopback for TCP) for the process-tree-global + # "root"-process (its tree-wide "mailbox") since all + # sub-actors should be able to speak to their root + # actor over that channel. + # + # ?TODO, per-OS non-network-proto alt options? + # -[ ] on linux we should be able to always use UDS? + # + raddrs: list[Address] = _state._runtime_vars['_root_addrs'] + raddrs.extend( + accept_addrs, + ) + # TODO, remove once we have also removed all usage; + # eventually all (root-)registry apis should expect > 1 addr. + _state._runtime_vars['_root_mailbox'] = raddrs[0] + # if 'chart' in actor.aid.name: + # from tractor.devx import mk_pdb + # mk_pdb().set_trace() + try: yield actor except (