Normalize `Registrar.delete_addr()` declarations

Match deletion inputs to the canonical addresses stored by
`register_actor()`. Legacy TCP/UDS pairs and the `uds` alias
otherwise miss their entry and leave it registered.

Cover legacy and tagged declarations as tuples and msgpack-style
lists, checking the returned uid and removal of the final entry.

Review: PR #505 (goodboy)
https://github.com/goodboy/tractor/pull/505#pullrequestreview-5408224830

(this patch was generated in some part by `opencode` using `gpt-6-astra` (`openai`))
wkt/addr_unpacking
Gud Boi 2026-10-04 20:37:47 -04:00
parent f70cfd62a6
commit 2b4b65e717
2 changed files with 64 additions and 3 deletions

View File

@ -157,3 +157,64 @@ def test_registrar_stores_canonical_addresses(
assert registrar._registry == { assert registrar._registry == {
new_uid: [canonical], new_uid: [canonical],
} }
@pytest.mark.parametrize('container', [tuple, list])
@pytest.mark.parametrize(
'declaration',
[
pytest.param(('127.0.0.1', 1616), id='legacy-tcp'),
pytest.param(
('tcp', '127.0.0.1', 1616),
id='tagged-tcp',
),
pytest.param(
('/tmp/tractor', 'registry.sock'),
id='legacy-uds',
),
pytest.param(
('unix', '/tmp/tractor/registry.sock'),
id='tagged-unix',
),
pytest.param(
('uds', '/tmp/tractor/registry.sock'),
id='uds-alias',
),
],
)
def test_registrar_deletes_equivalent_address_forms(
declaration: tuple,
container: type,
):
'''
Delete a canonical registration using any accepted spelling.
``Registrar.register_actor()`` stores canonical tagged addresses,
but ``Registrar.delete_addr()`` previously compared the supplied
tuple unchanged. A legacy declaration or ``uds`` alias therefore
left a stale entry in ``Registrar._registry``. Register each
declaration, verify its stored canonical form, then delete with
the original spelling as a tuple or msgpack-style list. Returning
the registered uid and leaving no entry proves normalization at
the deletion boundary without requiring a mixed-version tree.
'''
registrar = SimpleNamespace(
_registry={},
_waiters={},
)
uid = ('service', 'service-uid')
canonical = wrap_address(declaration).unwrap()
async def register_then_delete():
await Registrar.register_actor(registrar, uid, declaration)
assert registrar._registry == {uid: [canonical]}
deleted_uid = await Registrar.delete_addr(
registrar,
container(declaration),
)
assert deleted_uid == uid
assert registrar._registry == {}
trio.run(register_then_delete)

View File

@ -234,9 +234,9 @@ class Registrar(Actor):
addr: tuple[str, int|str]|list[str|int], addr: tuple[str, int|str]|list[str|int],
) -> tuple[str, str]|None: ) -> tuple[str, str]|None:
# NOTE: `addr` arrives as a `list` over IPC # NOTE: `addr` arrives as a `list` over IPC
# (msgpack deserializes tuples -> lists) so # (msgpack deserializes tuples -> lists). Normalize legacy
# coerce to `tuple` for the linear scan. # and tagged forms to match `Registrar._registry` entries.
addr = tuple(addr) addr = wrap_address(addr).unwrap()
uid: tuple[str, str]|None = None uid: tuple[str, str]|None = None
for _uid, addrs in self._registry.items(): for _uid, addrs in self._registry.items():