Skip to content

Commit c922a52

Browse files
committed
Add path functionality to SocketListener instead of making UnixSocketListener
1 parent b916c3f commit c922a52

4 files changed

Lines changed: 43 additions & 135 deletions

File tree

File renamed without changes.

src/trio/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@
7272
from ._highlevel_socket import (
7373
SocketListener as SocketListener,
7474
SocketStream as SocketStream,
75-
UnixSocketListener as UnixSocketListener,
7675
)
7776
from ._highlevel_ssl_helpers import (
7877
open_ssl_over_tcp_listeners as open_ssl_over_tcp_listeners,

src/trio/_highlevel_open_unix_listeners.py

Lines changed: 19 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from __future__ import annotations
22

33
import os
4-
import sys
54
from typing import TYPE_CHECKING
65

76
import trio
87
import trio.socket as tsocket
98
from trio import TaskStatus
109

10+
from ._highlevel_open_tcp_listeners import _compute_backlog
11+
1112
if TYPE_CHECKING:
1213
from collections.abc import Awaitable, Callable
1314

@@ -20,50 +21,12 @@
2021
HAS_UNIX = False
2122

2223

23-
# Default backlog size:
24-
#
25-
# Having the backlog too low can cause practical problems (a perfectly healthy
26-
# service that starts failing to accept connections if they arrive in a
27-
# burst).
28-
#
29-
# Having it too high doesn't really cause any problems. Like any buffer, you
30-
# want backlog queue to be zero usually, and it won't save you if you're
31-
# getting connection attempts faster than you can call accept() on an ongoing
32-
# basis. But unlike other buffers, this one doesn't really provide any
33-
# backpressure. If a connection gets stuck waiting in the backlog queue, then
34-
# from the peer's point of view the connection succeeded but then their
35-
# send/recv will stall until we get to it, possibly for a long time. OTOH if
36-
# there isn't room in the backlog queue, then their connect stalls, possibly
37-
# for a long time, which is pretty much the same thing.
38-
#
39-
# A large backlog can also use a bit more kernel memory, but this seems fairly
40-
# negligible these days.
41-
#
42-
# So this suggests we should make the backlog as large as possible. This also
43-
# matches what Golang does. However, they do it in a weird way, where they
44-
# have a bunch of code to sniff out the configured upper limit for backlog on
45-
# different operating systems. But on every system, passing in a too-large
46-
# backlog just causes it to be silently truncated to the configured maximum,
47-
# so this is unnecessary -- we can just pass in "infinity" and get the maximum
48-
# that way. (Verified on Windows, Linux, macOS using
49-
# https://github.com/python-trio/trio/wiki/notes-to-self#measure-listen-backlogpy
50-
def _compute_backlog(backlog: int | None) -> int:
51-
# Many systems (Linux, BSDs, ...) store the backlog in a uint16 and are
52-
# missing overflow protection, so we apply our own overflow protection.
53-
# https://github.com/golang/go/issues/5030
54-
if not isinstance(backlog, int) and backlog is not None:
55-
raise TypeError(f"backlog must be an int or None, not {backlog!r}")
56-
if backlog is None:
57-
return 0xFFFF
58-
return min(backlog, 0xFFFF)
59-
60-
6124
async def open_unix_listener(
6225
path: str | bytes | os.PathLike[str] | os.PathLike[bytes],
6326
*,
64-
mode: int | None = None, # 0o666,
27+
mode: int | None = None,
6528
backlog: int | None = None,
66-
) -> trio.UnixSocketListener:
29+
) -> trio.SocketListener:
6730
"""Create :class:`SocketListener` objects to listen for connections.
6831
Opens a connection to the specified
6932
`Unix domain socket <https://en.wikipedia.org/wiki/Unix_domain_socket>`__.
@@ -76,20 +39,21 @@ async def open_unix_listener(
7639
Absolute or relative paths may be used.
7740
7841
mode (int or None): The socket file permissions.
79-
UNIX permissions are usually specified in octal numbers.
80-
If you leave this as ``None``, Trio will not change the mode from
42+
UNIX permissions are usually specified in octal numbers. If
43+
you leave this as ``None``, Trio will not change the mode from
8144
the operating system's default.
8245
8346
backlog (int or None): The listen backlog to use. If you leave this as
84-
``None`` then Trio will pick a good default. (Currently: whatever
85-
your system has configured as the maximum backlog.)
47+
``None`` then Trio will pick a good default. (Currently:
48+
whatever your system has configured as the maximum backlog.)
8649
8750
Returns:
8851
:class:`UnixSocketListener`
8952
9053
Raises:
91-
:class:`TypeError` if invalid arguments.
54+
:class:`ValueError` If invalid arguments.
9255
:class:`RuntimeError`: If AF_UNIX sockets are not supported.
56+
:class:`FileNotFoundError`: If folder socket file is to be created in does not exist.
9357
"""
9458
if not HAS_UNIX:
9559
raise RuntimeError("Unix sockets are not supported on this platform")
@@ -102,28 +66,23 @@ async def open_unix_listener(
10266
if not await folder.exists():
10367
raise FileNotFoundError(f"Socket folder does not exist: {folder!r}")
10468

105-
# much more simplified logic vs tcp sockets - one socket type and only one
69+
str_path = str(fspath)
70+
71+
# much more simplified logic vs tcp sockets - one socket family and only one
10672
# possible location to connect to
10773
sock = tsocket.socket(AF_UNIX, tsocket.SOCK_STREAM)
10874
try:
109-
# See https://github.com/python-trio/trio/issues/39
110-
if sys.platform != "win32":
111-
sock.setsockopt(tsocket.SOL_SOCKET, tsocket.SO_REUSEADDR, 1)
112-
113-
await sock.bind(str(fspath))
114-
115-
sock.listen(computed_backlog)
75+
await sock.bind(str_path)
11676

11777
if mode is not None:
11878
await fspath.chmod(mode)
11979

120-
return trio.UnixSocketListener(sock)
121-
except BaseException as exc:
80+
sock.listen(computed_backlog)
81+
82+
return trio.SocketListener(sock, str_path)
83+
except BaseException:
12284
sock.close()
123-
try:
124-
os.unlink(str(fspath))
125-
except BaseException as exc_2:
126-
raise exc_2 from exc
85+
os.unlink(str_path)
12786
raise
12887

12988

src/trio/_highlevel_socket.py

Lines changed: 24 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
import errno
55
from contextlib import contextmanager, suppress
6-
from os import unlink
6+
from os import PathLike, stat, unlink
7+
from stat import S_ISSOCK
78
from typing import TYPE_CHECKING, Final, overload
89

910
import trio
@@ -71,6 +72,8 @@ class SocketStream(HalfCloseableStream):
7172
7273
"""
7374

75+
__slots__ = ("_send_conflict_detector", "socket")
76+
7477
def __init__(self, socket: SocketType) -> None:
7578
if not isinstance(socket, tsocket.SocketType):
7679
raise TypeError("SocketStream requires a Trio socket object")
@@ -355,90 +358,34 @@ class SocketListener(Listener[SocketStream]):
355358
incoming connections as :class:`SocketStream` objects.
356359
357360
Args:
361+
358362
socket: The Trio socket object to wrap. Must have type ``SOCK_STREAM``,
359363
and be listening.
360364
365+
path: Used for keeping track of which path a Unix socket is bound
366+
to. If not ``None``, :meth:`aclose` will unlink this path.
367+
File must have socket mode flag set.
368+
361369
Note that the :class:`SocketListener` "takes ownership" of the given
362370
socket; closing the :class:`SocketListener` will also close the socket.
363371
364372
.. attribute:: socket
365373
366374
The Trio socket object that this stream wraps.
367375
368-
"""
369-
370-
def __init__(self, socket: SocketType) -> None:
371-
if not isinstance(socket, tsocket.SocketType):
372-
raise TypeError("SocketListener requires a Trio socket object")
373-
if socket.type != tsocket.SOCK_STREAM:
374-
raise ValueError("SocketListener requires a SOCK_STREAM socket")
375-
try:
376-
listening = socket.getsockopt(tsocket.SOL_SOCKET, tsocket.SO_ACCEPTCONN)
377-
except OSError:
378-
# SO_ACCEPTCONN fails on macOS; we just have to trust the user.
379-
pass
380-
else:
381-
if not listening:
382-
raise ValueError("SocketListener requires a listening socket")
383-
384-
self.socket = socket
385-
386-
async def accept(self) -> SocketStream:
387-
"""Accept an incoming connection.
388-
389-
Returns:
390-
:class:`SocketStream`
391-
392-
Raises:
393-
OSError: if the underlying call to ``accept`` raises an unexpected
394-
error.
395-
ClosedResourceError: if you already closed the socket.
376+
.. attribute:: path
396377
397-
This method handles routine errors like ``ECONNABORTED``, but passes
398-
other errors on to its caller. In particular, it does *not* make any
399-
special effort to handle resource exhaustion errors like ``EMFILE``,
400-
``ENFILE``, ``ENOBUFS``, ``ENOMEM``.
401-
402-
"""
403-
while True:
404-
try:
405-
sock, _ = await self.socket.accept()
406-
except OSError as exc:
407-
if exc.errno in _closed_stream_errnos:
408-
raise trio.ClosedResourceError from None
409-
if exc.errno not in _ignorable_accept_errnos:
410-
raise
411-
else:
412-
return SocketStream(sock)
413-
414-
async def aclose(self) -> None:
415-
"""Close this listener and its underlying socket."""
416-
self.socket.close()
417-
await trio.lowlevel.checkpoint()
418-
419-
420-
@final
421-
class UnixSocketListener(Listener[SocketStream]):
422-
"""A :class:`~trio.abc.Listener` that uses a listening socket to accept
423-
incoming connections as :class:`SocketStream` objects.
424-
425-
Args:
426-
socket: The Trio socket object to wrap. Must have type ``SOCK_STREAM``,
427-
and be listening.
428-
429-
Note that the :class:`UnixSocketListener` "takes ownership" of the given
430-
socket; closing the :class:`UnixSocketListener` will also close the socket
431-
and unlink its associated file.
432-
433-
.. attribute:: socket
434-
435-
The Trio socket object that this stream wraps.
378+
The path to unlink in :meth:`aclose` that a Unix socket is bound to.
436379
437380
"""
438381

439-
def __init__(self, socket: SocketType) -> None:
440-
if not HAS_UNIX:
441-
raise RuntimeError("Unix sockets are not supported on this platform")
382+
__slots__ = ("path", "socket")
383+
384+
def __init__(
385+
self,
386+
socket: SocketType,
387+
path: str | bytes | PathLike[str] | PathLike[bytes] | None = None,
388+
) -> None:
442389
if not isinstance(socket, tsocket.SocketType):
443390
raise TypeError("SocketListener requires a Trio socket object")
444391
if socket.type != tsocket.SOCK_STREAM:
@@ -451,8 +398,11 @@ def __init__(self, socket: SocketType) -> None:
451398
else:
452399
if not listening:
453400
raise ValueError("SocketListener requires a listening socket")
401+
if path is not None and not S_ISSOCK(stat(path).st_mode):
402+
raise ValueError("Specified path must be a Unix socket file")
454403

455404
self.socket = socket
405+
self.path = path
456406

457407
async def accept(self) -> SocketStream:
458408
"""Accept an incoming connection.
@@ -483,8 +433,8 @@ async def accept(self) -> SocketStream:
483433
return SocketStream(sock)
484434

485435
async def aclose(self) -> None:
486-
"""Close this listener, its underlying socket, and unlink its associated file."""
487-
path = self.socket.getsockname()
436+
"""Close this listener and its underlying socket."""
488437
self.socket.close()
489-
unlink(path)
438+
if self.path is not None:
439+
unlink(self.path)
490440
await trio.lowlevel.checkpoint()

0 commit comments

Comments
 (0)