diff --git a/tests/ipc/test_tipc.py b/tests/ipc/test_tipc.py index 0a82ee3e..141aa4e7 100644 --- a/tests/ipc/test_tipc.py +++ b/tests/ipc/test_tipc.py @@ -232,6 +232,43 @@ def test_get_random_collision_resistance(): assert all(addr.is_valid for addr in addrs) +def test_get_random_keys_live_actors_by_uuid( + monkeypatch: pytest.MonkeyPatch, +): + ''' + TIPC names are cluster-wide while PIDs are host-local. Hashing + only `(actor name, pid)` therefore made same-named actors with + equal PIDs on different hosts publish one service name, where + TIPC silently round-robins connects between them. + + Hold the actor name and PID fixed while changing only its UUID; + distinct instances prove the globally unique identity field is + now part of the derivation. + + ''' + monkeypatch.setattr(_tipc.os, 'getpid', lambda: 1616) + + def get_addr(uuid: str) -> TIPCAddress: + actor = SimpleNamespace( + aid=Aid( + name='worker', + uuid=uuid, + pid=1616, + ) + ) + monkeypatch.setattr( + _tipc, + 'current_actor', + lambda **kwargs: actor, + ) + return TIPCAddress.get_random() + + first: TIPCAddress = get_addr('actor-uuid-a') + second: TIPCAddress = get_addr('actor-uuid-b') + assert first._instance != second._instance + assert get_addr('actor-uuid-a')._instance == first._instance + + def test_get_random_honors_bindspace(): addr: TIPCAddress = TIPCAddress.get_random( bindspace=TIPC_NODE_SCOPE, diff --git a/tractor/ipc/_tipc.py b/tractor/ipc/_tipc.py index 89d5eca6..da22f499 100644 --- a/tractor/ipc/_tipc.py +++ b/tractor/ipc/_tipc.py @@ -369,10 +369,11 @@ class TIPCAddress( between them (verified). I.e. a collision manifests as *silent crosstalk*, not an error. - So the instance is a `blake2b` digest of a per-call-unique - seed, giving a well-spread 32b value. Being a pure fn of the - seed it is also *reproducible*, which the (follow-up) - registrar-less discovery fast-path wants. + So the instance is a `blake2b` digest of the actor UUID, or + a per-call token outside a live runtime, giving a well-spread + 32b value. Being a pure fn of the seed it is also + *reproducible*, which the (follow-up) registrar-less + discovery fast-path wants. NOTE the residual risk is birthday-bounded: ~1.2e-2 for 10k names sharing one `_stype`. See plan 01 ยง9 for the @@ -384,7 +385,7 @@ class TIPCAddress( err_on_no_runtime=False, ) if actor: - seed: str = f'{actor.aid.name}@{pid}' + seed: str = '.'.join(actor.aid.uid) else: if is_root_process(): prefix: str = 'no_runtime_root'