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-codewindows_support_round2
parent
65bf9df5ba
commit
470334c0b1
|
|
@ -31,12 +31,21 @@ from threading import (
|
||||||
RLock,
|
RLock,
|
||||||
)
|
)
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
|
|
||||||
|
import platform
|
||||||
|
|
||||||
from signal import (
|
from signal import (
|
||||||
signal,
|
signal,
|
||||||
getsignal,
|
getsignal,
|
||||||
SIGUSR1,
|
|
||||||
SIGINT,
|
SIGINT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
from signal import SIGUSR1
|
||||||
|
else:
|
||||||
|
SIGUSR1 = None
|
||||||
|
|
||||||
# import traceback
|
# import traceback
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import (
|
from typing import (
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import platform
|
||||||
|
import socket
|
||||||
|
|
||||||
|
|
||||||
from bidict import bidict
|
from bidict import bidict
|
||||||
from trio import (
|
from trio import (
|
||||||
SocketListener,
|
SocketListener,
|
||||||
|
|
@ -32,7 +36,6 @@ from ..runtime._state import (
|
||||||
_def_tpt_proto,
|
_def_tpt_proto,
|
||||||
)
|
)
|
||||||
from ..ipc._tcp import TCPAddress
|
from ..ipc._tcp import TCPAddress
|
||||||
from ..ipc._uds import UDSAddress
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..runtime._runtime import Actor
|
from ..runtime._runtime import Actor
|
||||||
|
|
@ -40,6 +43,20 @@ if TYPE_CHECKING:
|
||||||
log = get_logger()
|
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?
|
# TODO, maybe breakout the netns key to a struct?
|
||||||
# class NetNs(Struct)[str, int]:
|
# class NetNs(Struct)[str, int]:
|
||||||
# ...
|
# ...
|
||||||
|
|
@ -172,20 +189,25 @@ class Address(Protocol):
|
||||||
|
|
||||||
_address_types: bidict[str, Type[Address]] = {
|
_address_types: bidict[str, Type[Address]] = {
|
||||||
'tcp': TCPAddress,
|
'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
|
# TODO! really these are discovery sys default addrs ONLY useful for
|
||||||
# when none is provided to a root actor on first boot.
|
# when none is provided to a root actor on first boot.
|
||||||
_default_lo_addrs: dict[
|
_default_lo_addrs: dict[str, UnwrappedAddress] = {
|
||||||
str,
|
|
||||||
UnwrappedAddress
|
|
||||||
] = {
|
|
||||||
'tcp': TCPAddress.get_root().unwrap(),
|
'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]:
|
def get_address_cls(name: str) -> Type[Address]:
|
||||||
return _address_types[name]
|
return _address_types[name]
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ from functools import partial
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import inspect
|
import inspect
|
||||||
import textwrap
|
import textwrap
|
||||||
|
import platform
|
||||||
|
import socket
|
||||||
from types import (
|
from types import (
|
||||||
ModuleType,
|
ModuleType,
|
||||||
)
|
)
|
||||||
|
|
@ -62,16 +64,23 @@ from .. import log
|
||||||
from ..discovery._addr import Address
|
from ..discovery._addr import Address
|
||||||
from ._chan import Channel
|
from ._chan import Channel
|
||||||
from ._transport import MsgTransport
|
from ._transport import MsgTransport
|
||||||
from ._uds import UDSAddress
|
|
||||||
from ._tcp import TCPAddress
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..runtime._runtime import Actor
|
from ..runtime._runtime import Actor
|
||||||
from ..runtime._supervise import ActorNursery
|
from ..runtime._supervise import ActorNursery
|
||||||
|
|
||||||
|
|
||||||
|
from ._tcp import TCPAddress
|
||||||
|
|
||||||
log = log.get_logger()
|
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(
|
async def maybe_wait_on_canced_subs(
|
||||||
uid: tuple[str, str],
|
uid: tuple[str, str],
|
||||||
|
|
|
||||||
|
|
@ -18,106 +18,132 @@
|
||||||
IPC subsys type-lookup helpers?
|
IPC subsys type-lookup helpers?
|
||||||
|
|
||||||
'''
|
'''
|
||||||
from typing import (
|
from typing import Type
|
||||||
Type,
|
import platform
|
||||||
# TYPE_CHECKING,
|
|
||||||
)
|
|
||||||
|
|
||||||
import trio
|
|
||||||
import socket
|
import socket
|
||||||
|
import trio
|
||||||
|
|
||||||
|
from tractor.log import get_logger
|
||||||
from tractor.ipc._transport import (
|
from tractor.ipc._transport import (
|
||||||
MsgTransportKey,
|
MsgTransportKey,
|
||||||
MsgTransport
|
MsgTransport,
|
||||||
)
|
)
|
||||||
from tractor.ipc._tcp import (
|
from tractor.ipc._tcp import (
|
||||||
TCPAddress,
|
TCPAddress,
|
||||||
MsgpackTCPStream,
|
MsgpackTCPStream,
|
||||||
)
|
)
|
||||||
from tractor.ipc._uds import (
|
|
||||||
UDSAddress,
|
log = get_logger()
|
||||||
MsgpackUDSStream,
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 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: list[Type[MsgTransport]] = [
|
||||||
# manually updated list of all supported msg transport types
|
|
||||||
_msg_transports = [
|
|
||||||
MsgpackTCPStream,
|
MsgpackTCPStream,
|
||||||
MsgpackUDSStream
|
|
||||||
]
|
]
|
||||||
|
if HAS_UDS:
|
||||||
|
_msg_transports.append(MsgpackUDSStream) # type: ignore
|
||||||
|
|
||||||
|
# Map MsgTransportKey -> transport type
|
||||||
# convert a MsgTransportKey to the corresponding transport type
|
_key_to_transport: dict[MsgTransportKey, Type[MsgTransport]] = {
|
||||||
_key_to_transport: dict[
|
("msgpack", "tcp"): MsgpackTCPStream,
|
||||||
MsgTransportKey,
|
|
||||||
Type[MsgTransport],
|
|
||||||
] = {
|
|
||||||
('msgpack', 'tcp'): MsgpackTCPStream,
|
|
||||||
('msgpack', 'uds'): MsgpackUDSStream,
|
|
||||||
}
|
}
|
||||||
|
if HAS_UDS:
|
||||||
|
_key_to_transport[("msgpack", "uds")] = MsgpackUDSStream # type: ignore
|
||||||
|
|
||||||
# convert an Address wrapper to its corresponding transport type
|
# Map Address wrapper -> transport type
|
||||||
_addr_to_transport: dict[
|
_addr_to_transport: dict[Type[Address], Type[MsgTransport]] = { # type: ignore
|
||||||
Type[TCPAddress|UDSAddress],
|
|
||||||
Type[MsgTransport]
|
|
||||||
] = {
|
|
||||||
TCPAddress: MsgpackTCPStream,
|
TCPAddress: MsgpackTCPStream,
|
||||||
UDSAddress: MsgpackUDSStream,
|
|
||||||
}
|
}
|
||||||
|
if HAS_UDS:
|
||||||
|
_addr_to_transport[UDSAddress] = MsgpackUDSStream # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# Helpers
|
||||||
|
# ------------------------------------------------------------
|
||||||
def transport_from_addr(
|
def transport_from_addr(
|
||||||
addr: Address,
|
addr: Address,
|
||||||
codec_key: str = 'msgpack',
|
codec_key: str = "msgpack",
|
||||||
) -> Type[MsgTransport]:
|
) -> Type[MsgTransport]:
|
||||||
'''
|
"""
|
||||||
Given a destination address and a desired codec, find the
|
Given a destination address and a desired codec, find the
|
||||||
corresponding `MsgTransport` type.
|
corresponding `MsgTransport` type.
|
||||||
|
"""
|
||||||
'''
|
|
||||||
try:
|
try:
|
||||||
return _addr_to_transport[type(addr)]
|
return _addr_to_transport[type(addr)] # type: ignore[call-arg]
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
f'No known transport for address {repr(addr)}'
|
f"No known transport for address {repr(addr)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def transport_from_stream(
|
def transport_from_stream(
|
||||||
stream: trio.abc.Stream,
|
stream: trio.abc.Stream,
|
||||||
codec_key: str = 'msgpack'
|
codec_key: str = "msgpack",
|
||||||
) -> Type[MsgTransport]:
|
) -> Type[MsgTransport]:
|
||||||
'''
|
"""
|
||||||
Given an arbitrary `trio.abc.Stream` and a desired codec,
|
Given an arbitrary `trio.abc.Stream` and a desired codec,
|
||||||
find the corresponding `MsgTransport` type.
|
find the corresponding `MsgTransport` type.
|
||||||
|
"""
|
||||||
'''
|
|
||||||
transport = None
|
transport = None
|
||||||
|
|
||||||
if isinstance(stream, trio.SocketStream):
|
if isinstance(stream, trio.SocketStream):
|
||||||
sock: socket.socket = stream.socket
|
sock: socket.socket = stream.socket
|
||||||
match sock.family:
|
fam = sock.family
|
||||||
case socket.AF_INET | socket.AF_INET6:
|
|
||||||
transport = 'tcp'
|
|
||||||
|
|
||||||
case socket.AF_UNIX:
|
if fam in (socket.AF_INET, getattr(socket, "AF_INET6", None)):
|
||||||
transport = 'uds'
|
transport = "tcp"
|
||||||
|
|
||||||
case _:
|
# Only consider AF_UNIX when both Python exposes it and our backend is active
|
||||||
raise NotImplementedError(
|
if transport is None and HAS_UDS and HAS_AF_UNIX and fam == socket.AF_UNIX: # type: ignore[attr-defined]
|
||||||
f'Unsupported socket family: {sock.family}'
|
transport = "uds"
|
||||||
)
|
|
||||||
|
if transport is None:
|
||||||
|
raise NotImplementedError(f"Unsupported socket family: {fam}")
|
||||||
|
|
||||||
if not transport:
|
if not transport:
|
||||||
raise NotImplementedError(
|
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)
|
key = (codec_key, transport)
|
||||||
|
|
||||||
return _key_to_transport[key]
|
return _key_to_transport[key]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue