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`))
wkt/addr_unpacking
Gud Boi 2026-09-02 16:57:43 -04:00
parent ca436aa9ec
commit 9d1e476d04
2 changed files with 60 additions and 1 deletions

View File

@ -3,10 +3,13 @@ Canonical tagged-address decoding and legacy input compatibility.
''' '''
from pathlib import Path from pathlib import Path
from types import SimpleNamespace
import pytest import pytest
import trio
from tractor.discovery._addr import wrap_address from tractor.discovery._addr import wrap_address
from tractor.discovery._registry import Registrar
from tractor.ipc._tcp import TCPAddress from tractor.ipc._tcp import TCPAddress
from tractor.ipc._uds import UDSAddress from tractor.ipc._uds import UDSAddress
@ -98,3 +101,59 @@ def test_tcp_from_native_ipv6_sockname():
) )
assert addr.unwrap() == ('tcp', '::1', 1616) 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],
}

View File

@ -184,7 +184,7 @@ class Registrar(Actor):
# should never be 0-dynamic-os-alloc # should never be 0-dynamic-os-alloc
await debug.pause() await debug.pause()
addr_tup: tuple = tuple(addr) addr_tup: tuple = waddr.unwrap()
# Evict stale entries: if a *different* uid claims # Evict stale entries: if a *different* uid claims
# this addr (e.g. after unclean shutdown or # this addr (e.g. after unclean shutdown or