Make UDS + `SIGUSR1` optional for Windows
Windows (and any CPython that doesn't expose `socket.AF_UNIX`) can't import the UDS transport backend nor `signal.SIGUSR1`, so the unconditional imports break `import tractor` outright on those hosts. Guard the platform-specific bits behind capability probes and fall back to a TCP-only runtime when the UDS backend is unavailable. - across `discovery/_addr.py`, `ipc/_server.py` and `ipc/_types.py`, gate on `getattr(socket, 'AF_UNIX', None)` + `platform.system()` and import `UDSAddress` / `MsgpackUDSStream` only when supported, leaving the names as `None` otherwise. - register the `'uds'` key in `_address_types`, its default-loopback addr, and the transport lookup maps only when the backend actually loads, so TCP keeps working standalone. - in `devx/_stackscope.py`, import `SIGUSR1` conditionally and set it to `None` on Windows. Rebased onto the post-reorg tree where `_addr.py` now lives under `tractor/discovery/`; adapt the relocated imports to the package's `..ipc._uds` / `..ipc._tcp` paths (the original single-dot paths would silently disable UDS on POSIX) and drop a duplicated `TYPE_CHECKING` block and dead `import logging` left by the move. (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code
parent
f8b7233b3e
commit
2743b81b71
|
|
@ -31,12 +31,22 @@ from threading import (
|
|||
RLock,
|
||||
)
|
||||
import multiprocessing as mp
|
||||
|
||||
import platform
|
||||
import signal
|
||||
|
||||
from signal import (
|
||||
signal,
|
||||
getsignal,
|
||||
SIGUSR1,
|
||||
SIGINT,
|
||||
)
|
||||
|
||||
|
||||
if platform.system() != "Windows":
|
||||
from signal import SIGUSR1
|
||||
else:
|
||||
SIGUSR1 = None
|
||||
|
||||
# import traceback
|
||||
from types import ModuleType
|
||||
from typing import (
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ from typing import (
|
|||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
import platform
|
||||
import socket
|
||||
|
||||
|
||||
from bidict import bidict
|
||||
from trio import (
|
||||
SocketListener,
|
||||
|
|
@ -32,7 +36,6 @@ from ..runtime._state import (
|
|||
_def_tpt_proto,
|
||||
)
|
||||
from ..ipc._tcp import TCPAddress
|
||||
from ..ipc._uds import UDSAddress
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..runtime._runtime import Actor
|
||||
|
|
@ -40,6 +43,20 @@ if TYPE_CHECKING:
|
|||
log = get_logger()
|
||||
|
||||
|
||||
HAS_AF_UNIX = getattr(socket, "AF_UNIX", None) is not None
|
||||
IS_WINDOWS = platform.system() == "Windows"
|
||||
|
||||
UDSAddress = None # so references exist but do nothing on Windows
|
||||
|
||||
if HAS_AF_UNIX and not IS_WINDOWS:
|
||||
try:
|
||||
from ..ipc._uds import UDSAddress as _UDSAddress
|
||||
UDSAddress = _UDSAddress
|
||||
except Exception as e:
|
||||
log.warning("UDS backend import failed: %s", e)
|
||||
else:
|
||||
log.warning("UDS backend disabled on this platform.")
|
||||
|
||||
# TODO, maybe breakout the netns key to a struct?
|
||||
# class NetNs(Struct)[str, int]:
|
||||
# ...
|
||||
|
|
@ -172,20 +189,25 @@ class Address(Protocol):
|
|||
|
||||
_address_types: bidict[str, Type[Address]] = {
|
||||
'tcp': TCPAddress,
|
||||
'uds': UDSAddress
|
||||
}
|
||||
|
||||
if UDSAddress is not None:
|
||||
_address_types['uds'] = UDSAddress
|
||||
else:
|
||||
log.warning("Skipping UDS address type: no UDS backend available.")
|
||||
|
||||
|
||||
# TODO! really these are discovery sys default addrs ONLY useful for
|
||||
# when none is provided to a root actor on first boot.
|
||||
_default_lo_addrs: dict[
|
||||
str,
|
||||
UnwrappedAddress
|
||||
] = {
|
||||
_default_lo_addrs: dict[str, UnwrappedAddress] = {
|
||||
'tcp': TCPAddress.get_root().unwrap(),
|
||||
'uds': UDSAddress.get_root().unwrap(),
|
||||
}
|
||||
|
||||
if UDSAddress is not None:
|
||||
_default_lo_addrs['uds'] = UDSAddress.get_root().unwrap()
|
||||
else:
|
||||
log.warning("Skipping UDS default loopback address: no UDS backend available.")
|
||||
|
||||
|
||||
def get_address_cls(name: str) -> Type[Address]:
|
||||
return _address_types[name]
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ from functools import partial
|
|||
from itertools import chain
|
||||
import inspect
|
||||
import textwrap
|
||||
import platform
|
||||
import socket
|
||||
from types import (
|
||||
ModuleType,
|
||||
)
|
||||
|
|
@ -62,16 +64,23 @@ from .. import log
|
|||
from ..discovery._addr import Address
|
||||
from ._chan import Channel
|
||||
from ._transport import MsgTransport
|
||||
from ._uds import UDSAddress
|
||||
from ._tcp import TCPAddress
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..runtime._runtime import Actor
|
||||
from ..runtime._supervise import ActorNursery
|
||||
|
||||
|
||||
from ._tcp import TCPAddress
|
||||
|
||||
log = log.get_logger()
|
||||
|
||||
UDSAddress = None
|
||||
|
||||
if getattr(socket, "AF_UNIX", None) is not None and platform.system() != "Windows":
|
||||
from ._uds import UDSAddress
|
||||
else:
|
||||
pass
|
||||
|
||||
async def maybe_wait_on_canced_subs(
|
||||
uid: tuple[str, str],
|
||||
|
|
|
|||
|
|
@ -18,106 +18,132 @@
|
|||
IPC subsys type-lookup helpers?
|
||||
|
||||
'''
|
||||
from typing import (
|
||||
Type,
|
||||
# TYPE_CHECKING,
|
||||
)
|
||||
|
||||
import trio
|
||||
from typing import Type
|
||||
import platform
|
||||
import socket
|
||||
import trio
|
||||
|
||||
from tractor.log import get_logger
|
||||
from tractor.ipc._transport import (
|
||||
MsgTransportKey,
|
||||
MsgTransport
|
||||
MsgTransport,
|
||||
)
|
||||
from tractor.ipc._tcp import (
|
||||
TCPAddress,
|
||||
MsgpackTCPStream,
|
||||
)
|
||||
from tractor.ipc._uds import (
|
||||
UDSAddress,
|
||||
MsgpackUDSStream,
|
||||
|
||||
log = get_logger()
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Optional UDS backend (Windows / some Pythons may not have AF_UNIX)
|
||||
# ------------------------------------------------------------
|
||||
HAS_AF_UNIX = getattr(socket, "AF_UNIX", None) is not None
|
||||
IS_WINDOWS = platform.system() == "Windows"
|
||||
|
||||
HAS_UDS = False
|
||||
UDSAddress = None # type: ignore
|
||||
MsgpackUDSStream = None # type: ignore
|
||||
|
||||
if HAS_AF_UNIX and not IS_WINDOWS:
|
||||
try:
|
||||
from tractor.ipc._uds import ( # type: ignore
|
||||
UDSAddress as _UDSAddress,
|
||||
MsgpackUDSStream as _MsgpackUDSStream,
|
||||
)
|
||||
UDSAddress = _UDSAddress # type: ignore
|
||||
MsgpackUDSStream = _MsgpackUDSStream # type: ignore
|
||||
HAS_UDS = True
|
||||
except Exception as e:
|
||||
log.warning("UDS backend unavailable (%s); continuing without it.", e)
|
||||
else:
|
||||
if not HAS_AF_UNIX:
|
||||
log.warning("AF_UNIX not exposed by this Python; disabling UDS backend.")
|
||||
elif IS_WINDOWS:
|
||||
# Even if the Windows kernel supports AF_UNIX, CPython may not expose it,
|
||||
# and this project currently targets POSIX for the UDS backend.
|
||||
log.warning("Windows detected; disabling UDS backend.")
|
||||
|
||||
# if TYPE_CHECKING:
|
||||
# from tractor._addr import Address
|
||||
# ------------------------------------------------------------
|
||||
# Public types and transport registries
|
||||
# ------------------------------------------------------------
|
||||
|
||||
# Address is TCP-only unless UDS is available.
|
||||
if HAS_UDS:
|
||||
Address = TCPAddress | UDSAddress # type: ignore
|
||||
else:
|
||||
Address = TCPAddress # type: ignore
|
||||
|
||||
Address = TCPAddress|UDSAddress
|
||||
|
||||
# manually updated list of all supported msg transport types
|
||||
_msg_transports = [
|
||||
# Manually updated list of all supported msg transport types
|
||||
_msg_transports: list[Type[MsgTransport]] = [
|
||||
MsgpackTCPStream,
|
||||
MsgpackUDSStream
|
||||
]
|
||||
if HAS_UDS:
|
||||
_msg_transports.append(MsgpackUDSStream) # type: ignore
|
||||
|
||||
|
||||
# convert a MsgTransportKey to the corresponding transport type
|
||||
_key_to_transport: dict[
|
||||
MsgTransportKey,
|
||||
Type[MsgTransport],
|
||||
] = {
|
||||
('msgpack', 'tcp'): MsgpackTCPStream,
|
||||
('msgpack', 'uds'): MsgpackUDSStream,
|
||||
# Map MsgTransportKey -> transport type
|
||||
_key_to_transport: dict[MsgTransportKey, Type[MsgTransport]] = {
|
||||
("msgpack", "tcp"): MsgpackTCPStream,
|
||||
}
|
||||
if HAS_UDS:
|
||||
_key_to_transport[("msgpack", "uds")] = MsgpackUDSStream # type: ignore
|
||||
|
||||
# convert an Address wrapper to its corresponding transport type
|
||||
_addr_to_transport: dict[
|
||||
Type[TCPAddress|UDSAddress],
|
||||
Type[MsgTransport]
|
||||
] = {
|
||||
# Map Address wrapper -> transport type
|
||||
_addr_to_transport: dict[Type[Address], Type[MsgTransport]] = { # type: ignore
|
||||
TCPAddress: MsgpackTCPStream,
|
||||
UDSAddress: MsgpackUDSStream,
|
||||
}
|
||||
if HAS_UDS:
|
||||
_addr_to_transport[UDSAddress] = MsgpackUDSStream # type: ignore
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Helpers
|
||||
# ------------------------------------------------------------
|
||||
def transport_from_addr(
|
||||
addr: Address,
|
||||
codec_key: str = 'msgpack',
|
||||
codec_key: str = "msgpack",
|
||||
) -> Type[MsgTransport]:
|
||||
'''
|
||||
"""
|
||||
Given a destination address and a desired codec, find the
|
||||
corresponding `MsgTransport` type.
|
||||
|
||||
'''
|
||||
"""
|
||||
try:
|
||||
return _addr_to_transport[type(addr)]
|
||||
|
||||
return _addr_to_transport[type(addr)] # type: ignore[call-arg]
|
||||
except KeyError:
|
||||
raise NotImplementedError(
|
||||
f'No known transport for address {repr(addr)}'
|
||||
f"No known transport for address {repr(addr)}"
|
||||
)
|
||||
|
||||
|
||||
def transport_from_stream(
|
||||
stream: trio.abc.Stream,
|
||||
codec_key: str = 'msgpack'
|
||||
codec_key: str = "msgpack",
|
||||
) -> Type[MsgTransport]:
|
||||
'''
|
||||
"""
|
||||
Given an arbitrary `trio.abc.Stream` and a desired codec,
|
||||
find the corresponding `MsgTransport` type.
|
||||
|
||||
'''
|
||||
"""
|
||||
transport = None
|
||||
|
||||
if isinstance(stream, trio.SocketStream):
|
||||
sock: socket.socket = stream.socket
|
||||
match sock.family:
|
||||
case socket.AF_INET | socket.AF_INET6:
|
||||
transport = 'tcp'
|
||||
fam = sock.family
|
||||
|
||||
case socket.AF_UNIX:
|
||||
transport = 'uds'
|
||||
if fam in (socket.AF_INET, getattr(socket, "AF_INET6", None)):
|
||||
transport = "tcp"
|
||||
|
||||
case _:
|
||||
raise NotImplementedError(
|
||||
f'Unsupported socket family: {sock.family}'
|
||||
)
|
||||
# Only consider AF_UNIX when both Python exposes it and our backend is active
|
||||
if transport is None and HAS_UDS and HAS_AF_UNIX and fam == socket.AF_UNIX: # type: ignore[attr-defined]
|
||||
transport = "uds"
|
||||
|
||||
if transport is None:
|
||||
raise NotImplementedError(f"Unsupported socket family: {fam}")
|
||||
|
||||
if not transport:
|
||||
raise NotImplementedError(
|
||||
f'Could not figure out transport type for stream type {type(stream)}'
|
||||
f"Could not figure out transport type for stream type {type(stream)}"
|
||||
)
|
||||
|
||||
key = (codec_key, transport)
|
||||
|
||||
return _key_to_transport[key]
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue