Register `tipc` in the tpt tables + test harness
Wire the backend through every registration site (contract §2)
so `--tpt-proto tipc` is a first-class suite mode,
- `_state.TransportProtocolKey` gains the key
- `_addr._address_types` + `._default_lo_addrs`
- `_addr.wrap_address()` gets a `case ('tipc', *_)`; being a
4-elem seq it can't collide w/ `tcp`s or `uds`s 2-tuple
cases, so NO ordering hazard (and a bare seq-pattern matches
the `list` form `msgpack` decodes to).
- `_types`: the `Address` union, `_msg_transports`,
`_key_to_transport`, `_addr_to_transport` and the
`transport_from_stream()` family match. That last one keys
off `._tipc.AF_TIPC` (which carries the uapi fallback) NOT
`socket.AF_TIPC` which is linux-only.
Test-harness side,
- `get_rando_addr()` gains a `tipc` branch; `.get_random()`
already salts w/ `uuid4`+pid so both within- and cross-proc
isolation come for free.
- the `tpt_protos` fixture calls an addr-type's optional
`.is_available()` and `pytest.fail()`s w/ its reason. Keeps
a module-less box from turning `--tpt-proto tipc` into a few
hundred confusing connect-timeouts. Generic on purpose —
plans 02/03 need the same hook.
- the discovery `daemon` fixture's readiness probe learns to
dial a TIPC service name (it previously assumed tcp-or-uds
and blew up on the 4-tuple).
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
wkt/tipc_backend_wg_stack
parent
bf4147b71c
commit
c4bc9df70b
|
|
@ -99,6 +99,20 @@ def get_rando_addr(
|
||||||
assert addr.sockpath.resolve()
|
assert addr.sockpath.resolve()
|
||||||
testrun_reg_addr = addr.unwrap()
|
testrun_reg_addr = addr.unwrap()
|
||||||
|
|
||||||
|
# NOTE, `.get_random()` already derives the service
|
||||||
|
# *instance* from a `uuid4`+pid-salted seed, so both the
|
||||||
|
# within- and cross-proc isolation the other 2 protos
|
||||||
|
# hand-roll above comes for free.
|
||||||
|
#
|
||||||
|
# XXX matters MORE here than for tcp/uds: a TIPC name
|
||||||
|
# clash doesn't raise `EADDRINUSE`, it silently
|
||||||
|
# round-robins connects between both publishers.
|
||||||
|
case 'tipc':
|
||||||
|
from tractor.ipc._tipc import TIPCAddress
|
||||||
|
addr: TIPCAddress = addr_type.get_random()
|
||||||
|
assert addr.is_valid
|
||||||
|
testrun_reg_addr = addr.unwrap()
|
||||||
|
|
||||||
# XXX, as sanity it should never the same as the default for the
|
# XXX, as sanity it should never the same as the default for the
|
||||||
# host-singleton registry actor.
|
# host-singleton registry actor.
|
||||||
assert def_reg_addr != testrun_reg_addr
|
assert def_reg_addr != testrun_reg_addr
|
||||||
|
|
|
||||||
|
|
@ -498,14 +498,14 @@ def pytest_configure(
|
||||||
)
|
)
|
||||||
config.addinivalue_line(
|
config.addinivalue_line(
|
||||||
'markers',
|
'markers',
|
||||||
'tipc: test targets the `AF_TIPC` tpt backend; the kernel- '
|
'trio: legacy mark for tests meant to run under the `trio` '
|
||||||
'touching cases self-skip unless the `tipc` module is loaded '
|
'spawn backend (e.g. `test_local.py`).'
|
||||||
'(`sudo modprobe tipc`).'
|
|
||||||
)
|
)
|
||||||
config.addinivalue_line(
|
config.addinivalue_line(
|
||||||
'markers',
|
'markers',
|
||||||
'trio: legacy mark for tests meant to run under the `trio` '
|
'tipc: test targets the `AF_TIPC` tpt backend; the kernel- '
|
||||||
'spawn backend (e.g. `test_local.py`).'
|
'touching cases self-skip unless the `tipc` module is loaded '
|
||||||
|
'(`sudo modprobe tipc`).'
|
||||||
)
|
)
|
||||||
|
|
||||||
# `--enable-stackscope`: install SIGUSR1 → trio task-tree
|
# `--enable-stackscope`: install SIGUSR1 → trio task-tree
|
||||||
|
|
@ -803,6 +803,27 @@ def tpt_protos(
|
||||||
addr_type = _addr._address_types[proto_key]
|
addr_type = _addr._address_types[proto_key]
|
||||||
assert addr_type.proto_key == proto_key
|
assert addr_type.proto_key == proto_key
|
||||||
|
|
||||||
|
# XXX, generic capability gate: an env-dependent tpt
|
||||||
|
# whose backing kernel-mod/lib/netns isn't present here
|
||||||
|
# must fail LOUDLY and EARLY rather than as a few hundred
|
||||||
|
# confusing connect-timeouts downstream.
|
||||||
|
#
|
||||||
|
# Any `Address` type MAY expose `.is_available()`
|
||||||
|
# returning `(ok, why_not)`; absence means "always
|
||||||
|
# available" (i.e. tcp/uds).
|
||||||
|
is_avail = getattr(
|
||||||
|
addr_type,
|
||||||
|
'is_available',
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if is_avail:
|
||||||
|
avail, why_not = is_avail()
|
||||||
|
if not avail:
|
||||||
|
pytest.fail(
|
||||||
|
f'--tpt-proto={proto_key!r} is NOT usable here!\n'
|
||||||
|
f'{why_not}\n'
|
||||||
|
)
|
||||||
|
|
||||||
yield proto_keys
|
yield proto_keys
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ from ..ipc._uds import (
|
||||||
UDSAddress,
|
UDSAddress,
|
||||||
HAS_UDS,
|
HAS_UDS,
|
||||||
)
|
)
|
||||||
|
from ..ipc._tipc import TIPCAddress
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# ONLY type-annots, the eager import costs ~4.5ms
|
# ONLY type-annots, the eager import costs ~4.5ms
|
||||||
|
|
@ -88,9 +89,16 @@ TaggedUDSAlias: TypeAlias = tuple[
|
||||||
Literal['uds'],
|
Literal['uds'],
|
||||||
str,
|
str,
|
||||||
]
|
]
|
||||||
|
TaggedTIPCAddress: TypeAlias = tuple[
|
||||||
|
Literal['tipc'],
|
||||||
|
int,
|
||||||
|
int,
|
||||||
|
int,
|
||||||
|
]
|
||||||
TaggedAddress: TypeAlias = (
|
TaggedAddress: TypeAlias = (
|
||||||
TaggedTCPAddress
|
TaggedTCPAddress
|
||||||
|TaggedUnixAddress
|
|TaggedUnixAddress
|
||||||
|
|TaggedTIPCAddress
|
||||||
)
|
)
|
||||||
|
|
||||||
# Input-only compatibility forms retained for older callers and
|
# Input-only compatibility forms retained for older callers and
|
||||||
|
|
@ -211,12 +219,14 @@ class Address(Protocol):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
# the address types available on this host: TCP always, UDS only
|
# the address types supported by this build: TCP and TIPC are
|
||||||
# where usable (`HAS_UDS`). Both registries derive from this single
|
# importable everywhere, while UDS is registered only where usable
|
||||||
# list via each type's `proto_key`.
|
# (`HAS_UDS`). Runtime TIPC availability is checked separately.
|
||||||
|
# Both registries derive from this list via each type's `proto_key`.
|
||||||
_address_protos: list[Type[Address]] = [TCPAddress]
|
_address_protos: list[Type[Address]] = [TCPAddress]
|
||||||
if HAS_UDS:
|
if HAS_UDS:
|
||||||
_address_protos.append(UDSAddress)
|
_address_protos.append(UDSAddress)
|
||||||
|
_address_protos.append(TIPCAddress)
|
||||||
|
|
||||||
_address_types: dict[str, Type[Address]] = {
|
_address_types: dict[str, Type[Address]] = {
|
||||||
cls.proto_key: cls
|
cls.proto_key: cls
|
||||||
|
|
@ -313,6 +323,16 @@ def wrap_address(
|
||||||
):
|
):
|
||||||
return UDSAddress.from_addr(addr)
|
return UDSAddress.from_addr(addr)
|
||||||
|
|
||||||
|
# XXX, the explicitly proto-keyed form (spelled with the
|
||||||
|
# `multiaddr` proto name) which is where ALL backends
|
||||||
|
# should eventually land per the `UnwrappedAddress`
|
||||||
|
# migration note above.
|
||||||
|
#
|
||||||
|
# NOTE, a bare seq-pattern matches `list` too, which is
|
||||||
|
# what `msgpack` decodes our tuples back to.
|
||||||
|
case ('tipc', *_):
|
||||||
|
cls = TIPCAddress
|
||||||
|
|
||||||
# classic network socket-address as tuple/list
|
# classic network socket-address as tuple/list
|
||||||
case (
|
case (
|
||||||
(str(), int())
|
(str(), int())
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ from tractor.runtime._state import (
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from tractor.discovery._addr import TaggedTIPCAddress
|
||||||
from tractor.runtime._runtime import Actor
|
from tractor.runtime._runtime import Actor
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -209,7 +210,7 @@ class TIPCAddress(
|
||||||
maybe_ref: int|None = None
|
maybe_ref: int|None = None
|
||||||
|
|
||||||
proto_key: ClassVar[str] = 'tipc'
|
proto_key: ClassVar[str] = 'tipc'
|
||||||
unwrapped_type: ClassVar[type] = tuple[str, int, int, int]
|
unwrapped_type: ClassVar[type] = tuple
|
||||||
def_bindspace: ClassVar[int] = TIPC_CLUSTER_SCOPE
|
def_bindspace: ClassVar[int] = TIPC_CLUSTER_SCOPE
|
||||||
|
|
||||||
# XXX, TIPC's `getsockname()` answers a `TIPC_ADDR_ID` port-id
|
# XXX, TIPC's `getsockname()` answers a `TIPC_ADDR_ID` port-id
|
||||||
|
|
@ -256,7 +257,7 @@ class TIPCAddress(
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_addr(
|
def from_addr(
|
||||||
cls,
|
cls,
|
||||||
addr: tuple[str, int, int, int],
|
addr: tuple|list,
|
||||||
) -> TIPCAddress:
|
) -> TIPCAddress:
|
||||||
match addr:
|
match addr:
|
||||||
# our proto-keyed unwrapped form, w/ scope optional
|
# our proto-keyed unwrapped form, w/ scope optional
|
||||||
|
|
@ -304,7 +305,7 @@ class TIPCAddress(
|
||||||
f'{addr!r}\n'
|
f'{addr!r}\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
def unwrap(self) -> tuple[str, int, int, int]:
|
def unwrap(self) -> TaggedTIPCAddress:
|
||||||
# NOTE, proto-keyed (w/ the `multiaddr` proto spelling) so
|
# NOTE, proto-keyed (w/ the `multiaddr` proto spelling) so
|
||||||
# `wrap_address()` can dispatch unambiguously against the
|
# `wrap_address()` can dispatch unambiguously against the
|
||||||
# other backends' 2-tuple forms; see contract §1.1.
|
# other backends' 2-tuple forms; see contract §1.1.
|
||||||
|
|
|
||||||
|
|
@ -35,20 +35,25 @@ from tractor.ipc._uds import (
|
||||||
MsgpackUDSStream,
|
MsgpackUDSStream,
|
||||||
HAS_UDS,
|
HAS_UDS,
|
||||||
)
|
)
|
||||||
|
from tractor.ipc._tipc import (
|
||||||
|
AF_TIPC,
|
||||||
|
TIPCAddress,
|
||||||
|
MsgpackTIPCStream,
|
||||||
|
)
|
||||||
|
|
||||||
# the UDS backend is importable everywhere but only *usable* when
|
Address = TCPAddress|UDSAddress|TIPCAddress
|
||||||
# `HAS_UDS` is `True`; otherwise the runtime registers TCP only.
|
|
||||||
Address = TCPAddress|UDSAddress
|
|
||||||
|
|
||||||
# the available msg-transport backends on this host: TCP always,
|
# the msg-transport backends supported by this build: TCP and TIPC
|
||||||
# UDS only where usable (`HAS_UDS`). The lookup maps below derive
|
# are importable everywhere, while UDS is registered only where
|
||||||
# from this single list via each backend's `codec_key` and
|
# usable (`HAS_UDS`). Runtime TIPC availability is checked separately.
|
||||||
# `address_type`: register a backend here and every map picks it up.
|
# The lookup maps derive from this list via each backend's `codec_key`
|
||||||
|
# and `address_type`.
|
||||||
_msg_transports: list[Type[MsgTransport]] = [
|
_msg_transports: list[Type[MsgTransport]] = [
|
||||||
MsgpackTCPStream,
|
MsgpackTCPStream,
|
||||||
]
|
]
|
||||||
if HAS_UDS:
|
if HAS_UDS:
|
||||||
_msg_transports.append(MsgpackUDSStream)
|
_msg_transports.append(MsgpackUDSStream)
|
||||||
|
_msg_transports.append(MsgpackTIPCStream)
|
||||||
|
|
||||||
# map a `MsgTransportKey` -> `MsgTransport` type
|
# map a `MsgTransportKey` -> `MsgTransport` type
|
||||||
_key_to_transport: dict[MsgTransportKey, Type[MsgTransport]] = {
|
_key_to_transport: dict[MsgTransportKey, Type[MsgTransport]] = {
|
||||||
|
|
@ -109,6 +114,12 @@ def transport_from_stream(
|
||||||
):
|
):
|
||||||
transport = 'uds'
|
transport = 'uds'
|
||||||
|
|
||||||
|
# NOTE, `AF_TIPC` is linux-only in CPython so we
|
||||||
|
# match the `._tipc` constant (which carries a uapi
|
||||||
|
# fallback) rather than `socket.AF_TIPC`.
|
||||||
|
case fam if fam == AF_TIPC:
|
||||||
|
transport = 'tipc'
|
||||||
|
|
||||||
case fam:
|
case fam:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
f'Unsupported socket family: {fam}'
|
f'Unsupported socket family: {fam}'
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ _DARWIN_TMPDIR: Path = Path('/tmp')
|
||||||
TransportProtocolKey = Literal[
|
TransportProtocolKey = Literal[
|
||||||
'tcp',
|
'tcp',
|
||||||
'uds',
|
'uds',
|
||||||
|
'tipc',
|
||||||
]
|
]
|
||||||
_def_tpt_proto: TransportProtocolKey = 'tcp'
|
_def_tpt_proto: TransportProtocolKey = 'tcp'
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue