diff --git a/tractor/_testing/addr.py b/tractor/_testing/addr.py index db95889d..58acfd1a 100644 --- a/tractor/_testing/addr.py +++ b/tractor/_testing/addr.py @@ -50,9 +50,14 @@ def get_rando_addr( fight over the bind, cascading into a chain of "Address already in use" failures. - For UDS this concern doesn't apply: `UDSAddress.get_random()` - already builds socket paths from `os.getpid()` so each - pytest process gets its own socket-path namespace. + For UDS the *cross*-process concern is handled by keying + socket paths off `os.getpid()` (each pytest process gets its + own namespace). *Within* a process — where `get_random()` has + no live runtime to name from — it also appends a per-call + `uuid4` token, so two calls (e.g. a `reg_addr` + a distinct + bind addr in one test body) yield distinct sockpaths rather + than aliasing to the same `name@pid.sock` and tripping + `EADDRINUSE`. ''' addr_type: Type[_addr.Addres] = _addr._address_types[tpt_proto] diff --git a/tractor/ipc/_uds.py b/tractor/ipc/_uds.py index 8c57664d..4c07a200 100644 --- a/tractor/ipc/_uds.py +++ b/tractor/ipc/_uds.py @@ -36,6 +36,7 @@ from typing import ( TYPE_CHECKING, ClassVar, ) +from uuid import uuid4 import msgspec import trio @@ -204,7 +205,16 @@ class UDSAddress( else: prefix: str = 'no_runtime_actor' - sockname: str = f'{prefix}@{pid}' + # XXX, no live actor -> no `Aid` to key off, so append a + # per-CALL token: w/o a runtime the sockname is otherwise + # a pure fn of `(prefix, pid)`, so two `get_random()` + # calls in one proc alias to the SAME sockpath and the + # 2nd `.bind()` trips `EADDRINUSE`. The token makes each + # call a distinct file. Safe vs `._reap` cleanup which + # only reconstructs the runtime `{name}@{pid}` convention + # (above), never these `no_runtime_*` socks. + token: str = uuid4().hex[:6] + sockname: str = f'{prefix}@{pid}.{token}' sockpath: Path = Path(f'{sockname}.sock') return UDSAddress(