From 9d1e476d0472d68e26d038926d90b1e16c17540b Mon Sep 17 00:00:00 2001 From: goodboy Date: Wed, 2 Sep 2026 16:57:43 -0400 Subject: [PATCH] Canonicalize addrs in `Registrar.register_actor()` Store the validated `waddr.unwrap()` result so legacy and tagged declarations share one registry identity. This lets stale-entry eviction replace an older actor which used the compatibility form. Cover TCP and UDS registrations crossing from legacy to tagged addrs. Review: PR #505 (goodboy) https://github.com/goodboy/tractor/pull/505#pullrequestreview-5094473850 (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`)) --- tests/discovery/test_address_serialization.py | 59 +++++++++++++++++++ tractor/discovery/_registry.py | 2 +- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/tests/discovery/test_address_serialization.py b/tests/discovery/test_address_serialization.py index ee0450c9..66688650 100644 --- a/tests/discovery/test_address_serialization.py +++ b/tests/discovery/test_address_serialization.py @@ -3,10 +3,13 @@ Canonical tagged-address decoding and legacy input compatibility. ''' from pathlib import Path +from types import SimpleNamespace import pytest +import trio from tractor.discovery._addr import wrap_address +from tractor.discovery._registry import Registrar from tractor.ipc._tcp import TCPAddress from tractor.ipc._uds import UDSAddress @@ -98,3 +101,59 @@ def test_tcp_from_native_ipv6_sockname(): ) assert addr.unwrap() == ('tcp', '::1', 1616) + + +@pytest.mark.parametrize( + 'legacy, canonical', + [ + ( + ('127.0.0.1', 1616), + ('tcp', '127.0.0.1', 1616), + ), + ( + ('/tmp/tractor', 'registry.sock'), + ('unix', '/tmp/tractor/registry.sock'), + ), + ], +) +def test_registrar_stores_canonical_addresses( + legacy: tuple, + canonical: tuple, +): + ''' + Normalize registrar entries before stale-address eviction. + + During the tagged-address migration an older actor can register + an untagged address before a newer actor reuses that endpoint with + its canonical tag. Store the first declaration canonically, then + register the tagged spelling under another uid. The old uid must + be evicted and the registry must retain exactly one canonical + address for the replacement actor. + + ''' + registrar = SimpleNamespace( + _registry={}, + _waiters={}, + ) + old_uid = ('old', 'old-uid') + new_uid = ('new', 'new-uid') + + async def register_both(): + await Registrar.register_actor( + registrar, + old_uid, + legacy, + ) + assert registrar._registry[old_uid] == [canonical] + + await Registrar.register_actor( + registrar, + new_uid, + canonical, + ) + + trio.run(register_both) + + assert registrar._registry == { + new_uid: [canonical], + } diff --git a/tractor/discovery/_registry.py b/tractor/discovery/_registry.py index 7cedb391..36248e50 100644 --- a/tractor/discovery/_registry.py +++ b/tractor/discovery/_registry.py @@ -184,7 +184,7 @@ class Registrar(Actor): # should never be 0-dynamic-os-alloc await debug.pause() - addr_tup: tuple = tuple(addr) + addr_tup: tuple = waddr.unwrap() # Evict stale entries: if a *different* uid claims # this addr (e.g. after unclean shutdown or