diff --git a/docs/source/reference-core.rst b/docs/source/reference-core.rst index 980a3106e5..30994e4386 100644 --- a/docs/source/reference-core.rst +++ b/docs/source/reference-core.rst @@ -922,9 +922,6 @@ The nursery API See :meth:`~Nursery.start`. -.. autoclass:: TaskStatus - :members: - .. _task-local-storage: Task-local storage diff --git a/docs/source/reference-lowlevel.rst b/docs/source/reference-lowlevel.rst index bacebff5ad..faf07268cb 100644 --- a/docs/source/reference-lowlevel.rst +++ b/docs/source/reference-lowlevel.rst @@ -534,6 +534,8 @@ Task API putting a task to sleep and then waking it up again. (See :func:`wait_task_rescheduled` for details.) +.. autoclass:: TaskStatus + :members: .. _guest-mode: diff --git a/trio/__init__.py b/trio/__init__.py index be7de42cde..277baa5339 100644 --- a/trio/__init__.py +++ b/trio/__init__.py @@ -35,7 +35,6 @@ EndOfChannel as EndOfChannel, Nursery as Nursery, RunFinishedError as RunFinishedError, - TaskStatus as TaskStatus, TrioInternalError as TrioInternalError, WouldBlock as WouldBlock, current_effective_deadline as current_effective_deadline, diff --git a/trio/_dtls.py b/trio/_dtls.py index 8675cb75b6..b3ed0fd883 100644 --- a/trio/_dtls.py +++ b/trio/_dtls.py @@ -42,8 +42,8 @@ from OpenSSL.SSL import Context from typing_extensions import Self, TypeAlias - from ._core._run import TaskStatus - from ._socket import Address, _SocketType + from trio.lowlevel import TaskStatus + from trio.socket import Address, _SocketType MAX_UDP_PACKET_SIZE = 65527 diff --git a/trio/_highlevel_open_tcp_listeners.py b/trio/_highlevel_open_tcp_listeners.py index 6211917254..0d5f630495 100644 --- a/trio/_highlevel_open_tcp_listeners.py +++ b/trio/_highlevel_open_tcp_listeners.py @@ -1,8 +1,12 @@ +from __future__ import annotations + import errno import sys +from collections.abc import Awaitable, Callable from math import inf import trio +from trio.lowlevel import TaskStatus from . import socket as tsocket @@ -23,7 +27,7 @@ # backpressure. If a connection gets stuck waiting in the backlog queue, then # from the peer's point of view the connection succeeded but then their # send/recv will stall until we get to it, possibly for a long time. OTOH if -# there isn't room in the backlog queue... then their connect stalls, possibly +# there isn't room in the backlog queue, then their connect stalls, possibly # for a long time, which is pretty much the same thing. # # A large backlog can also use a bit more kernel memory, but this seems fairly @@ -37,16 +41,24 @@ # so this is unnecessary -- we can just pass in "infinity" and get the maximum # that way. (Verified on Windows, Linux, macOS using # notes-to-self/measure-listen-backlog.py) -def _compute_backlog(backlog): - if backlog is None: - backlog = inf +def _compute_backlog(backlog: int | float | None) -> int: # Many systems (Linux, BSDs, ...) store the backlog in a uint16 and are # missing overflow protection, so we apply our own overflow protection. # https://github.com/golang/go/issues/5030 + if isinstance(backlog, float): + # TODO: Remove when removing infinity support + # https://github.com/python-trio/trio/pull/2724#discussion_r1278541729 + if backlog != inf: + raise ValueError(f"Only accepts infinity, not {backlog!r}") + backlog = None + if backlog is None: + return 0xFFFF return min(backlog, 0xFFFF) -async def open_tcp_listeners(port, *, host=None, backlog=None): +async def open_tcp_listeners( + port: int, *, host: str | bytes | None = None, backlog: int | float | None = None +) -> list[trio.SocketListener]: """Create :class:`SocketListener` objects to listen for TCP connections. Args: @@ -62,7 +74,7 @@ async def open_tcp_listeners(port, *, host=None, backlog=None): :func:`open_tcp_listeners` will bind to both the IPv4 wildcard address (``0.0.0.0``) and also the IPv6 wildcard address (``::``). - host (str, bytes-like, or None): The local interface to bind to. This is + host (str, bytes, or None): The local interface to bind to. This is passed to :func:`~socket.getaddrinfo` with the ``AI_PASSIVE`` flag set. @@ -78,13 +90,16 @@ async def open_tcp_listeners(port, *, host=None, backlog=None): all interfaces, pass the family-specific wildcard address: ``"0.0.0.0"`` for IPv4-only and ``"::"`` for IPv6-only. - backlog (int or None): The listen backlog to use. If you leave this as - ``None`` then Trio will pick a good default. (Currently: whatever + backlog (int, math.inf, or None): The listen backlog to use. If you leave this as + ``None`` or ``math.inf`` then Trio will pick a good default. (Currently: whatever your system has configured as the maximum backlog.) Returns: list of :class:`SocketListener` + Raises: + :class:`TypeError` if invalid arguments. + """ # getaddrinfo sometimes allows port=None, sometimes not (depending on # whether host=None). And on some systems it treats "" as 0, others it @@ -93,7 +108,7 @@ async def open_tcp_listeners(port, *, host=None, backlog=None): if not isinstance(port, int): raise TypeError(f"port must be an int not {port!r}") - backlog = _compute_backlog(backlog) + computed_backlog = _compute_backlog(backlog) addresses = await tsocket.getaddrinfo( host, port, type=tsocket.SOCK_STREAM, flags=tsocket.AI_PASSIVE @@ -126,7 +141,7 @@ async def open_tcp_listeners(port, *, host=None, backlog=None): sock.setsockopt(tsocket.IPPROTO_IPV6, tsocket.IPV6_V6ONLY, 1) await sock.bind(sockaddr) - sock.listen(backlog) + sock.listen(computed_backlog) listeners.append(trio.SocketListener(sock)) except: @@ -150,14 +165,14 @@ async def open_tcp_listeners(port, *, host=None, backlog=None): async def serve_tcp( - handler, - port, + handler: Callable[[trio.SocketStream], Awaitable[object]], + port: int, *, - host=None, - backlog=None, - handler_nursery=None, - task_status=trio.TASK_STATUS_IGNORED, -): + host: str | bytes | None = None, + backlog: int | float | None = None, + handler_nursery: trio.Nursery | None = None, + task_status: TaskStatus = trio.TASK_STATUS_IGNORED, # type: ignore[assignment] # default has type "_TaskStatusIgnored", argument has type "TaskStatus" +) -> None: """Listen for incoming TCP connections, and for each one start a task running ``handler(stream)``. diff --git a/trio/_tests/test_highlevel_open_tcp_listeners.py b/trio/_tests/test_highlevel_open_tcp_listeners.py index e58cbd13cc..6eca844f0c 100644 --- a/trio/_tests/test_highlevel_open_tcp_listeners.py +++ b/trio/_tests/test_highlevel_open_tcp_listeners.py @@ -1,6 +1,7 @@ import errno import socket as stdlib_socket import sys +from math import inf import attr import pytest @@ -289,6 +290,7 @@ async def test_open_tcp_listeners_backlog(): tsocket.set_custom_socket_factory(fsf) for given, expected in [ (None, 0xFFFF), + (inf, 0xFFFF), (99999999, 0xFFFF), (10, 10), (1, 1), @@ -297,3 +299,13 @@ async def test_open_tcp_listeners_backlog(): assert listeners for listener in listeners: assert listener.socket.backlog == expected + + +async def test_open_tcp_listeners_backlog_float_error(): + fsf = FakeSocketFactory(99) + tsocket.set_custom_socket_factory(fsf) + for should_fail in (0.0, 2.18, 3.14, 9.75): + with pytest.raises( + ValueError, match=f"Only accepts infinity, not {should_fail!r}" + ): + await open_tcp_listeners(0, backlog=should_fail) diff --git a/trio/_tests/verify_types.json b/trio/_tests/verify_types.json index ac2cfbd197..9c71ddd58a 100644 --- a/trio/_tests/verify_types.json +++ b/trio/_tests/verify_types.json @@ -7,11 +7,11 @@ "warningCount": 0 }, "typeCompleteness": { - "completenessScore": 0.9154704944178629, + "completenessScore": 0.9170653907496013, "exportedSymbolCounts": { "withAmbiguousType": 0, - "withKnownType": 574, - "withUnknownType": 53 + "withKnownType": 575, + "withUnknownType": 52 }, "ignoreUnknownTypesFromImports": true, "missingClassDocStringCount": 1, @@ -108,7 +108,6 @@ "trio.lowlevel.wait_writable", "trio.open_ssl_over_tcp_listeners", "trio.open_ssl_over_tcp_stream", - "trio.open_tcp_listeners", "trio.open_unix_socket", "trio.run", "trio.run_process", diff --git a/trio/lowlevel.py b/trio/lowlevel.py index 36d23d5955..c66e22b60e 100644 --- a/trio/lowlevel.py +++ b/trio/lowlevel.py @@ -15,6 +15,7 @@ RaiseCancelT as RaiseCancelT, RunVar as RunVar, Task as Task, + TaskStatus as TaskStatus, TrioToken as TrioToken, UnboundedQueue as UnboundedQueue, UnboundedQueueStatistics as UnboundedQueueStatistics,