Merge pull request #475 from goodboy/windows_support_round2
Restore Windows support (optional UDS + `SIGUSR1`)wkt/macos_ci_reruns
commit
4e27dcde48
|
|
@ -91,6 +91,10 @@ jobs:
|
||||||
name: '${{ matrix.os }} Python${{ matrix.python-version }} spawn_backend=${{ matrix.spawn_backend }} tpt_proto=${{ matrix.tpt_proto }}'
|
name: '${{ matrix.os }} Python${{ matrix.python-version }} spawn_backend=${{ matrix.spawn_backend }} tpt_proto=${{ matrix.tpt_proto }}'
|
||||||
timeout-minutes: 16
|
timeout-minutes: 16
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
# Windows support is nascent: its full test suite remains
|
||||||
|
# informational, while setup and the `import tractor` smoke below
|
||||||
|
# are hard signals. Promote the test step to required once the
|
||||||
|
# suite is green.
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
@ -98,6 +102,7 @@ jobs:
|
||||||
os: [
|
os: [
|
||||||
ubuntu-latest,
|
ubuntu-latest,
|
||||||
macos-latest,
|
macos-latest,
|
||||||
|
windows-latest,
|
||||||
]
|
]
|
||||||
python-version: [
|
python-version: [
|
||||||
'3.13',
|
'3.13',
|
||||||
|
|
@ -118,6 +123,11 @@ jobs:
|
||||||
'tcp',
|
'tcp',
|
||||||
'uds',
|
'uds',
|
||||||
]
|
]
|
||||||
|
exclude:
|
||||||
|
# UDS is POSIX-only; Windows has no `AF_UNIX` so the
|
||||||
|
# backend is intentionally unavailable there.
|
||||||
|
- os: windows-latest
|
||||||
|
tpt_proto: 'uds'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
@ -145,7 +155,14 @@ jobs:
|
||||||
- name: List deps tree
|
- name: List deps tree
|
||||||
run: uv tree
|
run: uv tree
|
||||||
|
|
||||||
|
# hard signal for the Windows import-safety fix: `import
|
||||||
|
# tractor` must succeed everywhere, and `HAS_UDS` reflects
|
||||||
|
# platform capability (False on Windows, True on POSIX).
|
||||||
|
- name: 'Smoke: import tractor'
|
||||||
|
run: uv run python -c "import sys; import tractor; from tractor.ipc._uds import HAS_UDS; assert sys.platform != 'win32' or not HAS_UDS; print('import tractor OK | HAS_UDS=', HAS_UDS)"
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
continue-on-error: ${{ matrix.os == 'windows-latest' }}
|
||||||
run: >
|
run: >
|
||||||
uv run
|
uv run
|
||||||
pytest
|
pytest
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
model: openai/gpt-5.6-sol
|
||||||
|
service: opencode
|
||||||
|
session: pr475-review-fixes-20260817
|
||||||
|
timestamp: 2026-08-17T23:18:25Z
|
||||||
|
git_ref: 359fe75c
|
||||||
|
scope: code
|
||||||
|
substantive: true
|
||||||
|
raw_file: 20260817T231825Z_359fe75c_prompt_io.raw.md
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prompt
|
||||||
|
|
||||||
|
Continue the `/code-review-changes` pass for PR #475 in its isolated
|
||||||
|
worktree. Address the seven accepted manual-review findings in
|
||||||
|
`tractor/ipc/_types.py` and `tractor/ipc/_uds.py`, preserve the existing
|
||||||
|
Windows capability behavior, verify the result, and prepare the work for
|
||||||
|
human-controlled commit and review-reply steps. Do not publish replies,
|
||||||
|
stage, commit, or push without the required explicit authorization.
|
||||||
|
|
||||||
|
## Response summary
|
||||||
|
|
||||||
|
Restored project quote, docstring, multiline-expression, and
|
||||||
|
`match/case` conventions while retaining the Windows-safe UDS guard.
|
||||||
|
Removed unnecessary structural and comment churn, then verified the
|
||||||
|
focused transport, discovery, and lazy-import paths plus the missing
|
||||||
|
`AF_UNIX` behavior.
|
||||||
|
|
||||||
|
## Files changed
|
||||||
|
|
||||||
|
- `tractor/ipc/_types.py` - restore project style and guarded
|
||||||
|
socket-family dispatch.
|
||||||
|
- `tractor/ipc/_uds.py` - format the UDS capability gate
|
||||||
|
consistently.
|
||||||
|
|
||||||
|
## Human edits
|
||||||
|
|
||||||
|
None - the generated patch remains uncommitted and awaits human
|
||||||
|
review.
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
model: openai/gpt-5.6-sol
|
||||||
|
service: opencode
|
||||||
|
timestamp: 2026-08-17T23:18:25Z
|
||||||
|
git_ref: 359fe75c
|
||||||
|
diff_cmd: git diff HEAD~1..HEAD
|
||||||
|
---
|
||||||
|
|
||||||
|
Applied the seven accepted manual-review fixes for PR #475 while
|
||||||
|
preserving the Windows transport capability behavior.
|
||||||
|
|
||||||
|
> `git diff HEAD~1..HEAD -- tractor/ipc/_types.py`
|
||||||
|
|
||||||
|
The generated changes restore the project's single-quote docstring and
|
||||||
|
string conventions, remove the unnecessary helper divider, simplify the
|
||||||
|
transport-registry comments, and restore `match/case` socket-family
|
||||||
|
dispatch. The UDS case retains a `HAS_UDS` guard that short-circuits
|
||||||
|
before `socket.AF_UNIX` is evaluated on unsupported hosts. Nearby error
|
||||||
|
messages are wrapped without changing their content.
|
||||||
|
|
||||||
|
> `git diff HEAD~1..HEAD -- tractor/ipc/_uds.py`
|
||||||
|
|
||||||
|
The generated change reformats the `HAS_UDS` conjunction according to
|
||||||
|
the project's multiline boolean-expression convention and simplifies
|
||||||
|
the adjacent capability comment.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
`/home/goodboy/repos/tractor/py313/bin/pytest -q tests/test_lazy_imports.py tests/discovery tests/ipc/test_server.py`
|
||||||
|
|
||||||
|
Result: `66 passed, 2 xpassed in 60.62s`.
|
||||||
|
|
||||||
|
`ruff check --no-cache --output-format=json tractor/ipc/_types.py tractor/ipc/_uds.py`
|
||||||
|
|
||||||
|
Result: no findings.
|
||||||
|
|
||||||
|
`git diff --check`
|
||||||
|
|
||||||
|
Result: no whitespace errors.
|
||||||
|
|
||||||
|
An explicit missing-`AF_UNIX` probe set `HAS_UDS = False`, removed the
|
||||||
|
socket constant, and exercised an unsupported socket family. It raised
|
||||||
|
the expected `NotImplementedError` instead of `AttributeError`.
|
||||||
|
|
||||||
|
No review replies, commits, or pushes were published.
|
||||||
|
|
@ -20,6 +20,18 @@ from typing import (
|
||||||
import pytest
|
import pytest
|
||||||
import trio
|
import trio
|
||||||
import tractor
|
import tractor
|
||||||
|
|
||||||
|
# `infect_asyncio` mode is unsupported on Windows (asyncio's
|
||||||
|
# `ProactorEventLoop` is incompatible with our `trio` guest-mode
|
||||||
|
# interop and currently hangs/crashes the run). Skip the module on
|
||||||
|
# Windows so the CI leg completes + reports the rest of the suite.
|
||||||
|
import platform
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
pytest.skip(
|
||||||
|
'infect_asyncio mode is unsupported on Windows',
|
||||||
|
allow_module_level=True,
|
||||||
|
)
|
||||||
|
|
||||||
from tractor import (
|
from tractor import (
|
||||||
current_actor,
|
current_actor,
|
||||||
Actor,
|
Actor,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,22 @@
|
||||||
import time
|
import time
|
||||||
|
import platform
|
||||||
|
|
||||||
import trio
|
import trio
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import tractor
|
import tractor
|
||||||
|
|
||||||
|
# `tractor.ipc._ringbuf` is built on linux `eventfd(2)`; importing
|
||||||
|
# it pulls in `tractor.ipc._linux` whose module-level
|
||||||
|
# `ffi.dlopen(None)` raises on non-linux. Skip the whole module at
|
||||||
|
# COLLECTION before that crashing import runs (a `pytestmark` skip
|
||||||
|
# is too late — markers apply only after the import succeeds).
|
||||||
|
if platform.system() != 'Linux':
|
||||||
|
pytest.skip(
|
||||||
|
'ringbuf (eventfd) IPC is linux-only',
|
||||||
|
allow_module_level=True,
|
||||||
|
)
|
||||||
|
|
||||||
# XXX `cffi` dun build on py3.14 yet..
|
# XXX `cffi` dun build on py3.14 yet..
|
||||||
pytest.importorskip("cffi")
|
pytest.importorskip("cffi")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,17 @@ from functools import partial
|
||||||
import pytest
|
import pytest
|
||||||
import trio
|
import trio
|
||||||
import tractor
|
import tractor
|
||||||
|
|
||||||
|
# `infect_asyncio` mode is unsupported on Windows (see
|
||||||
|
# `test_infected_asyncio`); skip at COLLECTION before the
|
||||||
|
# asyncio-interop imports below so the CI leg completes.
|
||||||
|
import platform
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
pytest.skip(
|
||||||
|
'infect_asyncio mode is unsupported on Windows',
|
||||||
|
allow_module_level=True,
|
||||||
|
)
|
||||||
|
|
||||||
from tractor import (
|
from tractor import (
|
||||||
to_asyncio,
|
to_asyncio,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,11 @@ async def test_lifetime_stack_wipes_tmpfile(
|
||||||
1.6 if error_in_child
|
1.6 if error_in_child
|
||||||
else 1
|
else 1
|
||||||
)
|
)
|
||||||
|
# scale for slow/noisy CI (esp. macOS) so the child error
|
||||||
|
# propagates before the deadline; otherwise `move_on_after`
|
||||||
|
# cancels first and flips the `error_in_child=True` assert.
|
||||||
|
from .conftest import cpu_perf_headroom
|
||||||
|
timeout *= cpu_perf_headroom()
|
||||||
try:
|
try:
|
||||||
with trio.move_on_after(timeout) as cs:
|
with trio.move_on_after(timeout) as cs:
|
||||||
async with tractor.open_nursery(
|
async with tractor.open_nursery(
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,13 @@ async def child_read_shm_list(
|
||||||
print(f'(child): reading frame: {frame}')
|
print(f'(child): reading frame: {frame}')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
platform.system() == 'Windows',
|
||||||
|
reason=(
|
||||||
|
'parent/child shm IPC deadlocks on Windows '
|
||||||
|
'(frame-size dependent hang); nascent — see #404'
|
||||||
|
),
|
||||||
|
)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'use_str',
|
'use_str',
|
||||||
[False, True],
|
[False, True],
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
@ -347,8 +356,8 @@ def dump_tree_on_sig(
|
||||||
|
|
||||||
|
|
||||||
def enable_stack_on_sig(
|
def enable_stack_on_sig(
|
||||||
sig: int = SIGUSR1,
|
sig: int|None = SIGUSR1,
|
||||||
) -> ModuleType:
|
) -> ModuleType|None:
|
||||||
'''
|
'''
|
||||||
Enable `stackscope` tracing on reception of a signal; by
|
Enable `stackscope` tracing on reception of a signal; by
|
||||||
default this is SIGUSR1.
|
default this is SIGUSR1.
|
||||||
|
|
@ -367,6 +376,16 @@ def enable_stack_on_sig(
|
||||||
>> pkill --signal SIGUSR1 -f <part-of-cmd: str>
|
>> pkill --signal SIGUSR1 -f <part-of-cmd: str>
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
# no `SIGUSR1` on this platform (e.g. Windows) -> nothing to
|
||||||
|
# wire up; degrade gracefully instead of crashing callers that
|
||||||
|
# only guard against a missing `stackscope` (`ImportError`).
|
||||||
|
if sig is None:
|
||||||
|
log.warning(
|
||||||
|
'No `SIGUSR1` on this platform;\n'
|
||||||
|
'skipping `stackscope` trace-on-signal setup!\n'
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# NOTE, `stackscope._glue` does intentional async-gen type
|
# NOTE, `stackscope._glue` does intentional async-gen type
|
||||||
# introspection at import-time which trips
|
# introspection at import-time which trips
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,10 @@ 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
|
from ..ipc._uds import (
|
||||||
|
UDSAddress,
|
||||||
|
HAS_UDS,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# ONLY type-annots, the eager import costs ~4.5ms
|
# ONLY type-annots, the eager import costs ~4.5ms
|
||||||
|
|
@ -43,7 +46,6 @@ else:
|
||||||
|
|
||||||
log = get_logger()
|
log = get_logger()
|
||||||
|
|
||||||
|
|
||||||
# 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]:
|
||||||
# ...
|
# ...
|
||||||
|
|
@ -174,25 +176,36 @@ class Address(Protocol):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
# the address types available on this host: TCP always, UDS only
|
||||||
|
# where usable (`HAS_UDS`). Both registries derive from this single
|
||||||
|
# list via each type's `proto_key`.
|
||||||
|
_address_protos: list[Type[Address]] = [TCPAddress]
|
||||||
|
if HAS_UDS:
|
||||||
|
_address_protos.append(UDSAddress)
|
||||||
|
|
||||||
_address_types: dict[str, Type[Address]] = {
|
_address_types: dict[str, Type[Address]] = {
|
||||||
'tcp': TCPAddress,
|
cls.proto_key: cls
|
||||||
'uds': UDSAddress
|
for cls in _address_protos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# 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,
|
cls.proto_key: cls.get_root().unwrap()
|
||||||
UnwrappedAddress
|
for cls in _address_protos
|
||||||
] = {
|
|
||||||
'tcp': TCPAddress.get_root().unwrap(),
|
|
||||||
'uds': UDSAddress.get_root().unwrap(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_address_cls(name: str) -> Type[Address]:
|
def get_address_cls(name: str) -> Type[Address]:
|
||||||
return _address_types[name]
|
try:
|
||||||
|
return _address_types[name]
|
||||||
|
except KeyError:
|
||||||
|
raise NotImplementedError(
|
||||||
|
f'No IPC transport backend for {name!r} on this '
|
||||||
|
f'platform!\n'
|
||||||
|
f'(available: {list(_address_types)})\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def is_wrapped_addr(addr: any) -> bool:
|
def is_wrapped_addr(addr: any) -> bool:
|
||||||
|
|
@ -290,7 +303,14 @@ def default_lo_addrs(
|
||||||
for an input transport key set.
|
for an input transport key set.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
return [
|
lo_addrs: list[UnwrappedAddress] = []
|
||||||
_default_lo_addrs[transport]
|
for transport in transports:
|
||||||
for transport in transports
|
try:
|
||||||
]
|
lo_addrs.append(_default_lo_addrs[transport])
|
||||||
|
except KeyError:
|
||||||
|
raise NotImplementedError(
|
||||||
|
f'No default loopback addr for transport '
|
||||||
|
f'{transport!r} on this platform!\n'
|
||||||
|
f'(available: {list(_default_lo_addrs)})\n'
|
||||||
|
)
|
||||||
|
return lo_addrs
|
||||||
|
|
|
||||||
|
|
@ -62,19 +62,20 @@ 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
|
||||||
|
from ._uds import UDSAddress
|
||||||
|
|
||||||
log = log.get_logger()
|
log = log.get_logger()
|
||||||
|
|
||||||
_PRE_REG_HANDSHAKE_TIMEOUT: float = 10
|
_PRE_REG_HANDSHAKE_TIMEOUT: float = 10
|
||||||
|
|
||||||
|
|
||||||
async def maybe_wait_on_canced_subs(
|
async def maybe_wait_on_canced_subs(
|
||||||
uid: tuple[str, str],
|
uid: tuple[str, str],
|
||||||
chan: Channel,
|
chan: Channel,
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,13 @@
|
||||||
IPC subsys type-lookup helpers?
|
IPC subsys type-lookup helpers?
|
||||||
|
|
||||||
'''
|
'''
|
||||||
from typing import (
|
from typing import Type
|
||||||
Type,
|
|
||||||
# TYPE_CHECKING,
|
|
||||||
)
|
|
||||||
|
|
||||||
import trio
|
|
||||||
import socket
|
import socket
|
||||||
|
import trio
|
||||||
|
|
||||||
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,
|
||||||
|
|
@ -37,37 +33,33 @@ from tractor.ipc._tcp import (
|
||||||
from tractor.ipc._uds import (
|
from tractor.ipc._uds import (
|
||||||
UDSAddress,
|
UDSAddress,
|
||||||
MsgpackUDSStream,
|
MsgpackUDSStream,
|
||||||
|
HAS_UDS,
|
||||||
)
|
)
|
||||||
|
|
||||||
# if TYPE_CHECKING:
|
# the UDS backend is importable everywhere but only *usable* when
|
||||||
# from tractor._addr import Address
|
# `HAS_UDS` is `True`; otherwise the runtime registers TCP only.
|
||||||
|
|
||||||
|
|
||||||
Address = TCPAddress|UDSAddress
|
Address = TCPAddress|UDSAddress
|
||||||
|
|
||||||
# manually updated list of all supported msg transport types
|
# the available msg-transport backends on this host: TCP always,
|
||||||
_msg_transports = [
|
# UDS only where usable (`HAS_UDS`). The lookup maps below derive
|
||||||
|
# from this single list via each backend's `codec_key` and
|
||||||
|
# `address_type`: register a backend here and every map picks it up.
|
||||||
|
_msg_transports: list[Type[MsgTransport]] = [
|
||||||
MsgpackTCPStream,
|
MsgpackTCPStream,
|
||||||
MsgpackUDSStream
|
|
||||||
]
|
]
|
||||||
|
if HAS_UDS:
|
||||||
|
_msg_transports.append(MsgpackUDSStream)
|
||||||
|
|
||||||
|
# map a `MsgTransportKey` -> `MsgTransport` type
|
||||||
# convert a MsgTransportKey to the corresponding transport type
|
_key_to_transport: dict[MsgTransportKey, Type[MsgTransport]] = {
|
||||||
_key_to_transport: dict[
|
(t.codec_key, t.address_type.proto_key): t
|
||||||
MsgTransportKey,
|
for t in _msg_transports
|
||||||
Type[MsgTransport],
|
|
||||||
] = {
|
|
||||||
('msgpack', 'tcp'): MsgpackTCPStream,
|
|
||||||
('msgpack', 'uds'): MsgpackUDSStream,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# convert an Address wrapper to its corresponding transport type
|
# map an `Address`-wrapper -> `MsgTransport` type
|
||||||
_addr_to_transport: dict[
|
_addr_to_transport: dict[Type[Address], Type[MsgTransport]] = {
|
||||||
Type[TCPAddress|UDSAddress],
|
t.address_type: t
|
||||||
Type[MsgTransport]
|
for t in _msg_transports
|
||||||
] = {
|
|
||||||
TCPAddress: MsgpackTCPStream,
|
|
||||||
UDSAddress: MsgpackUDSStream,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -81,41 +73,51 @@ def transport_from_addr(
|
||||||
|
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
return _addr_to_transport[type(addr)]
|
addr_type = type(addr)
|
||||||
|
return _addr_to_transport[addr_type]
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
f'No known transport for address {repr(addr)}'
|
f'No known transport for address '
|
||||||
|
f'{addr!r}'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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: str|None = None
|
||||||
|
|
||||||
if isinstance(stream, trio.SocketStream):
|
if isinstance(stream, trio.SocketStream):
|
||||||
sock: socket.socket = stream.socket
|
sock: socket.socket = stream.socket
|
||||||
match sock.family:
|
match sock.family:
|
||||||
case socket.AF_INET | socket.AF_INET6:
|
case socket.AF_INET | socket.AF_INET6:
|
||||||
transport = 'tcp'
|
transport = 'tcp'
|
||||||
|
|
||||||
case socket.AF_UNIX:
|
# `HAS_UDS` short-circuits before `socket.AF_UNIX` on
|
||||||
|
# hosts where that constant is absent.
|
||||||
|
case fam if (
|
||||||
|
HAS_UDS
|
||||||
|
and
|
||||||
|
fam == socket.AF_UNIX
|
||||||
|
):
|
||||||
transport = 'uds'
|
transport = 'uds'
|
||||||
|
|
||||||
case _:
|
case fam:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
f'Unsupported socket family: {sock.family}'
|
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 '
|
||||||
|
f'{type(stream)}'
|
||||||
)
|
)
|
||||||
|
|
||||||
key = (codec_key, transport)
|
key = (codec_key, transport)
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,21 @@ from pathlib import Path
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from socket import (
|
from socket import (
|
||||||
AF_UNIX,
|
|
||||||
SOCK_STREAM,
|
SOCK_STREAM,
|
||||||
SOL_SOCKET,
|
SOL_SOCKET,
|
||||||
error as socket_error,
|
error as socket_error,
|
||||||
)
|
)
|
||||||
|
# NOTE, `AF_UNIX` is absent on Windows / any CPython built without
|
||||||
|
# unix-domain-socket support. Keep this module importable
|
||||||
|
# everywhere (so `UDSAddress` stays referenceable for type and
|
||||||
|
# `isinstance()` checks plus registry lookups); the `AF_UNIX`-using
|
||||||
|
# code paths below are runtime-only and are never reached when the
|
||||||
|
# UDS backend is unusable (gated on `trio`'s `has_unix`, see
|
||||||
|
# `HAS_UDS`).
|
||||||
|
try:
|
||||||
|
from socket import AF_UNIX
|
||||||
|
except ImportError:
|
||||||
|
AF_UNIX = None
|
||||||
import struct
|
import struct
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
|
|
@ -104,6 +114,16 @@ _SUN_PATH_LIMIT: int = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# single source of truth for whether the UDS backend is usable on this
|
||||||
|
# host. Windows can expose `AF_UNIX`, but this backend remains
|
||||||
|
# POSIX-only until its credential and lifecycle paths are supported.
|
||||||
|
HAS_UDS: bool = (
|
||||||
|
sys.platform != 'win32'
|
||||||
|
and
|
||||||
|
has_unix
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def unwrap_sockpath(
|
def unwrap_sockpath(
|
||||||
sockpath: Path,
|
sockpath: Path,
|
||||||
) -> tuple[Path, Path]:
|
) -> tuple[Path, Path]:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue