From 1e7080a5b27f9789b9564af8b4966b17b78000db Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 17 Aug 2023 13:55:27 +0200
Subject: [PATCH 01/19] add types to io_windows
---
pyproject.toml | 2 +
trio/_core/_generated_io_windows.py | 12 +++---
trio/_core/_io_windows.py | 67 ++++++++++++++++-------------
3 files changed, 44 insertions(+), 37 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 073e2508d1..f0a9e94aa4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -53,9 +53,11 @@ module = [
"trio._core._entry_queue",
"trio._core._generated_io_epoll",
"trio._core._generated_io_kqueue",
+ "trio._core._generated_io_windows",
"trio._core._generated_run",
"trio._core._io_epoll",
"trio._core._io_kqueue",
+ "trio._core._io_windows",
"trio._core._local",
"trio._core._multierror",
"trio._core._run",
diff --git a/trio/_core/_generated_io_windows.py b/trio/_core/_generated_io_windows.py
index 7fa6fd5126..1114048845 100644
--- a/trio/_core/_generated_io_windows.py
+++ b/trio/_core/_generated_io_windows.py
@@ -14,7 +14,7 @@
assert not TYPE_CHECKING or sys.platform=="win32"
-async def wait_readable(sock):
+async def wait_readable(sock) ->None:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(sock)
@@ -22,7 +22,7 @@ async def wait_readable(sock):
raise RuntimeError("must be called from async context")
-async def wait_writable(sock):
+async def wait_writable(sock) ->None:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(sock)
@@ -30,7 +30,7 @@ async def wait_writable(sock):
raise RuntimeError("must be called from async context")
-def notify_closing(handle):
+def notify_closing(handle) ->None:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(handle)
@@ -38,7 +38,7 @@ def notify_closing(handle):
raise RuntimeError("must be called from async context")
-def register_with_iocp(handle):
+def register_with_iocp(handle) ->None:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return GLOBAL_RUN_CONTEXT.runner.io_manager.register_with_iocp(handle)
@@ -70,7 +70,7 @@ async def readinto_overlapped(handle, buffer, file_offset=0):
raise RuntimeError("must be called from async context")
-def current_iocp():
+def current_iocp() ->int:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return GLOBAL_RUN_CONTEXT.runner.io_manager.current_iocp()
@@ -78,7 +78,7 @@ def current_iocp():
raise RuntimeError("must be called from async context")
-def monitor_completion_key():
+def monitor_completion_key() ->ContextManager[tuple[int, UnboundedQueue[object]]]:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return GLOBAL_RUN_CONTEXT.runner.io_manager.monitor_completion_key()
diff --git a/trio/_core/_io_windows.py b/trio/_core/_io_windows.py
index 9757d25b5f..62ec9b1454 100644
--- a/trio/_core/_io_windows.py
+++ b/trio/_core/_io_windows.py
@@ -5,7 +5,7 @@
import socket
import sys
from contextlib import contextmanager
-from typing import TYPE_CHECKING, Literal
+from typing import TYPE_CHECKING, Iterator, Literal
import attr
from outcome import Value
@@ -33,6 +33,9 @@
if TYPE_CHECKING:
from typing_extensions import TypeAlias
+
+ from ._traps import Abort, RaiseCancelT
+ from ._unbounded_queue import UnboundedQueue
EventResult: TypeAlias = int
# There's a lot to be said about the overall design of a Windows event
@@ -185,13 +188,15 @@ class CKeys(enum.IntEnum):
USER_DEFINED = 4 # and above
-def _check(success):
+def _check(success: bool) -> Literal[True]:
if not success:
raise_winerror()
- return success
+ return True
-def _get_underlying_socket(sock, *, which=WSAIoctls.SIO_BASE_HANDLE):
+def _get_underlying_socket(
+ sock: socket.socket | int, *, which=WSAIoctls.SIO_BASE_HANDLE
+):
if hasattr(sock, "fileno"):
sock = sock.fileno()
base_ptr = ffi.new("HANDLE *")
@@ -336,9 +341,9 @@ def _afd_helper_handle():
# operation and start a new one.
@attr.s(slots=True, eq=False)
class AFDWaiters:
- read_task = attr.ib(default=None)
- write_task = attr.ib(default=None)
- current_op = attr.ib(default=None)
+ read_task: None = attr.ib(default=None)
+ write_task: None = attr.ib(default=None)
+ current_op: None = attr.ib(default=None)
# We also need to bundle up all the info for a single op into a standalone
@@ -346,10 +351,10 @@ class AFDWaiters:
# finishes, even if we're throwing it away.
@attr.s(slots=True, eq=False, frozen=True)
class AFDPollOp:
- lpOverlapped = attr.ib()
- poll_info = attr.ib()
- waiters = attr.ib()
- afd_group = attr.ib()
+ lpOverlapped: None = attr.ib()
+ poll_info: None = attr.ib()
+ waiters: None = attr.ib()
+ afd_group: None = attr.ib()
# The Windows kernel has a weird issue when using AFD handles. If you have N
@@ -365,8 +370,8 @@ class AFDPollOp:
@attr.s(slots=True, eq=False)
class AFDGroup:
- size = attr.ib()
- handle = attr.ib()
+ size: int = attr.ib()
+ handle: None = attr.ib()
@attr.s(slots=True, eq=False, frozen=True)
@@ -387,8 +392,8 @@ class _WindowsStatistics:
@attr.s(frozen=True)
class CompletionKeyEventInfo:
- lpOverlapped = attr.ib()
- dwNumberOfBytesTransferred = attr.ib()
+ lpOverlapped: None = attr.ib()
+ dwNumberOfBytesTransferred: int = attr.ib()
class WindowsIOManager:
@@ -455,7 +460,7 @@ def __init__(self):
"netsh winsock show catalog"
)
- def close(self):
+ def close(self) -> None:
try:
if self._iocp is not None:
iocp = self._iocp
@@ -466,10 +471,10 @@ def close(self):
afd_handle = self._all_afd_handles.pop()
_check(kernel32.CloseHandle(afd_handle))
- def __del__(self):
+ def __del__(self) -> None:
self.close()
- def statistics(self):
+ def statistics(self) -> _WindowsStatistics:
tasks_waiting_read = 0
tasks_waiting_write = 0
for waiter in self._afd_waiters.values():
@@ -484,7 +489,7 @@ def statistics(self):
completion_key_monitors=len(self._completion_key_queues),
)
- def force_wakeup(self):
+ def force_wakeup(self) -> None:
_check(
kernel32.PostQueuedCompletionStatus(
self._iocp, 0, CKeys.FORCE_WAKEUP, ffi.NULL
@@ -590,7 +595,7 @@ def process_events(self, received: EventResult) -> None:
)
queue.put_nowait(info)
- def _register_with_iocp(self, handle, completion_key):
+ def _register_with_iocp(self, handle, completion_key) -> None:
handle = _handle(handle)
_check(kernel32.CreateIoCompletionPort(handle, self._iocp, completion_key, 0))
# Supposedly this makes things slightly faster, by disabling the
@@ -607,7 +612,7 @@ def _register_with_iocp(self, handle, completion_key):
# AFD stuff
################################################################
- def _refresh_afd(self, base_handle):
+ def _refresh_afd(self, base_handle) -> None:
waiters = self._afd_waiters[base_handle]
if waiters.current_op is not None:
afd_group = waiters.current_op.afd_group
@@ -683,7 +688,7 @@ def _refresh_afd(self, base_handle):
if afd_group.size >= MAX_AFD_GROUP_SIZE:
self._vacant_afd_groups.remove(afd_group)
- async def _afd_poll(self, sock, mode):
+ async def _afd_poll(self, sock, mode) -> None:
base_handle = _get_base_socket(sock)
waiters = self._afd_waiters.get(base_handle)
if waiters is None:
@@ -696,7 +701,7 @@ async def _afd_poll(self, sock, mode):
# we let it escape.
self._refresh_afd(base_handle)
- def abort_fn(_):
+ def abort_fn(_: RaiseCancelT) -> Abort:
setattr(waiters, mode, None)
self._refresh_afd(base_handle)
return _core.Abort.SUCCEEDED
@@ -704,15 +709,15 @@ def abort_fn(_):
await _core.wait_task_rescheduled(abort_fn)
@_public
- async def wait_readable(self, sock):
+ async def wait_readable(self, sock) -> None:
await self._afd_poll(sock, "read_task")
@_public
- async def wait_writable(self, sock):
+ async def wait_writable(self, sock) -> None:
await self._afd_poll(sock, "write_task")
@_public
- def notify_closing(self, handle):
+ def notify_closing(self, handle) -> None:
handle = _get_base_socket(handle)
waiters = self._afd_waiters.get(handle)
if waiters is not None:
@@ -724,7 +729,7 @@ def notify_closing(self, handle):
################################################################
@_public
- def register_with_iocp(self, handle):
+ def register_with_iocp(self, handle) -> None:
self._register_with_iocp(handle, CKeys.WAIT_OVERLAPPED)
@_public
@@ -740,7 +745,7 @@ async def wait_overlapped(self, handle, lpOverlapped):
self._overlapped_waiters[lpOverlapped] = task
raise_cancel = None
- def abort(raise_cancel_):
+ def abort(raise_cancel_: RaiseCancelT) -> Abort:
nonlocal raise_cancel
raise_cancel = raise_cancel_
try:
@@ -860,14 +865,14 @@ def submit_read(lpOverlapped):
################################################################
@_public
- def current_iocp(self):
+ def current_iocp(self) -> int:
return int(ffi.cast("uintptr_t", self._iocp))
@contextmanager
@_public
- def monitor_completion_key(self):
+ def monitor_completion_key(self) -> Iterator[tuple[int, UnboundedQueue[object]]]:
key = next(self._completion_key_counter)
- queue = _core.UnboundedQueue()
+ queue = _core.UnboundedQueue[object]()
self._completion_key_queues[key] = queue
try:
yield (key, queue)
From d5d871b887df6e634c9ef72a0e8ace4cda81b62b Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 17 Aug 2023 14:03:15 +0200
Subject: [PATCH 02/19] add changes to gen_exports
---
trio/_tools/gen_exports.py | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/trio/_tools/gen_exports.py b/trio/_tools/gen_exports.py
index 9d78cd5bd7..43ed8b8bb8 100755
--- a/trio/_tools/gen_exports.py
+++ b/trio/_tools/gen_exports.py
@@ -235,7 +235,12 @@ def main() -> None: # pragma: no cover
"runner.instruments",
imports=IMPORTS_INSTRUMENT,
),
- File(core / "_io_windows.py", "runner.io_manager", platform="win32"),
+ File(
+ core / "_io_windows.py",
+ "runner.io_manager",
+ platform="win32",
+ imports=IMPORTS_WINDOWS,
+ ),
File(
core / "_io_epoll.py",
"runner.io_manager",
@@ -285,6 +290,13 @@ def main() -> None: # pragma: no cover
"""
+IMPORTS_WINDOWS = """\
+from typing import TYPE_CHECKING, ContextManager
+
+if TYPE_CHECKING:
+ from ._unbounded_queue import UnboundedQueue
+"""
+
if __name__ == "__main__": # pragma: no cover
main()
From 09abd4935fb947afed58c21b214eebb6d3231655 Mon Sep 17 00:00:00 2001
From: Spencer Brown
Date: Tue, 22 Aug 2023 08:42:55 +1000
Subject: [PATCH 03/19] Statically type some Windows CFFI functions
---
trio/_core/_windows_cffi.py | 130 +++++++++++++++++++++++++++++++++---
trio/_wait_for_object.py | 3 +-
2 files changed, 124 insertions(+), 9 deletions(-)
diff --git a/trio/_core/_windows_cffi.py b/trio/_core/_windows_cffi.py
index a65a332c2f..76e1edf3a7 100644
--- a/trio/_core/_windows_cffi.py
+++ b/trio/_core/_windows_cffi.py
@@ -2,7 +2,7 @@
import enum
import re
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, NewType, Protocol, cast
if TYPE_CHECKING:
from typing_extensions import NoReturn, TypeAlias
@@ -222,12 +222,121 @@
LIB = re.sub(r"\bPASCAL\b", "__stdcall", LIB)
ffi = cffi.api.FFI()
-CData: TypeAlias = cffi.api.FFI.CData
ffi.cdef(LIB)
-kernel32 = ffi.dlopen("kernel32.dll")
-ntdll = ffi.dlopen("ntdll.dll")
-ws2_32 = ffi.dlopen("ws2_32.dll")
+CData: TypeAlias = cffi.api.FFI.CData
+CType: TypeAlias = cffi.api.FFI.CType
+AlwaysNull: TypeAlias = CType # We currently always pass ffi.NULL here.
+Handle = NewType("Handle", CData)
+HandleArray = NewType("HandleArray", CData)
+
+
+class _Kernel32(Protocol):
+ """Statically typed version of the kernel32.dll functions we use."""
+
+ def CreateIoCompletionPort(
+ self,
+ FileHandle: Handle,
+ ExistingCompletionPort: CData,
+ CompletionKey: int,
+ NumberOfConcurrentThreads: int,
+ /,
+ ) -> CData:
+ ...
+
+ def CreateEventA(
+ self,
+ lpEventAttributes: AlwaysNull,
+ bManualReset: bool,
+ bInitialState: bool,
+ lpName: AlwaysNull,
+ /,
+ ) -> Handle:
+ ...
+
+ def SetFileCompletionNotificationModes(
+ self, handle: Handle, flags: CompletionModes, /
+ ) -> int:
+ ...
+
+ def GetQueuedCompletionStatusEx(
+ self,
+ CompletionPort: CData,
+ lpCompletionPortEntries: CData,
+ ulCount: int,
+ ulNumEntriesRemoved: CData,
+ dwMilliseconds: int,
+ fAlertable: bool | int,
+ /,
+ ) -> CData:
+ ...
+
+ def CreateFileW(
+ self,
+ lpFileName: CData,
+ dwDesiredAccess: FileFlags,
+ dwShareMode: FileFlags,
+ lpSecurityAttributes: AlwaysNull,
+ dwCreationDisposition: FileFlags,
+ dwFlagsAndAttributes: FileFlags,
+ hTemplateFile: AlwaysNull,
+ /,
+ ) -> Handle:
+ ...
+
+ def WaitForSingleObject(self, hHandle: Handle, dwMilliseconds: int, /) -> CData:
+ ...
+
+ def WaitForMultipleObjects(
+ self,
+ nCount: int,
+ lpHandles: HandleArray,
+ bWaitAll: bool,
+ dwMilliseconds: int,
+ /,
+ ) -> ErrorCodes:
+ ...
+
+ def SetEvent(self, handle: Handle, /) -> None:
+ ...
+
+ def CloseHandle(self, handle: Handle, /) -> None:
+ ...
+
+
+class _Nt(Protocol):
+ """Statically typed version of the dtdll.dll functions we use."""
+
+ def RtlNtStatusToDosError(self, status: int, /) -> ErrorCodes:
+ ...
+
+
+class _Ws2(Protocol):
+ """Statically typed version of the ws2_32.dll functions we use."""
+
+ def WSAGetLastError(self) -> int:
+ ...
+
+ def WSAIoctl(
+ self,
+ socket: CData,
+ dwIoControlCode: WSAIoctls,
+ lpvInBuffer: AlwaysNull,
+ cbInBuffer: int,
+ lpvOutBuffer: CData,
+ cbOutBuffer: int,
+ lpcbBytesReturned: int,
+ lpOverlapped: AlwaysNull,
+ # actually LPWSAOVERLAPPED_COMPLETION_ROUTINE
+ lpCompletionRoutine: AlwaysNull,
+ /,
+ ) -> int:
+ ...
+
+
+kernel32 = cast(_Kernel32, ffi.dlopen("kernel32.dll"))
+ntdll = cast(_Nt, ffi.dlopen("ntdll.dll"))
+ws2_32 = cast(_Ws2, ffi.dlopen("ws2_32.dll"))
################################################################
# Magic numbers
@@ -309,7 +418,7 @@ class IoControlCodes(enum.IntEnum):
################################################################
-def _handle(obj: int | CData) -> CData:
+def _handle(obj: int | CData) -> Handle:
# For now, represent handles as either cffi HANDLEs or as ints. If you
# try to pass in a file descriptor instead, it's not going to work
# out. (For that msvcrt.get_osfhandle does the trick, but I don't know if
@@ -317,8 +426,13 @@ def _handle(obj: int | CData) -> CData:
# matter, Python never allocates an fd. So let's wait until we actually
# encounter the problem before worrying about it.
if isinstance(obj, int):
- return ffi.cast("HANDLE", obj)
- return obj
+ return Handle(ffi.cast("HANDLE", obj))
+ return Handle(obj)
+
+
+def handle_array(count: int) -> HandleArray:
+ """Make an array of handles."""
+ return HandleArray(ffi.new(f"HANDLE[{count}]"))
def raise_winerror(
diff --git a/trio/_wait_for_object.py b/trio/_wait_for_object.py
index 50a9d13ff2..d2193d9c86 100644
--- a/trio/_wait_for_object.py
+++ b/trio/_wait_for_object.py
@@ -9,6 +9,7 @@
ErrorCodes,
_handle,
ffi,
+ handle_array,
kernel32,
raise_winerror,
)
@@ -57,7 +58,7 @@ async def WaitForSingleObject(obj: int | CData) -> None:
def WaitForMultipleObjects_sync(*handles: int | CData) -> None:
"""Wait for any of the given Windows handles to be signaled."""
n = len(handles)
- handle_arr = ffi.new(f"HANDLE[{n}]")
+ handle_arr = handle_array(n)
for i in range(n):
handle_arr[i] = handles[i]
timeout = 0xFFFFFFFF # INFINITE
From 745c02a273385ebf37667ae47d9a855ea3d920f3 Mon Sep 17 00:00:00 2001
From: Spencer Brown
Date: Tue, 22 Aug 2023 08:44:09 +1000
Subject: [PATCH 04/19] This accepts any value, not bools
---
trio/_core/_io_windows.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/trio/_core/_io_windows.py b/trio/_core/_io_windows.py
index 62ec9b1454..74f95aa273 100644
--- a/trio/_core/_io_windows.py
+++ b/trio/_core/_io_windows.py
@@ -5,7 +5,7 @@
import socket
import sys
from contextlib import contextmanager
-from typing import TYPE_CHECKING, Iterator, Literal
+from typing import TYPE_CHECKING, Iterator, Literal, TypeVar
import attr
from outcome import Value
@@ -36,7 +36,9 @@
from ._traps import Abort, RaiseCancelT
from ._unbounded_queue import UnboundedQueue
+
EventResult: TypeAlias = int
+T = TypeVar("T")
# There's a lot to be said about the overall design of a Windows event
# loop. See
@@ -188,10 +190,10 @@ class CKeys(enum.IntEnum):
USER_DEFINED = 4 # and above
-def _check(success: bool) -> Literal[True]:
+def _check(success: T) -> T:
if not success:
raise_winerror()
- return True
+ return success
def _get_underlying_socket(
From 282dd8120a87cc3d33ce4e1667b675c9cfedc762 Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Sat, 26 Aug 2023 01:14:33 +0900
Subject: [PATCH 05/19] Update some configuration
---
pyproject.toml | 4 ----
test-requirements.in | 1 +
test-requirements.txt | 3 +++
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 184b46056d..aadaf8c6f1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -60,10 +60,6 @@ disallow_untyped_calls = false
# files not yet fully typed
[[tool.mypy.overrides]]
module = [
-# 2761
-"trio/_core/_generated_io_windows",
-"trio/_core/_io_windows",
-
"trio/_signals",
# internal
diff --git a/test-requirements.in b/test-requirements.in
index 1911b1bf11..20eae50aa5 100644
--- a/test-requirements.in
+++ b/test-requirements.in
@@ -23,6 +23,7 @@ codespell
# https://github.com/python-trio/trio/pull/654#issuecomment-420518745
mypy-extensions; implementation_name == "cpython"
typing-extensions
+types-cffi; os_name == "nt" and implementation_name == "cpython"
# Trio's own dependencies
cffi; os_name == "nt"
diff --git a/test-requirements.txt b/test-requirements.txt
index 73d94f09c1..e0bdb01eae 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -24,6 +24,7 @@ build==0.10.0
# via pip-tools
cffi==1.15.1
# via cryptography
+ # via -r test-requirements.in
click==8.1.6
# via
# black
@@ -158,6 +159,8 @@ traitlets==5.9.0
# matplotlib-inline
trustme==1.1.0
# via -r test-requirements.in
+types-cffi==1.15.1.15 ; os_name == "nt" and implementation_name == "cpython"
+ # via -r test-requirements.in
types-pyopenssl==23.2.0.2 ; implementation_name == "cpython"
# via -r test-requirements.in
typing-extensions==4.7.1
From c847fe84a72dc7d08290dc01678b08f1a03c1305 Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Sat, 26 Aug 2023 07:11:57 +0900
Subject: [PATCH 06/19] Type the stuff
---
pyproject.toml | 1 +
trio/_core/_generated_instrumentation.py | 100 ++---
trio/_core/_generated_io_epoll.py | 84 ++--
trio/_core/_generated_io_kqueue.py | 152 +++----
trio/_core/_generated_io_windows.py | 188 +++++----
trio/_core/_generated_run.py | 508 +++++++++++------------
trio/_core/_io_common.py | 3 +-
trio/_core/_io_windows.py | 109 +++--
trio/_core/_windows_cffi.py | 89 +++-
trio/_tests/check_type_completeness.py | 15 +-
trio/_tools/gen_exports.py | 4 +
11 files changed, 689 insertions(+), 564 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 73173e023c..4a64e70b80 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -82,6 +82,7 @@ module = [
"trio/_core/_tests/test_thread_cache",
"trio/_core/_tests/test_tutil",
"trio/_core/_tests/test_unbounded_queue",
+"trio/_core/_tests/test_windows",
"trio/_core/_tests/tutil",
"trio/_tests/pytest_plugin",
"trio/_tests/test_abc",
diff --git a/trio/_core/_generated_instrumentation.py b/trio/_core/_generated_instrumentation.py
index 605a6372f2..cc6b6335e0 100644
--- a/trio/_core/_generated_instrumentation.py
+++ b/trio/_core/_generated_instrumentation.py
@@ -1,50 +1,50 @@
-# ***********************************************************
-# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
-# *************************************************************
-# Don't lint this file, generation will not format this too nicely.
-# isort: skip_file
-# fmt: off
-from __future__ import annotations
-
-from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import GLOBAL_RUN_CONTEXT
-from ._instrumentation import Instrument
-
-
-def add_instrument(instrument: Instrument) ->None:
- """Start instrumenting the current run loop with the given instrument.
-
- Args:
- instrument (trio.abc.Instrument): The instrument to activate.
-
- If ``instrument`` is already active, does nothing.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.instruments.add_instrument(instrument)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def remove_instrument(instrument: Instrument) ->None:
- """Stop instrumenting the current run loop with the given instrument.
-
- Args:
- instrument (trio.abc.Instrument): The instrument to de-activate.
-
- Raises:
- KeyError: if the instrument is not currently active. This could
- occur either because you never added it, or because you added it
- and then it raised an unhandled exception and was automatically
- deactivated.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.instruments.remove_instrument(instrument)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-# fmt: on
+# ***********************************************************
+# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
+# *************************************************************
+# Don't lint this file, generation will not format this too nicely.
+# isort: skip_file
+# fmt: off
+from __future__ import annotations
+
+from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
+from ._run import GLOBAL_RUN_CONTEXT
+from ._instrumentation import Instrument
+
+
+def add_instrument(instrument: Instrument) ->None:
+ """Start instrumenting the current run loop with the given instrument.
+
+ Args:
+ instrument (trio.abc.Instrument): The instrument to activate.
+
+ If ``instrument`` is already active, does nothing.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.instruments.add_instrument(instrument)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def remove_instrument(instrument: Instrument) ->None:
+ """Stop instrumenting the current run loop with the given instrument.
+
+ Args:
+ instrument (trio.abc.Instrument): The instrument to de-activate.
+
+ Raises:
+ KeyError: if the instrument is not currently active. This could
+ occur either because you never added it, or because you added it
+ and then it raised an unhandled exception and was automatically
+ deactivated.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.instruments.remove_instrument(instrument)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+# fmt: on
diff --git a/trio/_core/_generated_io_epoll.py b/trio/_core/_generated_io_epoll.py
index abe49ed3ff..68cb3c6001 100644
--- a/trio/_core/_generated_io_epoll.py
+++ b/trio/_core/_generated_io_epoll.py
@@ -1,42 +1,42 @@
-# ***********************************************************
-# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
-# *************************************************************
-# Don't lint this file, generation will not format this too nicely.
-# isort: skip_file
-# fmt: off
-from __future__ import annotations
-
-from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import GLOBAL_RUN_CONTEXT
-from socket import socket
-from typing import TYPE_CHECKING
-import sys
-
-assert not TYPE_CHECKING or sys.platform=="linux"
-
-
-async def wait_readable(fd: (int | socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(fd)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_writable(fd: (int | socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(fd)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def notify_closing(fd: (int | socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(fd)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-# fmt: on
+# ***********************************************************
+# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
+# *************************************************************
+# Don't lint this file, generation will not format this too nicely.
+# isort: skip_file
+# fmt: off
+from __future__ import annotations
+
+from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
+from ._run import GLOBAL_RUN_CONTEXT
+from socket import socket
+from typing import TYPE_CHECKING
+import sys
+
+assert not TYPE_CHECKING or sys.platform=="linux"
+
+
+async def wait_readable(fd: (int | socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(fd)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_writable(fd: (int | socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(fd)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def notify_closing(fd: (int | socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(fd)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+# fmt: on
diff --git a/trio/_core/_generated_io_kqueue.py b/trio/_core/_generated_io_kqueue.py
index cfcf6354c7..e1348f0915 100644
--- a/trio/_core/_generated_io_kqueue.py
+++ b/trio/_core/_generated_io_kqueue.py
@@ -1,76 +1,76 @@
-# ***********************************************************
-# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
-# *************************************************************
-# Don't lint this file, generation will not format this too nicely.
-# isort: skip_file
-# fmt: off
-from __future__ import annotations
-
-from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import GLOBAL_RUN_CONTEXT
-from typing import Callable, ContextManager, TYPE_CHECKING
-
-if TYPE_CHECKING:
- import select
- from socket import socket
-
- from ._traps import Abort, RaiseCancelT
-
- from .. import _core
-
-import sys
-
-assert not TYPE_CHECKING or sys.platform=="darwin"
-
-
-def current_kqueue() ->select.kqueue:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.current_kqueue()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def monitor_kevent(ident: int, filter: int) ->ContextManager[_core.UnboundedQueue
- [select.kevent]]:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.monitor_kevent(ident, filter)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_kevent(ident: int, filter: int, abort_func: Callable[[
- RaiseCancelT], Abort]) ->Abort:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_kevent(ident, filter, abort_func)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_readable(fd: (int | socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(fd)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_writable(fd: (int | socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(fd)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def notify_closing(fd: (int | socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(fd)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-# fmt: on
+# ***********************************************************
+# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
+# *************************************************************
+# Don't lint this file, generation will not format this too nicely.
+# isort: skip_file
+# fmt: off
+from __future__ import annotations
+
+from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
+from ._run import GLOBAL_RUN_CONTEXT
+from typing import Callable, ContextManager, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ import select
+ from socket import socket
+
+ from ._traps import Abort, RaiseCancelT
+
+ from .. import _core
+
+import sys
+
+assert not TYPE_CHECKING or sys.platform=="darwin"
+
+
+def current_kqueue() ->select.kqueue:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.current_kqueue()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def monitor_kevent(ident: int, filter: int) ->ContextManager[_core.UnboundedQueue
+ [select.kevent]]:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.monitor_kevent(ident, filter)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_kevent(ident: int, filter: int, abort_func: Callable[[
+ RaiseCancelT], Abort]) ->Abort:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_kevent(ident, filter, abort_func)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_readable(fd: (int | socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(fd)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_writable(fd: (int | socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(fd)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def notify_closing(fd: (int | socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(fd)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+# fmt: on
diff --git a/trio/_core/_generated_io_windows.py b/trio/_core/_generated_io_windows.py
index 1114048845..eb2a1f3b43 100644
--- a/trio/_core/_generated_io_windows.py
+++ b/trio/_core/_generated_io_windows.py
@@ -1,89 +1,99 @@
-# ***********************************************************
-# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
-# *************************************************************
-# Don't lint this file, generation will not format this too nicely.
-# isort: skip_file
-# fmt: off
-from __future__ import annotations
-
-from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import GLOBAL_RUN_CONTEXT
-from typing import TYPE_CHECKING
-import sys
-
-assert not TYPE_CHECKING or sys.platform=="win32"
-
-
-async def wait_readable(sock) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(sock)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_writable(sock) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(sock)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def notify_closing(handle) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(handle)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def register_with_iocp(handle) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.register_with_iocp(handle)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_overlapped(handle, lpOverlapped):
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_overlapped(handle, lpOverlapped)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def write_overlapped(handle, data, file_offset=0):
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.write_overlapped(handle, data, file_offset)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def readinto_overlapped(handle, buffer, file_offset=0):
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.readinto_overlapped(handle, buffer, file_offset)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def current_iocp() ->int:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.current_iocp()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def monitor_completion_key() ->ContextManager[tuple[int, UnboundedQueue[object]]]:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.monitor_completion_key()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-# fmt: on
+# ***********************************************************
+# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
+# *************************************************************
+# Don't lint this file, generation will not format this too nicely.
+# isort: skip_file
+# fmt: off
+from __future__ import annotations
+
+from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
+from ._run import GLOBAL_RUN_CONTEXT
+from typing import TYPE_CHECKING, ContextManager
+
+if TYPE_CHECKING:
+ import socket
+ from ._windows_cffi import Handle, CData
+ from typing_extensions import Buffer
+
+ from ._unbounded_queue import UnboundedQueue
+import sys
+
+assert not TYPE_CHECKING or sys.platform=="win32"
+
+
+async def wait_readable(sock: (socket.socket | int)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(sock)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_writable(sock: (socket.socket | int)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(sock)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def notify_closing(handle: (Handle | int | socket.socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(handle)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def register_with_iocp(handle: (int | CData)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.register_with_iocp(handle)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_overlapped(handle_: (int | CData), lpOverlapped: (CData | int)
+ ) ->object:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_overlapped(handle_, lpOverlapped)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def write_overlapped(handle: (int | CData), data: Buffer, file_offset:
+ int=0) ->int:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.write_overlapped(handle, data, file_offset)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def readinto_overlapped(handle: (int | CData), buffer: Buffer,
+ file_offset: int=0) ->int:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.readinto_overlapped(handle, buffer, file_offset)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def current_iocp() ->int:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.current_iocp()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def monitor_completion_key() ->ContextManager[tuple[int, UnboundedQueue[object]]]:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.monitor_completion_key()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+# fmt: on
diff --git a/trio/_core/_generated_run.py b/trio/_core/_generated_run.py
index bd5abbd639..ef2055b5d8 100644
--- a/trio/_core/_generated_run.py
+++ b/trio/_core/_generated_run.py
@@ -1,254 +1,254 @@
-# ***********************************************************
-# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
-# *************************************************************
-# Don't lint this file, generation will not format this too nicely.
-# isort: skip_file
-# fmt: off
-from __future__ import annotations
-
-from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import GLOBAL_RUN_CONTEXT
-from collections.abc import Awaitable, Callable
-from typing import Any
-
-from outcome import Outcome
-import contextvars
-
-from ._run import _NO_SEND, RunStatistics, Task
-from ._entry_queue import TrioToken
-from .._abc import Clock
-
-
-def current_statistics() ->RunStatistics:
- """Returns ``RunStatistics``, which contains run-loop-level debugging information.
-
- Currently, the following fields are defined:
-
- * ``tasks_living`` (int): The number of tasks that have been spawned
- and not yet exited.
- * ``tasks_runnable`` (int): The number of tasks that are currently
- queued on the run queue (as opposed to blocked waiting for something
- to happen).
- * ``seconds_to_next_deadline`` (float): The time until the next
- pending cancel scope deadline. May be negative if the deadline has
- expired but we haven't yet processed cancellations. May be
- :data:`~math.inf` if there are no pending deadlines.
- * ``run_sync_soon_queue_size`` (int): The number of
- unprocessed callbacks queued via
- :meth:`trio.lowlevel.TrioToken.run_sync_soon`.
- * ``io_statistics`` (object): Some statistics from Trio's I/O
- backend. This always has an attribute ``backend`` which is a string
- naming which operating-system-specific I/O backend is in use; the
- other attributes vary between backends.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.current_statistics()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def current_time() ->float:
- """Returns the current time according to Trio's internal clock.
-
- Returns:
- float: The current time.
-
- Raises:
- RuntimeError: if not inside a call to :func:`trio.run`.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.current_time()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def current_clock() ->Clock:
- """Returns the current :class:`~trio.abc.Clock`."""
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.current_clock()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def current_root_task() ->(Task | None):
- """Returns the current root :class:`Task`.
-
- This is the task that is the ultimate parent of all other tasks.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.current_root_task()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def reschedule(task: Task, next_send: Outcome[Any]=_NO_SEND) ->None:
- """Reschedule the given task with the given
- :class:`outcome.Outcome`.
-
- See :func:`wait_task_rescheduled` for the gory details.
-
- There must be exactly one call to :func:`reschedule` for every call to
- :func:`wait_task_rescheduled`. (And when counting, keep in mind that
- returning :data:`Abort.SUCCEEDED` from an abort callback is equivalent
- to calling :func:`reschedule` once.)
-
- Args:
- task (trio.lowlevel.Task): the task to be rescheduled. Must be blocked
- in a call to :func:`wait_task_rescheduled`.
- next_send (outcome.Outcome): the value (or error) to return (or
- raise) from :func:`wait_task_rescheduled`.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.reschedule(task, next_send)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def spawn_system_task(async_fn: Callable[..., Awaitable[object]], *args:
- object, name: object=None, context: (contextvars.Context | None)=None
- ) ->Task:
- """Spawn a "system" task.
-
- System tasks have a few differences from regular tasks:
-
- * They don't need an explicit nursery; instead they go into the
- internal "system nursery".
-
- * If a system task raises an exception, then it's converted into a
- :exc:`~trio.TrioInternalError` and *all* tasks are cancelled. If you
- write a system task, you should be careful to make sure it doesn't
- crash.
-
- * System tasks are automatically cancelled when the main task exits.
-
- * By default, system tasks have :exc:`KeyboardInterrupt` protection
- *enabled*. If you want your task to be interruptible by control-C,
- then you need to use :func:`disable_ki_protection` explicitly (and
- come up with some plan for what to do with a
- :exc:`KeyboardInterrupt`, given that system tasks aren't allowed to
- raise exceptions).
-
- * System tasks do not inherit context variables from their creator.
-
- Towards the end of a call to :meth:`trio.run`, after the main
- task and all system tasks have exited, the system nursery
- becomes closed. At this point, new calls to
- :func:`spawn_system_task` will raise ``RuntimeError("Nursery
- is closed to new arrivals")`` instead of creating a system
- task. It's possible to encounter this state either in
- a ``finally`` block in an async generator, or in a callback
- passed to :meth:`TrioToken.run_sync_soon` at the right moment.
-
- Args:
- async_fn: An async callable.
- args: Positional arguments for ``async_fn``. If you want to pass
- keyword arguments, use :func:`functools.partial`.
- name: The name for this task. Only used for debugging/introspection
- (e.g. ``repr(task_obj)``). If this isn't a string,
- :func:`spawn_system_task` will try to make it one. A common use
- case is if you're wrapping a function before spawning a new
- task, you might pass the original function as the ``name=`` to
- make debugging easier.
- context: An optional ``contextvars.Context`` object with context variables
- to use for this task. You would normally get a copy of the current
- context with ``context = contextvars.copy_context()`` and then you would
- pass that ``context`` object here.
-
- Returns:
- Task: the newly spawned task
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.spawn_system_task(async_fn, *args, name=name, context=context)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def current_trio_token() ->TrioToken:
- """Retrieve the :class:`TrioToken` for the current call to
- :func:`trio.run`.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.current_trio_token()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_all_tasks_blocked(cushion: float=0.0) ->None:
- """Block until there are no runnable tasks.
-
- This is useful in testing code when you want to give other tasks a
- chance to "settle down". The calling task is blocked, and doesn't wake
- up until all other tasks are also blocked for at least ``cushion``
- seconds. (Setting a non-zero ``cushion`` is intended to handle cases
- like two tasks talking to each other over a local socket, where we
- want to ignore the potential brief moment between a send and receive
- when all tasks are blocked.)
-
- Note that ``cushion`` is measured in *real* time, not the Trio clock
- time.
-
- If there are multiple tasks blocked in :func:`wait_all_tasks_blocked`,
- then the one with the shortest ``cushion`` is the one woken (and
- this task becoming unblocked resets the timers for the remaining
- tasks). If there are multiple tasks that have exactly the same
- ``cushion``, then all are woken.
-
- You should also consider :class:`trio.testing.Sequencer`, which
- provides a more explicit way to control execution ordering within a
- test, and will often produce more readable tests.
-
- Example:
- Here's an example of one way to test that Trio's locks are fair: we
- take the lock in the parent, start a child, wait for the child to be
- blocked waiting for the lock (!), and then check that we can't
- release and immediately re-acquire the lock::
-
- async def lock_taker(lock):
- await lock.acquire()
- lock.release()
-
- async def test_lock_fairness():
- lock = trio.Lock()
- await lock.acquire()
- async with trio.open_nursery() as nursery:
- nursery.start_soon(lock_taker, lock)
- # child hasn't run yet, we have the lock
- assert lock.locked()
- assert lock._owner is trio.lowlevel.current_task()
- await trio.testing.wait_all_tasks_blocked()
- # now the child has run and is blocked on lock.acquire(), we
- # still have the lock
- assert lock.locked()
- assert lock._owner is trio.lowlevel.current_task()
- lock.release()
- try:
- # The child has a prior claim, so we can't have it
- lock.acquire_nowait()
- except trio.WouldBlock:
- assert lock._owner is not trio.lowlevel.current_task()
- print("PASS")
- else:
- print("FAIL")
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.wait_all_tasks_blocked(cushion)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-# fmt: on
+# ***********************************************************
+# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
+# *************************************************************
+# Don't lint this file, generation will not format this too nicely.
+# isort: skip_file
+# fmt: off
+from __future__ import annotations
+
+from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
+from ._run import GLOBAL_RUN_CONTEXT
+from collections.abc import Awaitable, Callable
+from typing import Any
+
+from outcome import Outcome
+import contextvars
+
+from ._run import _NO_SEND, RunStatistics, Task
+from ._entry_queue import TrioToken
+from .._abc import Clock
+
+
+def current_statistics() ->RunStatistics:
+ """Returns ``RunStatistics``, which contains run-loop-level debugging information.
+
+ Currently, the following fields are defined:
+
+ * ``tasks_living`` (int): The number of tasks that have been spawned
+ and not yet exited.
+ * ``tasks_runnable`` (int): The number of tasks that are currently
+ queued on the run queue (as opposed to blocked waiting for something
+ to happen).
+ * ``seconds_to_next_deadline`` (float): The time until the next
+ pending cancel scope deadline. May be negative if the deadline has
+ expired but we haven't yet processed cancellations. May be
+ :data:`~math.inf` if there are no pending deadlines.
+ * ``run_sync_soon_queue_size`` (int): The number of
+ unprocessed callbacks queued via
+ :meth:`trio.lowlevel.TrioToken.run_sync_soon`.
+ * ``io_statistics`` (object): Some statistics from Trio's I/O
+ backend. This always has an attribute ``backend`` which is a string
+ naming which operating-system-specific I/O backend is in use; the
+ other attributes vary between backends.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.current_statistics()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def current_time() ->float:
+ """Returns the current time according to Trio's internal clock.
+
+ Returns:
+ float: The current time.
+
+ Raises:
+ RuntimeError: if not inside a call to :func:`trio.run`.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.current_time()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def current_clock() ->Clock:
+ """Returns the current :class:`~trio.abc.Clock`."""
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.current_clock()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def current_root_task() ->(Task | None):
+ """Returns the current root :class:`Task`.
+
+ This is the task that is the ultimate parent of all other tasks.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.current_root_task()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def reschedule(task: Task, next_send: Outcome[Any]=_NO_SEND) ->None:
+ """Reschedule the given task with the given
+ :class:`outcome.Outcome`.
+
+ See :func:`wait_task_rescheduled` for the gory details.
+
+ There must be exactly one call to :func:`reschedule` for every call to
+ :func:`wait_task_rescheduled`. (And when counting, keep in mind that
+ returning :data:`Abort.SUCCEEDED` from an abort callback is equivalent
+ to calling :func:`reschedule` once.)
+
+ Args:
+ task (trio.lowlevel.Task): the task to be rescheduled. Must be blocked
+ in a call to :func:`wait_task_rescheduled`.
+ next_send (outcome.Outcome): the value (or error) to return (or
+ raise) from :func:`wait_task_rescheduled`.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.reschedule(task, next_send)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def spawn_system_task(async_fn: Callable[..., Awaitable[object]], *args:
+ object, name: object=None, context: (contextvars.Context | None)=None
+ ) ->Task:
+ """Spawn a "system" task.
+
+ System tasks have a few differences from regular tasks:
+
+ * They don't need an explicit nursery; instead they go into the
+ internal "system nursery".
+
+ * If a system task raises an exception, then it's converted into a
+ :exc:`~trio.TrioInternalError` and *all* tasks are cancelled. If you
+ write a system task, you should be careful to make sure it doesn't
+ crash.
+
+ * System tasks are automatically cancelled when the main task exits.
+
+ * By default, system tasks have :exc:`KeyboardInterrupt` protection
+ *enabled*. If you want your task to be interruptible by control-C,
+ then you need to use :func:`disable_ki_protection` explicitly (and
+ come up with some plan for what to do with a
+ :exc:`KeyboardInterrupt`, given that system tasks aren't allowed to
+ raise exceptions).
+
+ * System tasks do not inherit context variables from their creator.
+
+ Towards the end of a call to :meth:`trio.run`, after the main
+ task and all system tasks have exited, the system nursery
+ becomes closed. At this point, new calls to
+ :func:`spawn_system_task` will raise ``RuntimeError("Nursery
+ is closed to new arrivals")`` instead of creating a system
+ task. It's possible to encounter this state either in
+ a ``finally`` block in an async generator, or in a callback
+ passed to :meth:`TrioToken.run_sync_soon` at the right moment.
+
+ Args:
+ async_fn: An async callable.
+ args: Positional arguments for ``async_fn``. If you want to pass
+ keyword arguments, use :func:`functools.partial`.
+ name: The name for this task. Only used for debugging/introspection
+ (e.g. ``repr(task_obj)``). If this isn't a string,
+ :func:`spawn_system_task` will try to make it one. A common use
+ case is if you're wrapping a function before spawning a new
+ task, you might pass the original function as the ``name=`` to
+ make debugging easier.
+ context: An optional ``contextvars.Context`` object with context variables
+ to use for this task. You would normally get a copy of the current
+ context with ``context = contextvars.copy_context()`` and then you would
+ pass that ``context`` object here.
+
+ Returns:
+ Task: the newly spawned task
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.spawn_system_task(async_fn, *args, name=name, context=context)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def current_trio_token() ->TrioToken:
+ """Retrieve the :class:`TrioToken` for the current call to
+ :func:`trio.run`.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.current_trio_token()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_all_tasks_blocked(cushion: float=0.0) ->None:
+ """Block until there are no runnable tasks.
+
+ This is useful in testing code when you want to give other tasks a
+ chance to "settle down". The calling task is blocked, and doesn't wake
+ up until all other tasks are also blocked for at least ``cushion``
+ seconds. (Setting a non-zero ``cushion`` is intended to handle cases
+ like two tasks talking to each other over a local socket, where we
+ want to ignore the potential brief moment between a send and receive
+ when all tasks are blocked.)
+
+ Note that ``cushion`` is measured in *real* time, not the Trio clock
+ time.
+
+ If there are multiple tasks blocked in :func:`wait_all_tasks_blocked`,
+ then the one with the shortest ``cushion`` is the one woken (and
+ this task becoming unblocked resets the timers for the remaining
+ tasks). If there are multiple tasks that have exactly the same
+ ``cushion``, then all are woken.
+
+ You should also consider :class:`trio.testing.Sequencer`, which
+ provides a more explicit way to control execution ordering within a
+ test, and will often produce more readable tests.
+
+ Example:
+ Here's an example of one way to test that Trio's locks are fair: we
+ take the lock in the parent, start a child, wait for the child to be
+ blocked waiting for the lock (!), and then check that we can't
+ release and immediately re-acquire the lock::
+
+ async def lock_taker(lock):
+ await lock.acquire()
+ lock.release()
+
+ async def test_lock_fairness():
+ lock = trio.Lock()
+ await lock.acquire()
+ async with trio.open_nursery() as nursery:
+ nursery.start_soon(lock_taker, lock)
+ # child hasn't run yet, we have the lock
+ assert lock.locked()
+ assert lock._owner is trio.lowlevel.current_task()
+ await trio.testing.wait_all_tasks_blocked()
+ # now the child has run and is blocked on lock.acquire(), we
+ # still have the lock
+ assert lock.locked()
+ assert lock._owner is trio.lowlevel.current_task()
+ lock.release()
+ try:
+ # The child has a prior claim, so we can't have it
+ lock.acquire_nowait()
+ except trio.WouldBlock:
+ assert lock._owner is not trio.lowlevel.current_task()
+ print("PASS")
+ else:
+ print("FAIL")
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.wait_all_tasks_blocked(cushion)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+# fmt: on
diff --git a/trio/_core/_io_common.py b/trio/_core/_io_common.py
index c1af293278..14cd9d33e6 100644
--- a/trio/_core/_io_common.py
+++ b/trio/_core/_io_common.py
@@ -9,10 +9,11 @@
if TYPE_CHECKING:
from ._io_epoll import EpollWaiters
+ from ._io_windows import AFDWaiters
# Utility function shared between _io_epoll and _io_windows
-def wake_all(waiters: EpollWaiters, exc: BaseException) -> None:
+def wake_all(waiters: EpollWaiters | AFDWaiters, exc: BaseException) -> None:
try:
current_task = _core.current_task()
except RuntimeError:
diff --git a/trio/_core/_io_windows.py b/trio/_core/_io_windows.py
index 74f95aa273..f27610e5dc 100644
--- a/trio/_core/_io_windows.py
+++ b/trio/_core/_io_windows.py
@@ -5,7 +5,16 @@
import socket
import sys
from contextlib import contextmanager
-from typing import TYPE_CHECKING, Iterator, Literal, TypeVar
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Iterator,
+ Literal,
+ Optional,
+ TypeVar,
+ cast,
+)
import attr
from outcome import Value
@@ -16,12 +25,15 @@
from ._windows_cffi import (
INVALID_HANDLE_VALUE,
AFDPollFlags,
+ CData,
CompletionModes,
ErrorCodes,
FileFlags,
+ Handle,
IoControlCodes,
WSAIoctls,
_handle,
+ _Overlapped,
ffi,
kernel32,
ntdll,
@@ -32,7 +44,7 @@
assert not TYPE_CHECKING or sys.platform == "win32"
if TYPE_CHECKING:
- from typing_extensions import TypeAlias
+ from typing_extensions import Buffer, TypeAlias
from ._traps import Abort, RaiseCancelT
from ._unbounded_queue import UnboundedQueue
@@ -197,8 +209,8 @@ def _check(success: T) -> T:
def _get_underlying_socket(
- sock: socket.socket | int, *, which=WSAIoctls.SIO_BASE_HANDLE
-):
+ sock: socket.socket | int | Handle, *, which: WSAIoctls = WSAIoctls.SIO_BASE_HANDLE
+) -> Handle:
if hasattr(sock, "fileno"):
sock = sock.fileno()
base_ptr = ffi.new("HANDLE *")
@@ -217,10 +229,10 @@ def _get_underlying_socket(
if failed:
code = ws2_32.WSAGetLastError()
raise_winerror(code)
- return base_ptr[0]
+ return Handle(base_ptr[0])
-def _get_base_socket(sock):
+def _get_base_socket(sock: socket.socket | int | Handle) -> Handle:
# There is a development kit for LSPs called Komodia Redirector.
# It does some unusual (some might say evil) things like intercepting
# SIO_BASE_HANDLE (fails) and SIO_BSP_HANDLE_SELECT (returns the same
@@ -267,7 +279,7 @@ def _get_base_socket(sock):
sock = next_sock
-def _afd_helper_handle():
+def _afd_helper_handle() -> Handle:
# The "AFD" driver is exposed at the NT path "\Device\Afd". We're using
# the Win32 CreateFile, though, so we have to pass a Win32 path. \\.\ is
# how Win32 refers to the NT \GLOBAL??\ directory, and GLOBALROOT is a
@@ -345,7 +357,7 @@ def _afd_helper_handle():
class AFDWaiters:
read_task: None = attr.ib(default=None)
write_task: None = attr.ib(default=None)
- current_op: None = attr.ib(default=None)
+ current_op: Optional[AFDPollOp] = attr.ib(default=None)
# We also need to bundle up all the info for a single op into a standalone
@@ -353,10 +365,10 @@ class AFDWaiters:
# finishes, even if we're throwing it away.
@attr.s(slots=True, eq=False, frozen=True)
class AFDPollOp:
- lpOverlapped: None = attr.ib()
- poll_info: None = attr.ib()
- waiters: None = attr.ib()
- afd_group: None = attr.ib()
+ lpOverlapped: CData = attr.ib()
+ poll_info: Any = attr.ib()
+ waiters: AFDWaiters = attr.ib()
+ afd_group: AFDGroup = attr.ib()
# The Windows kernel has a weird issue when using AFD handles. If you have N
@@ -373,7 +385,7 @@ class AFDPollOp:
@attr.s(slots=True, eq=False)
class AFDGroup:
size: int = attr.ib()
- handle: None = attr.ib()
+ handle: Handle = attr.ib()
@attr.s(slots=True, eq=False, frozen=True)
@@ -399,30 +411,30 @@ class CompletionKeyEventInfo:
class WindowsIOManager:
- def __init__(self):
+ def __init__(self) -> None:
# If this method raises an exception, then __del__ could run on a
# half-initialized object. So we initialize everything that __del__
# touches to safe values up front, before we do anything that can
# fail.
self._iocp = None
- self._all_afd_handles = []
+ self._all_afd_handles: list[Handle] = []
self._iocp = _check(
kernel32.CreateIoCompletionPort(INVALID_HANDLE_VALUE, ffi.NULL, 0, 0)
)
self._events = ffi.new("OVERLAPPED_ENTRY[]", MAX_EVENTS)
- self._vacant_afd_groups = set()
+ self._vacant_afd_groups: set[AFDGroup] = set()
# {lpOverlapped: AFDPollOp}
- self._afd_ops = {}
+ self._afd_ops: dict[CData, AFDPollOp] = {}
# {socket handle: AFDWaiters}
- self._afd_waiters = {}
+ self._afd_waiters: dict[Handle, AFDWaiters] = {}
# {lpOverlapped: task}
- self._overlapped_waiters = {}
- self._posted_too_late_to_cancel = set()
+ self._overlapped_waiters: dict[CData, _core.Task] = {}
+ self._posted_too_late_to_cancel: set[CData] = set()
- self._completion_key_queues = {}
+ self._completion_key_queues: dict[int, UnboundedQueue[object]] = {}
self._completion_key_counter = itertools.count(CKeys.USER_DEFINED)
with socket.socket() as s:
@@ -492,6 +504,7 @@ def statistics(self) -> _WindowsStatistics:
)
def force_wakeup(self) -> None:
+ assert self._iocp is not None
_check(
kernel32.PostQueuedCompletionStatus(
self._iocp, 0, CKeys.FORCE_WAKEUP, ffi.NULL
@@ -504,6 +517,7 @@ def get_events(self, timeout: float) -> EventResult:
if timeout > 0 and milliseconds == 0:
milliseconds = 1
try:
+ assert self._iocp is not None
_check(
kernel32.GetQueuedCompletionStatusEx(
self._iocp, self._events, MAX_EVENTS, received, milliseconds, 0
@@ -597,8 +611,9 @@ def process_events(self, received: EventResult) -> None:
)
queue.put_nowait(info)
- def _register_with_iocp(self, handle, completion_key) -> None:
- handle = _handle(handle)
+ def _register_with_iocp(self, handle_: int | CData, completion_key: int) -> None:
+ handle = _handle(handle_)
+ assert self._iocp is not None
_check(kernel32.CreateIoCompletionPort(handle, self._iocp, completion_key, 0))
# Supposedly this makes things slightly faster, by disabling the
# ability to do WaitForSingleObject(handle). We would never want to do
@@ -614,7 +629,7 @@ def _register_with_iocp(self, handle, completion_key) -> None:
# AFD stuff
################################################################
- def _refresh_afd(self, base_handle) -> None:
+ def _refresh_afd(self, base_handle: Handle) -> None:
waiters = self._afd_waiters[base_handle]
if waiters.current_op is not None:
afd_group = waiters.current_op.afd_group
@@ -652,7 +667,7 @@ def _refresh_afd(self, base_handle) -> None:
lpOverlapped = ffi.new("LPOVERLAPPED")
- poll_info = ffi.new("AFD_POLL_INFO *")
+ poll_info: Any = ffi.new("AFD_POLL_INFO *")
poll_info.Timeout = 2**63 - 1 # INT64_MAX
poll_info.NumberOfHandles = 1
poll_info.Exclusive = 0
@@ -690,7 +705,7 @@ def _refresh_afd(self, base_handle) -> None:
if afd_group.size >= MAX_AFD_GROUP_SIZE:
self._vacant_afd_groups.remove(afd_group)
- async def _afd_poll(self, sock, mode) -> None:
+ async def _afd_poll(self, sock: socket.socket | int, mode: str) -> None:
base_handle = _get_base_socket(sock)
waiters = self._afd_waiters.get(base_handle)
if waiters is None:
@@ -711,15 +726,15 @@ def abort_fn(_: RaiseCancelT) -> Abort:
await _core.wait_task_rescheduled(abort_fn)
@_public
- async def wait_readable(self, sock) -> None:
+ async def wait_readable(self, sock: socket.socket | int) -> None:
await self._afd_poll(sock, "read_task")
@_public
- async def wait_writable(self, sock) -> None:
+ async def wait_writable(self, sock: socket.socket | int) -> None:
await self._afd_poll(sock, "write_task")
@_public
- def notify_closing(self, handle) -> None:
+ def notify_closing(self, handle: Handle | int | socket.socket) -> None:
handle = _get_base_socket(handle)
waiters = self._afd_waiters.get(handle)
if waiters is not None:
@@ -731,12 +746,14 @@ def notify_closing(self, handle) -> None:
################################################################
@_public
- def register_with_iocp(self, handle) -> None:
+ def register_with_iocp(self, handle: int | CData) -> None:
self._register_with_iocp(handle, CKeys.WAIT_OVERLAPPED)
@_public
- async def wait_overlapped(self, handle, lpOverlapped):
- handle = _handle(handle)
+ async def wait_overlapped(
+ self, handle_: int | CData, lpOverlapped: CData | int
+ ) -> object:
+ handle = _handle(handle_)
if isinstance(lpOverlapped, int):
lpOverlapped = ffi.cast("LPOVERLAPPED", lpOverlapped)
if lpOverlapped in self._overlapped_waiters:
@@ -754,6 +771,7 @@ def abort(raise_cancel_: RaiseCancelT) -> Abort:
_check(kernel32.CancelIoEx(handle, lpOverlapped))
except OSError as exc:
if exc.winerror == ErrorCodes.ERROR_NOT_FOUND:
+ assert self._iocp is not None
# Too late to cancel. If this happens because the
# operation is already completed, we don't need to do
# anything; we'll get a notification of that completion
@@ -782,12 +800,14 @@ def abort(raise_cancel_: RaiseCancelT) -> Abort:
) from exc
return _core.Abort.FAILED
+ # TODO: what type does this return?
info = await _core.wait_task_rescheduled(abort)
- if lpOverlapped.Internal != 0:
+ lpOverlappedTyped = cast("_Overlapped", lpOverlapped)
+ if lpOverlappedTyped.Internal != 0:
# the lpOverlapped reports the error as an NT status code,
# which we must convert back to a Win32 error code before
# it will produce the right sorts of exceptions
- code = ntdll.RtlNtStatusToDosError(lpOverlapped.Internal)
+ code = ntdll.RtlNtStatusToDosError(lpOverlappedTyped.Internal)
if code == ErrorCodes.ERROR_OPERATION_ABORTED:
if raise_cancel is not None:
raise_cancel()
@@ -800,7 +820,9 @@ def abort(raise_cancel_: RaiseCancelT) -> Abort:
raise_winerror(code)
return info
- async def _perform_overlapped(self, handle, submit_fn):
+ async def _perform_overlapped(
+ self, handle: int | CData, submit_fn: Callable[[_Overlapped], None]
+ ) -> _Overlapped:
# submit_fn(lpOverlapped) submits some I/O
# it may raise an OSError with ERROR_IO_PENDING
# the handle must already be registered using
@@ -809,20 +831,22 @@ async def _perform_overlapped(self, handle, submit_fn):
# operation will not be cancellable, depending on how Windows is
# feeling today. So we need to check for cancellation manually.
await _core.checkpoint_if_cancelled()
- lpOverlapped = ffi.new("LPOVERLAPPED")
+ lpOverlapped = cast(_Overlapped, ffi.new("LPOVERLAPPED"))
try:
submit_fn(lpOverlapped)
except OSError as exc:
if exc.winerror != ErrorCodes.ERROR_IO_PENDING:
raise
- await self.wait_overlapped(handle, lpOverlapped)
+ await self.wait_overlapped(handle, cast(CData, lpOverlapped))
return lpOverlapped
@_public
- async def write_overlapped(self, handle, data, file_offset=0):
+ async def write_overlapped(
+ self, handle: int | CData, data: Buffer, file_offset: int = 0
+ ) -> int:
with ffi.from_buffer(data) as cbuf:
- def submit_write(lpOverlapped):
+ def submit_write(lpOverlapped: _Overlapped) -> None:
# yes, these are the real documented names
offset_fields = lpOverlapped.DUMMYUNIONNAME.DUMMYSTRUCTNAME
offset_fields.Offset = file_offset & 0xFFFFFFFF
@@ -842,10 +866,12 @@ def submit_write(lpOverlapped):
return lpOverlapped.InternalHigh
@_public
- async def readinto_overlapped(self, handle, buffer, file_offset=0):
+ async def readinto_overlapped(
+ self, handle: int | CData, buffer: Buffer, file_offset: int = 0
+ ) -> int:
with ffi.from_buffer(buffer, require_writable=True) as cbuf:
- def submit_read(lpOverlapped):
+ def submit_read(lpOverlapped: _Overlapped) -> None:
offset_fields = lpOverlapped.DUMMYUNIONNAME.DUMMYSTRUCTNAME
offset_fields.Offset = file_offset & 0xFFFFFFFF
offset_fields.OffsetHigh = file_offset >> 32
@@ -868,6 +894,7 @@ def submit_read(lpOverlapped):
@_public
def current_iocp(self) -> int:
+ assert self._iocp is not None
return int(ffi.cast("uintptr_t", self._iocp))
@contextmanager
diff --git a/trio/_core/_windows_cffi.py b/trio/_core/_windows_cffi.py
index 76e1edf3a7..041514f823 100644
--- a/trio/_core/_windows_cffi.py
+++ b/trio/_core/_windows_cffi.py
@@ -237,11 +237,11 @@ class _Kernel32(Protocol):
def CreateIoCompletionPort(
self,
FileHandle: Handle,
- ExistingCompletionPort: CData,
+ ExistingCompletionPort: CData | AlwaysNull,
CompletionKey: int,
NumberOfConcurrentThreads: int,
/,
- ) -> CData:
+ ) -> Handle:
...
def CreateEventA(
@@ -259,9 +259,51 @@ def SetFileCompletionNotificationModes(
) -> int:
...
+ def PostQueuedCompletionStatus(
+ self,
+ CompletionPort: Handle,
+ dwNumberOfBytesTransferred: int,
+ dwCompletionKey: int,
+ lpOverlapped: CData | AlwaysNull,
+ /,
+ ) -> bool:
+ ...
+
+ def CancelIoEx(
+ self,
+ hFile: Handle,
+ lpOverlapped: CData | AlwaysNull,
+ /,
+ ) -> bool:
+ ...
+
+ def WriteFile(
+ self,
+ hFile: Handle,
+ # not sure about this type
+ lpBuffer: CData,
+ nNumberOfBytesToWrite: int,
+ lpNumberOfBytesWritten: AlwaysNull,
+ lpOverlapped: _Overlapped,
+ /,
+ ) -> bool:
+ ...
+
+ def ReadFile(
+ self,
+ hFile: Handle,
+ # not sure about this type
+ lpBuffer: CData,
+ nNumberOfBytesToRead: int,
+ lpNumberOfBytesRead: AlwaysNull,
+ lpOverlapped: _Overlapped,
+ /,
+ ) -> bool:
+ ...
+
def GetQueuedCompletionStatusEx(
self,
- CompletionPort: CData,
+ CompletionPort: Handle,
lpCompletionPortEntries: CData,
ulCount: int,
ulNumEntriesRemoved: CData,
@@ -300,7 +342,23 @@ def WaitForMultipleObjects(
def SetEvent(self, handle: Handle, /) -> None:
...
- def CloseHandle(self, handle: Handle, /) -> None:
+ def CloseHandle(self, handle: Handle, /) -> bool:
+ ...
+
+ def DeviceIoControl(
+ self,
+ hDevice: Handle,
+ dwIoControlCode: int,
+ # this is wrong (it's not always null)
+ lpInBuffer: AlwaysNull,
+ nInBufferSize: int,
+ # this is also wrong
+ lpOutBuffer: AlwaysNull,
+ nOutBufferSize: int,
+ lpBytesReturned: AlwaysNull,
+ lpOverlapped: CData,
+ /,
+ ) -> bool:
...
@@ -325,7 +383,7 @@ def WSAIoctl(
cbInBuffer: int,
lpvOutBuffer: CData,
cbOutBuffer: int,
- lpcbBytesReturned: int,
+ lpcbBytesReturned: CData, # int*
lpOverlapped: AlwaysNull,
# actually LPWSAOVERLAPPED_COMPLETION_ROUTINE
lpCompletionRoutine: AlwaysNull,
@@ -334,6 +392,23 @@ def WSAIoctl(
...
+class _DummyStruct(Protocol):
+ Offset: int
+ OffsetHigh: int
+
+
+class _DummyUnion(Protocol):
+ DUMMYSTRUCTNAME: _DummyStruct
+ Pointer: object
+
+
+class _Overlapped(Protocol):
+ Internal: int
+ InternalHigh: int
+ DUMMYUNIONNAME: _DummyUnion
+ hEvent: Handle
+
+
kernel32 = cast(_Kernel32, ffi.dlopen("kernel32.dll"))
ntdll = cast(_Nt, ffi.dlopen("ntdll.dll"))
ws2_32 = cast(_Ws2, ffi.dlopen("ws2_32.dll"))
@@ -346,7 +421,7 @@ def WSAIoctl(
# https://www.magnumdb.com
# (Tip: check the box to see "Hex value")
-INVALID_HANDLE_VALUE = ffi.cast("HANDLE", -1)
+INVALID_HANDLE_VALUE = Handle(ffi.cast("HANDLE", -1))
class ErrorCodes(enum.IntEnum):
@@ -364,7 +439,7 @@ class ErrorCodes(enum.IntEnum):
ERROR_NOT_SOCKET = 10038
-class FileFlags(enum.IntEnum):
+class FileFlags(enum.IntFlag):
GENERIC_READ = 0x80000000
SYNCHRONIZE = 0x00100000
FILE_FLAG_OVERLAPPED = 0x40000000
diff --git a/trio/_tests/check_type_completeness.py b/trio/_tests/check_type_completeness.py
index 00d519c8a8..f1d659ab17 100755
--- a/trio/_tests/check_type_completeness.py
+++ b/trio/_tests/check_type_completeness.py
@@ -7,18 +7,19 @@
import subprocess
import sys
from pathlib import Path
+from typing import Mapping
# the result file is not marked in MANIFEST.in so it's not included in the package
failed = False
-def get_result_file_name(platform: str):
+def get_result_file_name(platform: str) -> Path:
return Path(__file__).parent / f"verify_types_{platform.lower()}.json"
# TODO: consider checking manually without `--ignoreexternal`, and/or
# removing it from the below call later on.
-def run_pyright(platform: str):
+def run_pyright(platform: str) -> subprocess.CompletedProcess[bytes]:
return subprocess.run(
[
"pyright",
@@ -33,7 +34,13 @@ def run_pyright(platform: str):
)
-def check_less_than(key, current_dict, last_dict, /, invert=False):
+def check_less_than(
+ key: str,
+ current_dict: Mapping[str, float],
+ last_dict: Mapping[str, float],
+ /,
+ invert: bool = False,
+) -> None:
global failed
current = current_dict[key]
last = last_dict[key]
@@ -57,7 +64,7 @@ def check_less_than(key, current_dict, last_dict, /, invert=False):
)
-def check_zero(key, current_dict):
+def check_zero(key: str, current_dict: Mapping[str, float]) -> None:
global failed
if current_dict[key] != 0:
failed = True
diff --git a/trio/_tools/gen_exports.py b/trio/_tools/gen_exports.py
index 0730c16684..5e2cc8c9de 100755
--- a/trio/_tools/gen_exports.py
+++ b/trio/_tools/gen_exports.py
@@ -287,6 +287,10 @@ def main() -> None: # pragma: no cover
from typing import TYPE_CHECKING, ContextManager
if TYPE_CHECKING:
+ import socket
+ from ._windows_cffi import Handle, CData
+ from typing_extensions import Buffer
+
from ._unbounded_queue import UnboundedQueue
"""
From 77293271914192bd461c65cc68d5b1721b13a341 Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Sat, 26 Aug 2023 09:34:03 +0900
Subject: [PATCH 07/19] Update verify_types files
---
trio/_tests/verify_types_darwin.json | 168 +++++++--------
trio/_tests/verify_types_linux.json | 144 ++++++-------
trio/_tests/verify_types_windows.json | 292 +++++++++-----------------
3 files changed, 260 insertions(+), 344 deletions(-)
diff --git a/trio/_tests/verify_types_darwin.json b/trio/_tests/verify_types_darwin.json
index 2b491521f5..6517c0d0eb 100644
--- a/trio/_tests/verify_types_darwin.json
+++ b/trio/_tests/verify_types_darwin.json
@@ -1,84 +1,84 @@
-{
- "generalDiagnostics": [],
- "summary": {
- "errorCount": 0,
- "filesAnalyzed": 8,
- "informationCount": 0,
- "warningCount": 0
- },
- "typeCompleteness": {
- "completenessScore": 1,
- "diagnostics": [
- {
- "message": "No docstring found for function \"trio.lowlevel.current_kqueue\"",
- "name": "trio.lowlevel.current_kqueue"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.monitor_kevent\"",
- "name": "trio.lowlevel.monitor_kevent"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.notify_closing\"",
- "name": "trio.lowlevel.notify_closing"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_kevent\"",
- "name": "trio.lowlevel.wait_kevent"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_readable\"",
- "name": "trio.lowlevel.wait_readable"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_writable\"",
- "name": "trio.lowlevel.wait_writable"
- },
- {
- "message": "No docstring found for class \"trio.tests.TestsDeprecationWrapper\"",
- "name": "trio.tests.TestsDeprecationWrapper"
- }
- ],
- "exportedSymbolCounts": {
- "withAmbiguousType": 0,
- "withKnownType": 631,
- "withUnknownType": 0
- },
- "ignoreUnknownTypesFromImports": true,
- "missingClassDocStringCount": 1,
- "missingDefaultParamCount": 0,
- "missingFunctionDocStringCount": 6,
- "moduleName": "trio",
- "modules": [
- {
- "name": "trio"
- },
- {
- "name": "trio.abc"
- },
- {
- "name": "trio.from_thread"
- },
- {
- "name": "trio.lowlevel"
- },
- {
- "name": "trio.socket"
- },
- {
- "name": "trio.testing"
- },
- {
- "name": "trio.tests"
- },
- {
- "name": "trio.to_thread"
- }
- ],
- "otherSymbolCounts": {
- "withAmbiguousType": 0,
- "withKnownType": 682,
- "withUnknownType": 0
- },
- "packageName": "trio"
- }
-}
+{
+ "generalDiagnostics": [],
+ "summary": {
+ "errorCount": 0,
+ "filesAnalyzed": 8,
+ "informationCount": 0,
+ "warningCount": 0
+ },
+ "typeCompleteness": {
+ "completenessScore": 1,
+ "diagnostics": [
+ {
+ "message": "No docstring found for function \"trio.lowlevel.current_kqueue\"",
+ "name": "trio.lowlevel.current_kqueue"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.monitor_kevent\"",
+ "name": "trio.lowlevel.monitor_kevent"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.notify_closing\"",
+ "name": "trio.lowlevel.notify_closing"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_kevent\"",
+ "name": "trio.lowlevel.wait_kevent"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_readable\"",
+ "name": "trio.lowlevel.wait_readable"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_writable\"",
+ "name": "trio.lowlevel.wait_writable"
+ },
+ {
+ "message": "No docstring found for class \"trio.tests.TestsDeprecationWrapper\"",
+ "name": "trio.tests.TestsDeprecationWrapper"
+ }
+ ],
+ "exportedSymbolCounts": {
+ "withAmbiguousType": 0,
+ "withKnownType": 631,
+ "withUnknownType": 0
+ },
+ "ignoreUnknownTypesFromImports": true,
+ "missingClassDocStringCount": 1,
+ "missingDefaultParamCount": 0,
+ "missingFunctionDocStringCount": 6,
+ "moduleName": "trio",
+ "modules": [
+ {
+ "name": "trio"
+ },
+ {
+ "name": "trio.abc"
+ },
+ {
+ "name": "trio.from_thread"
+ },
+ {
+ "name": "trio.lowlevel"
+ },
+ {
+ "name": "trio.socket"
+ },
+ {
+ "name": "trio.testing"
+ },
+ {
+ "name": "trio.tests"
+ },
+ {
+ "name": "trio.to_thread"
+ }
+ ],
+ "otherSymbolCounts": {
+ "withAmbiguousType": 0,
+ "withKnownType": 682,
+ "withUnknownType": 0
+ },
+ "packageName": "trio"
+ }
+}
diff --git a/trio/_tests/verify_types_linux.json b/trio/_tests/verify_types_linux.json
index a112e7edc9..3f1652d7de 100644
--- a/trio/_tests/verify_types_linux.json
+++ b/trio/_tests/verify_types_linux.json
@@ -1,72 +1,72 @@
-{
- "generalDiagnostics": [],
- "summary": {
- "errorCount": 0,
- "filesAnalyzed": 8,
- "informationCount": 0,
- "warningCount": 0
- },
- "typeCompleteness": {
- "completenessScore": 1,
- "diagnostics": [
- {
- "message": "No docstring found for function \"trio.lowlevel.notify_closing\"",
- "name": "trio.lowlevel.notify_closing"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_readable\"",
- "name": "trio.lowlevel.wait_readable"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_writable\"",
- "name": "trio.lowlevel.wait_writable"
- },
- {
- "message": "No docstring found for class \"trio.tests.TestsDeprecationWrapper\"",
- "name": "trio.tests.TestsDeprecationWrapper"
- }
- ],
- "exportedSymbolCounts": {
- "withAmbiguousType": 0,
- "withKnownType": 628,
- "withUnknownType": 0
- },
- "ignoreUnknownTypesFromImports": true,
- "missingClassDocStringCount": 1,
- "missingDefaultParamCount": 0,
- "missingFunctionDocStringCount": 3,
- "moduleName": "trio",
- "modules": [
- {
- "name": "trio"
- },
- {
- "name": "trio.abc"
- },
- {
- "name": "trio.from_thread"
- },
- {
- "name": "trio.lowlevel"
- },
- {
- "name": "trio.socket"
- },
- {
- "name": "trio.testing"
- },
- {
- "name": "trio.tests"
- },
- {
- "name": "trio.to_thread"
- }
- ],
- "otherSymbolCounts": {
- "withAmbiguousType": 0,
- "withKnownType": 682,
- "withUnknownType": 0
- },
- "packageName": "trio"
- }
-}
+{
+ "generalDiagnostics": [],
+ "summary": {
+ "errorCount": 0,
+ "filesAnalyzed": 8,
+ "informationCount": 0,
+ "warningCount": 0
+ },
+ "typeCompleteness": {
+ "completenessScore": 1,
+ "diagnostics": [
+ {
+ "message": "No docstring found for function \"trio.lowlevel.notify_closing\"",
+ "name": "trio.lowlevel.notify_closing"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_readable\"",
+ "name": "trio.lowlevel.wait_readable"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_writable\"",
+ "name": "trio.lowlevel.wait_writable"
+ },
+ {
+ "message": "No docstring found for class \"trio.tests.TestsDeprecationWrapper\"",
+ "name": "trio.tests.TestsDeprecationWrapper"
+ }
+ ],
+ "exportedSymbolCounts": {
+ "withAmbiguousType": 0,
+ "withKnownType": 628,
+ "withUnknownType": 0
+ },
+ "ignoreUnknownTypesFromImports": true,
+ "missingClassDocStringCount": 1,
+ "missingDefaultParamCount": 0,
+ "missingFunctionDocStringCount": 3,
+ "moduleName": "trio",
+ "modules": [
+ {
+ "name": "trio"
+ },
+ {
+ "name": "trio.abc"
+ },
+ {
+ "name": "trio.from_thread"
+ },
+ {
+ "name": "trio.lowlevel"
+ },
+ {
+ "name": "trio.socket"
+ },
+ {
+ "name": "trio.testing"
+ },
+ {
+ "name": "trio.tests"
+ },
+ {
+ "name": "trio.to_thread"
+ }
+ ],
+ "otherSymbolCounts": {
+ "withAmbiguousType": 0,
+ "withKnownType": 682,
+ "withUnknownType": 0
+ },
+ "packageName": "trio"
+ }
+}
diff --git a/trio/_tests/verify_types_windows.json b/trio/_tests/verify_types_windows.json
index 13c4756bd4..6ba76f741f 100644
--- a/trio/_tests/verify_types_windows.json
+++ b/trio/_tests/verify_types_windows.json
@@ -1,188 +1,104 @@
-{
- "generalDiagnostics": [],
- "summary": {
- "errorCount": 0,
- "filesAnalyzed": 8,
- "informationCount": 0,
- "warningCount": 0
- },
- "typeCompleteness": {
- "completenessScore": 0.9857369255150554,
- "diagnostics": [
- {
- "message": "Return type annotation is missing",
- "name": "trio.lowlevel.current_iocp"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.current_iocp\"",
- "name": "trio.lowlevel.current_iocp"
- },
- {
- "message": "Return type annotation is missing",
- "name": "trio.lowlevel.monitor_completion_key"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.monitor_completion_key\"",
- "name": "trio.lowlevel.monitor_completion_key"
- },
- {
- "message": "Type annotation for parameter \"handle\" is missing",
- "name": "trio.lowlevel.notify_closing"
- },
- {
- "message": "Return type annotation is missing",
- "name": "trio.lowlevel.notify_closing"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.notify_closing\"",
- "name": "trio.lowlevel.notify_closing"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.open_process\"",
- "name": "trio.lowlevel.open_process"
- },
- {
- "message": "Type annotation for parameter \"handle\" is missing",
- "name": "trio.lowlevel.readinto_overlapped"
- },
- {
- "message": "Type annotation for parameter \"buffer\" is missing",
- "name": "trio.lowlevel.readinto_overlapped"
- },
- {
- "message": "Type annotation for parameter \"file_offset\" is missing",
- "name": "trio.lowlevel.readinto_overlapped"
- },
- {
- "message": "Return type annotation is missing",
- "name": "trio.lowlevel.readinto_overlapped"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.readinto_overlapped\"",
- "name": "trio.lowlevel.readinto_overlapped"
- },
- {
- "message": "Type annotation for parameter \"handle\" is missing",
- "name": "trio.lowlevel.register_with_iocp"
- },
- {
- "message": "Return type annotation is missing",
- "name": "trio.lowlevel.register_with_iocp"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.register_with_iocp\"",
- "name": "trio.lowlevel.register_with_iocp"
- },
- {
- "message": "Type annotation for parameter \"handle\" is missing",
- "name": "trio.lowlevel.wait_overlapped"
- },
- {
- "message": "Type annotation for parameter \"lpOverlapped\" is missing",
- "name": "trio.lowlevel.wait_overlapped"
- },
- {
- "message": "Return type annotation is missing",
- "name": "trio.lowlevel.wait_overlapped"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_overlapped\"",
- "name": "trio.lowlevel.wait_overlapped"
- },
- {
- "message": "Type annotation for parameter \"sock\" is missing",
- "name": "trio.lowlevel.wait_readable"
- },
- {
- "message": "Return type annotation is missing",
- "name": "trio.lowlevel.wait_readable"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_readable\"",
- "name": "trio.lowlevel.wait_readable"
- },
- {
- "message": "Type annotation for parameter \"sock\" is missing",
- "name": "trio.lowlevel.wait_writable"
- },
- {
- "message": "Return type annotation is missing",
- "name": "trio.lowlevel.wait_writable"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_writable\"",
- "name": "trio.lowlevel.wait_writable"
- },
- {
- "message": "Type annotation for parameter \"handle\" is missing",
- "name": "trio.lowlevel.write_overlapped"
- },
- {
- "message": "Type annotation for parameter \"data\" is missing",
- "name": "trio.lowlevel.write_overlapped"
- },
- {
- "message": "Type annotation for parameter \"file_offset\" is missing",
- "name": "trio.lowlevel.write_overlapped"
- },
- {
- "message": "Return type annotation is missing",
- "name": "trio.lowlevel.write_overlapped"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.write_overlapped\"",
- "name": "trio.lowlevel.write_overlapped"
- },
- {
- "message": "No docstring found for function \"trio.run_process\"",
- "name": "trio.run_process"
- },
- {
- "message": "No docstring found for class \"trio.tests.TestsDeprecationWrapper\"",
- "name": "trio.tests.TestsDeprecationWrapper"
- }
- ],
- "exportedSymbolCounts": {
- "withAmbiguousType": 0,
- "withKnownType": 622,
- "withUnknownType": 9
- },
- "ignoreUnknownTypesFromImports": true,
- "missingClassDocStringCount": 1,
- "missingDefaultParamCount": 0,
- "missingFunctionDocStringCount": 11,
- "moduleName": "trio",
- "modules": [
- {
- "name": "trio"
- },
- {
- "name": "trio.abc"
- },
- {
- "name": "trio.from_thread"
- },
- {
- "name": "trio.lowlevel"
- },
- {
- "name": "trio.socket"
- },
- {
- "name": "trio.testing"
- },
- {
- "name": "trio.tests"
- },
- {
- "name": "trio.to_thread"
- }
- ],
- "otherSymbolCounts": {
- "withAmbiguousType": 0,
- "withKnownType": 673,
- "withUnknownType": 0
- },
- "packageName": "trio"
- }
-}
+{
+ "generalDiagnostics": [],
+ "summary": {
+ "errorCount": 0,
+ "filesAnalyzed": 8,
+ "informationCount": 0,
+ "warningCount": 0
+ },
+ "typeCompleteness": {
+ "completenessScore": 1,
+ "diagnostics": [
+ {
+ "message": "No docstring found for function \"trio.lowlevel.current_iocp\"",
+ "name": "trio.lowlevel.current_iocp"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.monitor_completion_key\"",
+ "name": "trio.lowlevel.monitor_completion_key"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.notify_closing\"",
+ "name": "trio.lowlevel.notify_closing"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.open_process\"",
+ "name": "trio.lowlevel.open_process"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.readinto_overlapped\"",
+ "name": "trio.lowlevel.readinto_overlapped"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.register_with_iocp\"",
+ "name": "trio.lowlevel.register_with_iocp"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_overlapped\"",
+ "name": "trio.lowlevel.wait_overlapped"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_readable\"",
+ "name": "trio.lowlevel.wait_readable"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_writable\"",
+ "name": "trio.lowlevel.wait_writable"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.write_overlapped\"",
+ "name": "trio.lowlevel.write_overlapped"
+ },
+ {
+ "message": "No docstring found for function \"trio.run_process\"",
+ "name": "trio.run_process"
+ },
+ {
+ "message": "No docstring found for class \"trio.tests.TestsDeprecationWrapper\"",
+ "name": "trio.tests.TestsDeprecationWrapper"
+ }
+ ],
+ "exportedSymbolCounts": {
+ "withAmbiguousType": 0,
+ "withKnownType": 631,
+ "withUnknownType": 0
+ },
+ "ignoreUnknownTypesFromImports": true,
+ "missingClassDocStringCount": 1,
+ "missingDefaultParamCount": 0,
+ "missingFunctionDocStringCount": 11,
+ "moduleName": "trio",
+ "modules": [
+ {
+ "name": "trio"
+ },
+ {
+ "name": "trio.abc"
+ },
+ {
+ "name": "trio.from_thread"
+ },
+ {
+ "name": "trio.lowlevel"
+ },
+ {
+ "name": "trio.socket"
+ },
+ {
+ "name": "trio.testing"
+ },
+ {
+ "name": "trio.tests"
+ },
+ {
+ "name": "trio.to_thread"
+ }
+ ],
+ "otherSymbolCounts": {
+ "withAmbiguousType": 0,
+ "withKnownType": 676,
+ "withUnknownType": 0
+ },
+ "packageName": "trio"
+ }
+}
From 776fbd11ec779c88925fbdfe63914e8a2210c8e6 Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Sat, 26 Aug 2023 10:19:33 +0900
Subject: [PATCH 08/19] CRLF -> LF
---
trio/_tests/verify_types_darwin.json | 168 ++++++++++-----------
trio/_tests/verify_types_linux.json | 144 +++++++++---------
trio/_tests/verify_types_windows.json | 208 +++++++++++++-------------
3 files changed, 260 insertions(+), 260 deletions(-)
diff --git a/trio/_tests/verify_types_darwin.json b/trio/_tests/verify_types_darwin.json
index 6517c0d0eb..2b491521f5 100644
--- a/trio/_tests/verify_types_darwin.json
+++ b/trio/_tests/verify_types_darwin.json
@@ -1,84 +1,84 @@
-{
- "generalDiagnostics": [],
- "summary": {
- "errorCount": 0,
- "filesAnalyzed": 8,
- "informationCount": 0,
- "warningCount": 0
- },
- "typeCompleteness": {
- "completenessScore": 1,
- "diagnostics": [
- {
- "message": "No docstring found for function \"trio.lowlevel.current_kqueue\"",
- "name": "trio.lowlevel.current_kqueue"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.monitor_kevent\"",
- "name": "trio.lowlevel.monitor_kevent"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.notify_closing\"",
- "name": "trio.lowlevel.notify_closing"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_kevent\"",
- "name": "trio.lowlevel.wait_kevent"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_readable\"",
- "name": "trio.lowlevel.wait_readable"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_writable\"",
- "name": "trio.lowlevel.wait_writable"
- },
- {
- "message": "No docstring found for class \"trio.tests.TestsDeprecationWrapper\"",
- "name": "trio.tests.TestsDeprecationWrapper"
- }
- ],
- "exportedSymbolCounts": {
- "withAmbiguousType": 0,
- "withKnownType": 631,
- "withUnknownType": 0
- },
- "ignoreUnknownTypesFromImports": true,
- "missingClassDocStringCount": 1,
- "missingDefaultParamCount": 0,
- "missingFunctionDocStringCount": 6,
- "moduleName": "trio",
- "modules": [
- {
- "name": "trio"
- },
- {
- "name": "trio.abc"
- },
- {
- "name": "trio.from_thread"
- },
- {
- "name": "trio.lowlevel"
- },
- {
- "name": "trio.socket"
- },
- {
- "name": "trio.testing"
- },
- {
- "name": "trio.tests"
- },
- {
- "name": "trio.to_thread"
- }
- ],
- "otherSymbolCounts": {
- "withAmbiguousType": 0,
- "withKnownType": 682,
- "withUnknownType": 0
- },
- "packageName": "trio"
- }
-}
+{
+ "generalDiagnostics": [],
+ "summary": {
+ "errorCount": 0,
+ "filesAnalyzed": 8,
+ "informationCount": 0,
+ "warningCount": 0
+ },
+ "typeCompleteness": {
+ "completenessScore": 1,
+ "diagnostics": [
+ {
+ "message": "No docstring found for function \"trio.lowlevel.current_kqueue\"",
+ "name": "trio.lowlevel.current_kqueue"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.monitor_kevent\"",
+ "name": "trio.lowlevel.monitor_kevent"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.notify_closing\"",
+ "name": "trio.lowlevel.notify_closing"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_kevent\"",
+ "name": "trio.lowlevel.wait_kevent"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_readable\"",
+ "name": "trio.lowlevel.wait_readable"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_writable\"",
+ "name": "trio.lowlevel.wait_writable"
+ },
+ {
+ "message": "No docstring found for class \"trio.tests.TestsDeprecationWrapper\"",
+ "name": "trio.tests.TestsDeprecationWrapper"
+ }
+ ],
+ "exportedSymbolCounts": {
+ "withAmbiguousType": 0,
+ "withKnownType": 631,
+ "withUnknownType": 0
+ },
+ "ignoreUnknownTypesFromImports": true,
+ "missingClassDocStringCount": 1,
+ "missingDefaultParamCount": 0,
+ "missingFunctionDocStringCount": 6,
+ "moduleName": "trio",
+ "modules": [
+ {
+ "name": "trio"
+ },
+ {
+ "name": "trio.abc"
+ },
+ {
+ "name": "trio.from_thread"
+ },
+ {
+ "name": "trio.lowlevel"
+ },
+ {
+ "name": "trio.socket"
+ },
+ {
+ "name": "trio.testing"
+ },
+ {
+ "name": "trio.tests"
+ },
+ {
+ "name": "trio.to_thread"
+ }
+ ],
+ "otherSymbolCounts": {
+ "withAmbiguousType": 0,
+ "withKnownType": 682,
+ "withUnknownType": 0
+ },
+ "packageName": "trio"
+ }
+}
diff --git a/trio/_tests/verify_types_linux.json b/trio/_tests/verify_types_linux.json
index 3f1652d7de..a112e7edc9 100644
--- a/trio/_tests/verify_types_linux.json
+++ b/trio/_tests/verify_types_linux.json
@@ -1,72 +1,72 @@
-{
- "generalDiagnostics": [],
- "summary": {
- "errorCount": 0,
- "filesAnalyzed": 8,
- "informationCount": 0,
- "warningCount": 0
- },
- "typeCompleteness": {
- "completenessScore": 1,
- "diagnostics": [
- {
- "message": "No docstring found for function \"trio.lowlevel.notify_closing\"",
- "name": "trio.lowlevel.notify_closing"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_readable\"",
- "name": "trio.lowlevel.wait_readable"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_writable\"",
- "name": "trio.lowlevel.wait_writable"
- },
- {
- "message": "No docstring found for class \"trio.tests.TestsDeprecationWrapper\"",
- "name": "trio.tests.TestsDeprecationWrapper"
- }
- ],
- "exportedSymbolCounts": {
- "withAmbiguousType": 0,
- "withKnownType": 628,
- "withUnknownType": 0
- },
- "ignoreUnknownTypesFromImports": true,
- "missingClassDocStringCount": 1,
- "missingDefaultParamCount": 0,
- "missingFunctionDocStringCount": 3,
- "moduleName": "trio",
- "modules": [
- {
- "name": "trio"
- },
- {
- "name": "trio.abc"
- },
- {
- "name": "trio.from_thread"
- },
- {
- "name": "trio.lowlevel"
- },
- {
- "name": "trio.socket"
- },
- {
- "name": "trio.testing"
- },
- {
- "name": "trio.tests"
- },
- {
- "name": "trio.to_thread"
- }
- ],
- "otherSymbolCounts": {
- "withAmbiguousType": 0,
- "withKnownType": 682,
- "withUnknownType": 0
- },
- "packageName": "trio"
- }
-}
+{
+ "generalDiagnostics": [],
+ "summary": {
+ "errorCount": 0,
+ "filesAnalyzed": 8,
+ "informationCount": 0,
+ "warningCount": 0
+ },
+ "typeCompleteness": {
+ "completenessScore": 1,
+ "diagnostics": [
+ {
+ "message": "No docstring found for function \"trio.lowlevel.notify_closing\"",
+ "name": "trio.lowlevel.notify_closing"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_readable\"",
+ "name": "trio.lowlevel.wait_readable"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_writable\"",
+ "name": "trio.lowlevel.wait_writable"
+ },
+ {
+ "message": "No docstring found for class \"trio.tests.TestsDeprecationWrapper\"",
+ "name": "trio.tests.TestsDeprecationWrapper"
+ }
+ ],
+ "exportedSymbolCounts": {
+ "withAmbiguousType": 0,
+ "withKnownType": 628,
+ "withUnknownType": 0
+ },
+ "ignoreUnknownTypesFromImports": true,
+ "missingClassDocStringCount": 1,
+ "missingDefaultParamCount": 0,
+ "missingFunctionDocStringCount": 3,
+ "moduleName": "trio",
+ "modules": [
+ {
+ "name": "trio"
+ },
+ {
+ "name": "trio.abc"
+ },
+ {
+ "name": "trio.from_thread"
+ },
+ {
+ "name": "trio.lowlevel"
+ },
+ {
+ "name": "trio.socket"
+ },
+ {
+ "name": "trio.testing"
+ },
+ {
+ "name": "trio.tests"
+ },
+ {
+ "name": "trio.to_thread"
+ }
+ ],
+ "otherSymbolCounts": {
+ "withAmbiguousType": 0,
+ "withKnownType": 682,
+ "withUnknownType": 0
+ },
+ "packageName": "trio"
+ }
+}
diff --git a/trio/_tests/verify_types_windows.json b/trio/_tests/verify_types_windows.json
index 6ba76f741f..f059f71476 100644
--- a/trio/_tests/verify_types_windows.json
+++ b/trio/_tests/verify_types_windows.json
@@ -1,104 +1,104 @@
-{
- "generalDiagnostics": [],
- "summary": {
- "errorCount": 0,
- "filesAnalyzed": 8,
- "informationCount": 0,
- "warningCount": 0
- },
- "typeCompleteness": {
- "completenessScore": 1,
- "diagnostics": [
- {
- "message": "No docstring found for function \"trio.lowlevel.current_iocp\"",
- "name": "trio.lowlevel.current_iocp"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.monitor_completion_key\"",
- "name": "trio.lowlevel.monitor_completion_key"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.notify_closing\"",
- "name": "trio.lowlevel.notify_closing"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.open_process\"",
- "name": "trio.lowlevel.open_process"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.readinto_overlapped\"",
- "name": "trio.lowlevel.readinto_overlapped"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.register_with_iocp\"",
- "name": "trio.lowlevel.register_with_iocp"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_overlapped\"",
- "name": "trio.lowlevel.wait_overlapped"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_readable\"",
- "name": "trio.lowlevel.wait_readable"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.wait_writable\"",
- "name": "trio.lowlevel.wait_writable"
- },
- {
- "message": "No docstring found for function \"trio.lowlevel.write_overlapped\"",
- "name": "trio.lowlevel.write_overlapped"
- },
- {
- "message": "No docstring found for function \"trio.run_process\"",
- "name": "trio.run_process"
- },
- {
- "message": "No docstring found for class \"trio.tests.TestsDeprecationWrapper\"",
- "name": "trio.tests.TestsDeprecationWrapper"
- }
- ],
- "exportedSymbolCounts": {
- "withAmbiguousType": 0,
- "withKnownType": 631,
- "withUnknownType": 0
- },
- "ignoreUnknownTypesFromImports": true,
- "missingClassDocStringCount": 1,
- "missingDefaultParamCount": 0,
- "missingFunctionDocStringCount": 11,
- "moduleName": "trio",
- "modules": [
- {
- "name": "trio"
- },
- {
- "name": "trio.abc"
- },
- {
- "name": "trio.from_thread"
- },
- {
- "name": "trio.lowlevel"
- },
- {
- "name": "trio.socket"
- },
- {
- "name": "trio.testing"
- },
- {
- "name": "trio.tests"
- },
- {
- "name": "trio.to_thread"
- }
- ],
- "otherSymbolCounts": {
- "withAmbiguousType": 0,
- "withKnownType": 676,
- "withUnknownType": 0
- },
- "packageName": "trio"
- }
-}
+{
+ "generalDiagnostics": [],
+ "summary": {
+ "errorCount": 0,
+ "filesAnalyzed": 8,
+ "informationCount": 0,
+ "warningCount": 0
+ },
+ "typeCompleteness": {
+ "completenessScore": 1,
+ "diagnostics": [
+ {
+ "message": "No docstring found for function \"trio.lowlevel.current_iocp\"",
+ "name": "trio.lowlevel.current_iocp"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.monitor_completion_key\"",
+ "name": "trio.lowlevel.monitor_completion_key"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.notify_closing\"",
+ "name": "trio.lowlevel.notify_closing"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.open_process\"",
+ "name": "trio.lowlevel.open_process"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.readinto_overlapped\"",
+ "name": "trio.lowlevel.readinto_overlapped"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.register_with_iocp\"",
+ "name": "trio.lowlevel.register_with_iocp"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_overlapped\"",
+ "name": "trio.lowlevel.wait_overlapped"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_readable\"",
+ "name": "trio.lowlevel.wait_readable"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.wait_writable\"",
+ "name": "trio.lowlevel.wait_writable"
+ },
+ {
+ "message": "No docstring found for function \"trio.lowlevel.write_overlapped\"",
+ "name": "trio.lowlevel.write_overlapped"
+ },
+ {
+ "message": "No docstring found for function \"trio.run_process\"",
+ "name": "trio.run_process"
+ },
+ {
+ "message": "No docstring found for class \"trio.tests.TestsDeprecationWrapper\"",
+ "name": "trio.tests.TestsDeprecationWrapper"
+ }
+ ],
+ "exportedSymbolCounts": {
+ "withAmbiguousType": 0,
+ "withKnownType": 631,
+ "withUnknownType": 0
+ },
+ "ignoreUnknownTypesFromImports": true,
+ "missingClassDocStringCount": 1,
+ "missingDefaultParamCount": 0,
+ "missingFunctionDocStringCount": 11,
+ "moduleName": "trio",
+ "modules": [
+ {
+ "name": "trio"
+ },
+ {
+ "name": "trio.abc"
+ },
+ {
+ "name": "trio.from_thread"
+ },
+ {
+ "name": "trio.lowlevel"
+ },
+ {
+ "name": "trio.socket"
+ },
+ {
+ "name": "trio.testing"
+ },
+ {
+ "name": "trio.tests"
+ },
+ {
+ "name": "trio.to_thread"
+ }
+ ],
+ "otherSymbolCounts": {
+ "withAmbiguousType": 0,
+ "withKnownType": 676,
+ "withUnknownType": 0
+ },
+ "packageName": "trio"
+ }
+}
From 70f25863df4a84c57e31d91ed8c133261fa940d0 Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Sat, 26 Aug 2023 10:43:53 +0900
Subject: [PATCH 09/19] Try to fix CI warnings
---
test-requirements.in | 2 +-
test-requirements.txt | 2 +-
trio/_core/_io_windows.py | 191 +++++++++++++++++++-------------------
3 files changed, 98 insertions(+), 97 deletions(-)
diff --git a/test-requirements.in b/test-requirements.in
index 20eae50aa5..3bebad4884 100644
--- a/test-requirements.in
+++ b/test-requirements.in
@@ -23,7 +23,7 @@ codespell
# https://github.com/python-trio/trio/pull/654#issuecomment-420518745
mypy-extensions; implementation_name == "cpython"
typing-extensions
-types-cffi; os_name == "nt" and implementation_name == "cpython"
+types-cffi; implementation_name == "cpython"
# Trio's own dependencies
cffi; os_name == "nt"
diff --git a/test-requirements.txt b/test-requirements.txt
index e0bdb01eae..15d78e3f95 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -159,7 +159,7 @@ traitlets==5.9.0
# matplotlib-inline
trustme==1.1.0
# via -r test-requirements.in
-types-cffi==1.15.1.15 ; os_name == "nt" and implementation_name == "cpython"
+types-cffi==1.15.1.15 ; implementation_name == "cpython"
# via -r test-requirements.in
types-pyopenssl==23.2.0.2 ; implementation_name == "cpython"
# via -r test-requirements.in
diff --git a/trio/_core/_io_windows.py b/trio/_core/_io_windows.py
index f27610e5dc..25f73dd146 100644
--- a/trio/_core/_io_windows.py
+++ b/trio/_core/_io_windows.py
@@ -41,8 +41,6 @@
ws2_32,
)
-assert not TYPE_CHECKING or sys.platform == "win32"
-
if TYPE_CHECKING:
from typing_extensions import Buffer, TypeAlias
@@ -202,6 +200,102 @@ class CKeys(enum.IntEnum):
USER_DEFINED = 4 # and above
+# AFD_POLL has a finer-grained set of events than other APIs. We collapse them
+# down into Unix-style "readable" and "writable".
+#
+# Note: AFD_POLL_LOCAL_CLOSE isn't a reliable substitute for notify_closing(),
+# because even if the user closes the socket *handle*, the socket *object*
+# could still remain open, e.g. if the socket was dup'ed (possibly into
+# another process). Explicitly calling notify_closing() guarantees that
+# everyone waiting on the *handle* wakes up, which is what you'd expect.
+#
+# However, we can't avoid getting LOCAL_CLOSE notifications -- the kernel
+# delivers them whether we ask for them or not -- so better to include them
+# here for documentation, and so that when we check (delivered & requested) we
+# get a match.
+
+READABLE_FLAGS = (
+ AFDPollFlags.AFD_POLL_RECEIVE
+ | AFDPollFlags.AFD_POLL_ACCEPT
+ | AFDPollFlags.AFD_POLL_DISCONNECT # other side sent an EOF
+ | AFDPollFlags.AFD_POLL_ABORT
+ | AFDPollFlags.AFD_POLL_LOCAL_CLOSE
+)
+
+WRITABLE_FLAGS = (
+ AFDPollFlags.AFD_POLL_SEND
+ | AFDPollFlags.AFD_POLL_CONNECT_FAIL
+ | AFDPollFlags.AFD_POLL_ABORT
+ | AFDPollFlags.AFD_POLL_LOCAL_CLOSE
+)
+
+
+# Annoyingly, while the API makes it *seem* like you can happily issue as many
+# independent AFD_POLL operations as you want without them interfering with
+# each other, in fact if you issue two AFD_POLL operations for the same socket
+# at the same time with notification going to the same IOCP port, then Windows
+# gets super confused. For example, if we issue one operation from
+# wait_readable, and another independent operation from wait_writable, then
+# Windows may complete the wait_writable operation when the socket becomes
+# readable.
+#
+# To avoid this, we have to coalesce all the operations on a single socket
+# into one, and when the set of waiters changes we have to throw away the old
+# operation and start a new one.
+@attr.s(slots=True, eq=False)
+class AFDWaiters:
+ read_task: None = attr.ib(default=None)
+ write_task: None = attr.ib(default=None)
+ current_op: Optional[AFDPollOp] = attr.ib(default=None)
+
+
+# We also need to bundle up all the info for a single op into a standalone
+# object, because we need to keep all these objects alive until the operation
+# finishes, even if we're throwing it away.
+@attr.s(slots=True, eq=False, frozen=True)
+class AFDPollOp:
+ lpOverlapped: CData = attr.ib()
+ poll_info: Any = attr.ib()
+ waiters: AFDWaiters = attr.ib()
+ afd_group: AFDGroup = attr.ib()
+
+
+# The Windows kernel has a weird issue when using AFD handles. If you have N
+# instances of wait_readable/wait_writable registered with a single AFD handle,
+# then cancelling any one of them takes something like O(N**2) time. So if we
+# used just a single AFD handle, then cancellation would quickly become very
+# expensive, e.g. a program with N active sockets would take something like
+# O(N**3) time to unwind after control-C. The solution is to spread our sockets
+# out over multiple AFD handles, so that N doesn't grow too large for any
+# individual handle.
+MAX_AFD_GROUP_SIZE = 500 # at 1000, the cubic scaling is just starting to bite
+
+
+@attr.s(slots=True, eq=False)
+class AFDGroup:
+ size: int = attr.ib()
+ handle: Handle = attr.ib()
+
+
+assert not TYPE_CHECKING or sys.platform == "win32"
+
+
+@attr.s(slots=True, eq=False, frozen=True)
+class _WindowsStatistics:
+ tasks_waiting_read: int = attr.ib()
+ tasks_waiting_write: int = attr.ib()
+ tasks_waiting_overlapped: int = attr.ib()
+ completion_key_monitors: int = attr.ib()
+ backend: Literal["windows"] = attr.ib(init=False, default="windows")
+
+
+# Maximum number of events to dequeue from the completion port on each pass
+# through the run loop. Somewhat arbitrary. Should be large enough to collect
+# a good set of tasks on each loop, but not so large to waste tons of memory.
+# (Each WindowsIOManager holds a buffer whose size is ~32x this number.)
+MAX_EVENTS = 1000
+
+
def _check(success: T) -> T:
if not success:
raise_winerror()
@@ -311,99 +405,6 @@ def _afd_helper_handle() -> Handle:
return handle
-# AFD_POLL has a finer-grained set of events than other APIs. We collapse them
-# down into Unix-style "readable" and "writable".
-#
-# Note: AFD_POLL_LOCAL_CLOSE isn't a reliable substitute for notify_closing(),
-# because even if the user closes the socket *handle*, the socket *object*
-# could still remain open, e.g. if the socket was dup'ed (possibly into
-# another process). Explicitly calling notify_closing() guarantees that
-# everyone waiting on the *handle* wakes up, which is what you'd expect.
-#
-# However, we can't avoid getting LOCAL_CLOSE notifications -- the kernel
-# delivers them whether we ask for them or not -- so better to include them
-# here for documentation, and so that when we check (delivered & requested) we
-# get a match.
-
-READABLE_FLAGS = (
- AFDPollFlags.AFD_POLL_RECEIVE
- | AFDPollFlags.AFD_POLL_ACCEPT
- | AFDPollFlags.AFD_POLL_DISCONNECT # other side sent an EOF
- | AFDPollFlags.AFD_POLL_ABORT
- | AFDPollFlags.AFD_POLL_LOCAL_CLOSE
-)
-
-WRITABLE_FLAGS = (
- AFDPollFlags.AFD_POLL_SEND
- | AFDPollFlags.AFD_POLL_CONNECT_FAIL
- | AFDPollFlags.AFD_POLL_ABORT
- | AFDPollFlags.AFD_POLL_LOCAL_CLOSE
-)
-
-
-# Annoyingly, while the API makes it *seem* like you can happily issue as many
-# independent AFD_POLL operations as you want without them interfering with
-# each other, in fact if you issue two AFD_POLL operations for the same socket
-# at the same time with notification going to the same IOCP port, then Windows
-# gets super confused. For example, if we issue one operation from
-# wait_readable, and another independent operation from wait_writable, then
-# Windows may complete the wait_writable operation when the socket becomes
-# readable.
-#
-# To avoid this, we have to coalesce all the operations on a single socket
-# into one, and when the set of waiters changes we have to throw away the old
-# operation and start a new one.
-@attr.s(slots=True, eq=False)
-class AFDWaiters:
- read_task: None = attr.ib(default=None)
- write_task: None = attr.ib(default=None)
- current_op: Optional[AFDPollOp] = attr.ib(default=None)
-
-
-# We also need to bundle up all the info for a single op into a standalone
-# object, because we need to keep all these objects alive until the operation
-# finishes, even if we're throwing it away.
-@attr.s(slots=True, eq=False, frozen=True)
-class AFDPollOp:
- lpOverlapped: CData = attr.ib()
- poll_info: Any = attr.ib()
- waiters: AFDWaiters = attr.ib()
- afd_group: AFDGroup = attr.ib()
-
-
-# The Windows kernel has a weird issue when using AFD handles. If you have N
-# instances of wait_readable/wait_writable registered with a single AFD handle,
-# then cancelling any one of them takes something like O(N**2) time. So if we
-# used just a single AFD handle, then cancellation would quickly become very
-# expensive, e.g. a program with N active sockets would take something like
-# O(N**3) time to unwind after control-C. The solution is to spread our sockets
-# out over multiple AFD handles, so that N doesn't grow too large for any
-# individual handle.
-MAX_AFD_GROUP_SIZE = 500 # at 1000, the cubic scaling is just starting to bite
-
-
-@attr.s(slots=True, eq=False)
-class AFDGroup:
- size: int = attr.ib()
- handle: Handle = attr.ib()
-
-
-@attr.s(slots=True, eq=False, frozen=True)
-class _WindowsStatistics:
- tasks_waiting_read: int = attr.ib()
- tasks_waiting_write: int = attr.ib()
- tasks_waiting_overlapped: int = attr.ib()
- completion_key_monitors: int = attr.ib()
- backend: Literal["windows"] = attr.ib(init=False, default="windows")
-
-
-# Maximum number of events to dequeue from the completion port on each pass
-# through the run loop. Somewhat arbitrary. Should be large enough to collect
-# a good set of tasks on each loop, but not so large to waste tons of memory.
-# (Each WindowsIOManager holds a buffer whose size is ~32x this number.)
-MAX_EVENTS = 1000
-
-
@attr.s(frozen=True)
class CompletionKeyEventInfo:
lpOverlapped: None = attr.ib()
From 0a592d8d778242793807a0d4d2b696f8546ea8db Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Sat, 26 Aug 2023 10:56:15 +0900
Subject: [PATCH 10/19] Bunch of stupid type ignores
---
trio/_core/_windows_cffi.py | 6 ++++--
trio/_subprocess_platform/waitid.py | 2 +-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/trio/_core/_windows_cffi.py b/trio/_core/_windows_cffi.py
index 041514f823..bc99aa9f21 100644
--- a/trio/_core/_windows_cffi.py
+++ b/trio/_core/_windows_cffi.py
@@ -2,6 +2,7 @@
import enum
import re
+import sys
from typing import TYPE_CHECKING, NewType, Protocol, cast
if TYPE_CHECKING:
@@ -516,13 +517,14 @@ def raise_winerror(
filename: str | None = None,
filename2: str | None = None,
) -> NoReturn:
+ assert sys.platform == "win32" # this should work starting next mypy release
if winerror is None:
- err = ffi.getwinerror()
+ err = ffi.getwinerror() # type: ignore[attr-defined,unused-ignore]
if err is None:
raise RuntimeError("No error set?")
winerror, msg = err
else:
- err = ffi.getwinerror(winerror)
+ err = ffi.getwinerror(winerror) # type: ignore[attr-defined,unused-ignore]
if err is None:
raise RuntimeError("No error set?")
_, msg = err
diff --git a/trio/_subprocess_platform/waitid.py b/trio/_subprocess_platform/waitid.py
index 2a2ca6719d..7d941747e3 100644
--- a/trio/_subprocess_platform/waitid.py
+++ b/trio/_subprocess_platform/waitid.py
@@ -43,7 +43,7 @@ def sync_wait_reapable(pid: int) -> None:
int waitid(int idtype, int id, siginfo_t* result, int options);
"""
)
- waitid_cffi = waitid_ffi.dlopen(None).waitid
+ waitid_cffi = waitid_ffi.dlopen(None).waitid # type: ignore[attr-defined]
def sync_wait_reapable(pid: int) -> None:
P_PID = 1
From 9fbb392e8596498121b20afb5a7e56d4bff589be Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Sat, 26 Aug 2023 11:14:26 +0900
Subject: [PATCH 11/19] Update pip-compile'd files
---
test-requirements.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/test-requirements.txt b/test-requirements.txt
index 15d78e3f95..7d45ea16e2 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -24,7 +24,6 @@ build==0.10.0
# via pip-tools
cffi==1.15.1
# via cryptography
- # via -r test-requirements.in
click==8.1.6
# via
# black
@@ -163,6 +162,8 @@ types-cffi==1.15.1.15 ; implementation_name == "cpython"
# via -r test-requirements.in
types-pyopenssl==23.2.0.2 ; implementation_name == "cpython"
# via -r test-requirements.in
+types-setuptools==68.1.0.0
+ # via types-cffi
typing-extensions==4.7.1
# via
# -r test-requirements.in
From 9b77c7b7162508e9ba5b24f89461e5e2cb167fbd Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Sat, 26 Aug 2023 14:45:07 +0900
Subject: [PATCH 12/19] Woes of working on Windows
---
trio/_core/_generated_instrumentation.py | 100 ++---
trio/_core/_generated_io_epoll.py | 84 ++--
trio/_core/_generated_io_kqueue.py | 152 +++----
trio/_core/_generated_io_windows.py | 198 ++++-----
trio/_core/_generated_run.py | 508 +++++++++++------------
5 files changed, 521 insertions(+), 521 deletions(-)
diff --git a/trio/_core/_generated_instrumentation.py b/trio/_core/_generated_instrumentation.py
index cc6b6335e0..605a6372f2 100644
--- a/trio/_core/_generated_instrumentation.py
+++ b/trio/_core/_generated_instrumentation.py
@@ -1,50 +1,50 @@
-# ***********************************************************
-# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
-# *************************************************************
-# Don't lint this file, generation will not format this too nicely.
-# isort: skip_file
-# fmt: off
-from __future__ import annotations
-
-from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import GLOBAL_RUN_CONTEXT
-from ._instrumentation import Instrument
-
-
-def add_instrument(instrument: Instrument) ->None:
- """Start instrumenting the current run loop with the given instrument.
-
- Args:
- instrument (trio.abc.Instrument): The instrument to activate.
-
- If ``instrument`` is already active, does nothing.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.instruments.add_instrument(instrument)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def remove_instrument(instrument: Instrument) ->None:
- """Stop instrumenting the current run loop with the given instrument.
-
- Args:
- instrument (trio.abc.Instrument): The instrument to de-activate.
-
- Raises:
- KeyError: if the instrument is not currently active. This could
- occur either because you never added it, or because you added it
- and then it raised an unhandled exception and was automatically
- deactivated.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.instruments.remove_instrument(instrument)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-# fmt: on
+# ***********************************************************
+# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
+# *************************************************************
+# Don't lint this file, generation will not format this too nicely.
+# isort: skip_file
+# fmt: off
+from __future__ import annotations
+
+from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
+from ._run import GLOBAL_RUN_CONTEXT
+from ._instrumentation import Instrument
+
+
+def add_instrument(instrument: Instrument) ->None:
+ """Start instrumenting the current run loop with the given instrument.
+
+ Args:
+ instrument (trio.abc.Instrument): The instrument to activate.
+
+ If ``instrument`` is already active, does nothing.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.instruments.add_instrument(instrument)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def remove_instrument(instrument: Instrument) ->None:
+ """Stop instrumenting the current run loop with the given instrument.
+
+ Args:
+ instrument (trio.abc.Instrument): The instrument to de-activate.
+
+ Raises:
+ KeyError: if the instrument is not currently active. This could
+ occur either because you never added it, or because you added it
+ and then it raised an unhandled exception and was automatically
+ deactivated.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.instruments.remove_instrument(instrument)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+# fmt: on
diff --git a/trio/_core/_generated_io_epoll.py b/trio/_core/_generated_io_epoll.py
index 68cb3c6001..abe49ed3ff 100644
--- a/trio/_core/_generated_io_epoll.py
+++ b/trio/_core/_generated_io_epoll.py
@@ -1,42 +1,42 @@
-# ***********************************************************
-# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
-# *************************************************************
-# Don't lint this file, generation will not format this too nicely.
-# isort: skip_file
-# fmt: off
-from __future__ import annotations
-
-from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import GLOBAL_RUN_CONTEXT
-from socket import socket
-from typing import TYPE_CHECKING
-import sys
-
-assert not TYPE_CHECKING or sys.platform=="linux"
-
-
-async def wait_readable(fd: (int | socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(fd)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_writable(fd: (int | socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(fd)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def notify_closing(fd: (int | socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(fd)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-# fmt: on
+# ***********************************************************
+# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
+# *************************************************************
+# Don't lint this file, generation will not format this too nicely.
+# isort: skip_file
+# fmt: off
+from __future__ import annotations
+
+from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
+from ._run import GLOBAL_RUN_CONTEXT
+from socket import socket
+from typing import TYPE_CHECKING
+import sys
+
+assert not TYPE_CHECKING or sys.platform=="linux"
+
+
+async def wait_readable(fd: (int | socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(fd)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_writable(fd: (int | socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(fd)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def notify_closing(fd: (int | socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(fd)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+# fmt: on
diff --git a/trio/_core/_generated_io_kqueue.py b/trio/_core/_generated_io_kqueue.py
index e1348f0915..cfcf6354c7 100644
--- a/trio/_core/_generated_io_kqueue.py
+++ b/trio/_core/_generated_io_kqueue.py
@@ -1,76 +1,76 @@
-# ***********************************************************
-# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
-# *************************************************************
-# Don't lint this file, generation will not format this too nicely.
-# isort: skip_file
-# fmt: off
-from __future__ import annotations
-
-from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import GLOBAL_RUN_CONTEXT
-from typing import Callable, ContextManager, TYPE_CHECKING
-
-if TYPE_CHECKING:
- import select
- from socket import socket
-
- from ._traps import Abort, RaiseCancelT
-
- from .. import _core
-
-import sys
-
-assert not TYPE_CHECKING or sys.platform=="darwin"
-
-
-def current_kqueue() ->select.kqueue:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.current_kqueue()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def monitor_kevent(ident: int, filter: int) ->ContextManager[_core.UnboundedQueue
- [select.kevent]]:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.monitor_kevent(ident, filter)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_kevent(ident: int, filter: int, abort_func: Callable[[
- RaiseCancelT], Abort]) ->Abort:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_kevent(ident, filter, abort_func)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_readable(fd: (int | socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(fd)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_writable(fd: (int | socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(fd)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def notify_closing(fd: (int | socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(fd)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-# fmt: on
+# ***********************************************************
+# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
+# *************************************************************
+# Don't lint this file, generation will not format this too nicely.
+# isort: skip_file
+# fmt: off
+from __future__ import annotations
+
+from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
+from ._run import GLOBAL_RUN_CONTEXT
+from typing import Callable, ContextManager, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ import select
+ from socket import socket
+
+ from ._traps import Abort, RaiseCancelT
+
+ from .. import _core
+
+import sys
+
+assert not TYPE_CHECKING or sys.platform=="darwin"
+
+
+def current_kqueue() ->select.kqueue:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.current_kqueue()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def monitor_kevent(ident: int, filter: int) ->ContextManager[_core.UnboundedQueue
+ [select.kevent]]:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.monitor_kevent(ident, filter)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_kevent(ident: int, filter: int, abort_func: Callable[[
+ RaiseCancelT], Abort]) ->Abort:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_kevent(ident, filter, abort_func)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_readable(fd: (int | socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(fd)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_writable(fd: (int | socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(fd)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def notify_closing(fd: (int | socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(fd)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+# fmt: on
diff --git a/trio/_core/_generated_io_windows.py b/trio/_core/_generated_io_windows.py
index eb2a1f3b43..c358550269 100644
--- a/trio/_core/_generated_io_windows.py
+++ b/trio/_core/_generated_io_windows.py
@@ -1,99 +1,99 @@
-# ***********************************************************
-# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
-# *************************************************************
-# Don't lint this file, generation will not format this too nicely.
-# isort: skip_file
-# fmt: off
-from __future__ import annotations
-
-from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import GLOBAL_RUN_CONTEXT
-from typing import TYPE_CHECKING, ContextManager
-
-if TYPE_CHECKING:
- import socket
- from ._windows_cffi import Handle, CData
- from typing_extensions import Buffer
-
- from ._unbounded_queue import UnboundedQueue
-import sys
-
-assert not TYPE_CHECKING or sys.platform=="win32"
-
-
-async def wait_readable(sock: (socket.socket | int)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(sock)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_writable(sock: (socket.socket | int)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(sock)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def notify_closing(handle: (Handle | int | socket.socket)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(handle)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def register_with_iocp(handle: (int | CData)) ->None:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.register_with_iocp(handle)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_overlapped(handle_: (int | CData), lpOverlapped: (CData | int)
- ) ->object:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_overlapped(handle_, lpOverlapped)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def write_overlapped(handle: (int | CData), data: Buffer, file_offset:
- int=0) ->int:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.write_overlapped(handle, data, file_offset)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def readinto_overlapped(handle: (int | CData), buffer: Buffer,
- file_offset: int=0) ->int:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.readinto_overlapped(handle, buffer, file_offset)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def current_iocp() ->int:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.current_iocp()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def monitor_completion_key() ->ContextManager[tuple[int, UnboundedQueue[object]]]:
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.io_manager.monitor_completion_key()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-# fmt: on
+# ***********************************************************
+# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
+# *************************************************************
+# Don't lint this file, generation will not format this too nicely.
+# isort: skip_file
+# fmt: off
+from __future__ import annotations
+
+from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
+from ._run import GLOBAL_RUN_CONTEXT
+from typing import TYPE_CHECKING, ContextManager
+
+if TYPE_CHECKING:
+ import socket
+ from ._windows_cffi import Handle, CData
+ from typing_extensions import Buffer
+
+ from ._unbounded_queue import UnboundedQueue
+import sys
+
+assert not TYPE_CHECKING or sys.platform=="win32"
+
+
+async def wait_readable(sock: (socket.socket | int)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(sock)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_writable(sock: (socket.socket | int)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(sock)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def notify_closing(handle: (Handle | int | socket.socket)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(handle)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def register_with_iocp(handle: (int | CData)) ->None:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.register_with_iocp(handle)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_overlapped(handle_: (int | CData), lpOverlapped: (CData | int)
+ ) ->object:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_overlapped(handle_, lpOverlapped)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def write_overlapped(handle: (int | CData), data: Buffer, file_offset:
+ int=0) ->int:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.write_overlapped(handle, data, file_offset)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def readinto_overlapped(handle: (int | CData), buffer: Buffer,
+ file_offset: int=0) ->int:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.readinto_overlapped(handle, buffer, file_offset)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def current_iocp() ->int:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.current_iocp()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def monitor_completion_key() ->ContextManager[tuple[int, UnboundedQueue[object]]]:
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.io_manager.monitor_completion_key()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+# fmt: on
diff --git a/trio/_core/_generated_run.py b/trio/_core/_generated_run.py
index ef2055b5d8..bd5abbd639 100644
--- a/trio/_core/_generated_run.py
+++ b/trio/_core/_generated_run.py
@@ -1,254 +1,254 @@
-# ***********************************************************
-# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
-# *************************************************************
-# Don't lint this file, generation will not format this too nicely.
-# isort: skip_file
-# fmt: off
-from __future__ import annotations
-
-from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
-from ._run import GLOBAL_RUN_CONTEXT
-from collections.abc import Awaitable, Callable
-from typing import Any
-
-from outcome import Outcome
-import contextvars
-
-from ._run import _NO_SEND, RunStatistics, Task
-from ._entry_queue import TrioToken
-from .._abc import Clock
-
-
-def current_statistics() ->RunStatistics:
- """Returns ``RunStatistics``, which contains run-loop-level debugging information.
-
- Currently, the following fields are defined:
-
- * ``tasks_living`` (int): The number of tasks that have been spawned
- and not yet exited.
- * ``tasks_runnable`` (int): The number of tasks that are currently
- queued on the run queue (as opposed to blocked waiting for something
- to happen).
- * ``seconds_to_next_deadline`` (float): The time until the next
- pending cancel scope deadline. May be negative if the deadline has
- expired but we haven't yet processed cancellations. May be
- :data:`~math.inf` if there are no pending deadlines.
- * ``run_sync_soon_queue_size`` (int): The number of
- unprocessed callbacks queued via
- :meth:`trio.lowlevel.TrioToken.run_sync_soon`.
- * ``io_statistics`` (object): Some statistics from Trio's I/O
- backend. This always has an attribute ``backend`` which is a string
- naming which operating-system-specific I/O backend is in use; the
- other attributes vary between backends.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.current_statistics()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def current_time() ->float:
- """Returns the current time according to Trio's internal clock.
-
- Returns:
- float: The current time.
-
- Raises:
- RuntimeError: if not inside a call to :func:`trio.run`.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.current_time()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def current_clock() ->Clock:
- """Returns the current :class:`~trio.abc.Clock`."""
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.current_clock()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def current_root_task() ->(Task | None):
- """Returns the current root :class:`Task`.
-
- This is the task that is the ultimate parent of all other tasks.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.current_root_task()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def reschedule(task: Task, next_send: Outcome[Any]=_NO_SEND) ->None:
- """Reschedule the given task with the given
- :class:`outcome.Outcome`.
-
- See :func:`wait_task_rescheduled` for the gory details.
-
- There must be exactly one call to :func:`reschedule` for every call to
- :func:`wait_task_rescheduled`. (And when counting, keep in mind that
- returning :data:`Abort.SUCCEEDED` from an abort callback is equivalent
- to calling :func:`reschedule` once.)
-
- Args:
- task (trio.lowlevel.Task): the task to be rescheduled. Must be blocked
- in a call to :func:`wait_task_rescheduled`.
- next_send (outcome.Outcome): the value (or error) to return (or
- raise) from :func:`wait_task_rescheduled`.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.reschedule(task, next_send)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def spawn_system_task(async_fn: Callable[..., Awaitable[object]], *args:
- object, name: object=None, context: (contextvars.Context | None)=None
- ) ->Task:
- """Spawn a "system" task.
-
- System tasks have a few differences from regular tasks:
-
- * They don't need an explicit nursery; instead they go into the
- internal "system nursery".
-
- * If a system task raises an exception, then it's converted into a
- :exc:`~trio.TrioInternalError` and *all* tasks are cancelled. If you
- write a system task, you should be careful to make sure it doesn't
- crash.
-
- * System tasks are automatically cancelled when the main task exits.
-
- * By default, system tasks have :exc:`KeyboardInterrupt` protection
- *enabled*. If you want your task to be interruptible by control-C,
- then you need to use :func:`disable_ki_protection` explicitly (and
- come up with some plan for what to do with a
- :exc:`KeyboardInterrupt`, given that system tasks aren't allowed to
- raise exceptions).
-
- * System tasks do not inherit context variables from their creator.
-
- Towards the end of a call to :meth:`trio.run`, after the main
- task and all system tasks have exited, the system nursery
- becomes closed. At this point, new calls to
- :func:`spawn_system_task` will raise ``RuntimeError("Nursery
- is closed to new arrivals")`` instead of creating a system
- task. It's possible to encounter this state either in
- a ``finally`` block in an async generator, or in a callback
- passed to :meth:`TrioToken.run_sync_soon` at the right moment.
-
- Args:
- async_fn: An async callable.
- args: Positional arguments for ``async_fn``. If you want to pass
- keyword arguments, use :func:`functools.partial`.
- name: The name for this task. Only used for debugging/introspection
- (e.g. ``repr(task_obj)``). If this isn't a string,
- :func:`spawn_system_task` will try to make it one. A common use
- case is if you're wrapping a function before spawning a new
- task, you might pass the original function as the ``name=`` to
- make debugging easier.
- context: An optional ``contextvars.Context`` object with context variables
- to use for this task. You would normally get a copy of the current
- context with ``context = contextvars.copy_context()`` and then you would
- pass that ``context`` object here.
-
- Returns:
- Task: the newly spawned task
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.spawn_system_task(async_fn, *args, name=name, context=context)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-def current_trio_token() ->TrioToken:
- """Retrieve the :class:`TrioToken` for the current call to
- :func:`trio.run`.
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return GLOBAL_RUN_CONTEXT.runner.current_trio_token()
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-async def wait_all_tasks_blocked(cushion: float=0.0) ->None:
- """Block until there are no runnable tasks.
-
- This is useful in testing code when you want to give other tasks a
- chance to "settle down". The calling task is blocked, and doesn't wake
- up until all other tasks are also blocked for at least ``cushion``
- seconds. (Setting a non-zero ``cushion`` is intended to handle cases
- like two tasks talking to each other over a local socket, where we
- want to ignore the potential brief moment between a send and receive
- when all tasks are blocked.)
-
- Note that ``cushion`` is measured in *real* time, not the Trio clock
- time.
-
- If there are multiple tasks blocked in :func:`wait_all_tasks_blocked`,
- then the one with the shortest ``cushion`` is the one woken (and
- this task becoming unblocked resets the timers for the remaining
- tasks). If there are multiple tasks that have exactly the same
- ``cushion``, then all are woken.
-
- You should also consider :class:`trio.testing.Sequencer`, which
- provides a more explicit way to control execution ordering within a
- test, and will often produce more readable tests.
-
- Example:
- Here's an example of one way to test that Trio's locks are fair: we
- take the lock in the parent, start a child, wait for the child to be
- blocked waiting for the lock (!), and then check that we can't
- release and immediately re-acquire the lock::
-
- async def lock_taker(lock):
- await lock.acquire()
- lock.release()
-
- async def test_lock_fairness():
- lock = trio.Lock()
- await lock.acquire()
- async with trio.open_nursery() as nursery:
- nursery.start_soon(lock_taker, lock)
- # child hasn't run yet, we have the lock
- assert lock.locked()
- assert lock._owner is trio.lowlevel.current_task()
- await trio.testing.wait_all_tasks_blocked()
- # now the child has run and is blocked on lock.acquire(), we
- # still have the lock
- assert lock.locked()
- assert lock._owner is trio.lowlevel.current_task()
- lock.release()
- try:
- # The child has a prior claim, so we can't have it
- lock.acquire_nowait()
- except trio.WouldBlock:
- assert lock._owner is not trio.lowlevel.current_task()
- print("PASS")
- else:
- print("FAIL")
-
- """
- locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
- try:
- return await GLOBAL_RUN_CONTEXT.runner.wait_all_tasks_blocked(cushion)
- except AttributeError:
- raise RuntimeError("must be called from async context")
-
-
-# fmt: on
+# ***********************************************************
+# ******* WARNING: AUTOGENERATED! ALL EDITS WILL BE LOST ******
+# *************************************************************
+# Don't lint this file, generation will not format this too nicely.
+# isort: skip_file
+# fmt: off
+from __future__ import annotations
+
+from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
+from ._run import GLOBAL_RUN_CONTEXT
+from collections.abc import Awaitable, Callable
+from typing import Any
+
+from outcome import Outcome
+import contextvars
+
+from ._run import _NO_SEND, RunStatistics, Task
+from ._entry_queue import TrioToken
+from .._abc import Clock
+
+
+def current_statistics() ->RunStatistics:
+ """Returns ``RunStatistics``, which contains run-loop-level debugging information.
+
+ Currently, the following fields are defined:
+
+ * ``tasks_living`` (int): The number of tasks that have been spawned
+ and not yet exited.
+ * ``tasks_runnable`` (int): The number of tasks that are currently
+ queued on the run queue (as opposed to blocked waiting for something
+ to happen).
+ * ``seconds_to_next_deadline`` (float): The time until the next
+ pending cancel scope deadline. May be negative if the deadline has
+ expired but we haven't yet processed cancellations. May be
+ :data:`~math.inf` if there are no pending deadlines.
+ * ``run_sync_soon_queue_size`` (int): The number of
+ unprocessed callbacks queued via
+ :meth:`trio.lowlevel.TrioToken.run_sync_soon`.
+ * ``io_statistics`` (object): Some statistics from Trio's I/O
+ backend. This always has an attribute ``backend`` which is a string
+ naming which operating-system-specific I/O backend is in use; the
+ other attributes vary between backends.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.current_statistics()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def current_time() ->float:
+ """Returns the current time according to Trio's internal clock.
+
+ Returns:
+ float: The current time.
+
+ Raises:
+ RuntimeError: if not inside a call to :func:`trio.run`.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.current_time()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def current_clock() ->Clock:
+ """Returns the current :class:`~trio.abc.Clock`."""
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.current_clock()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def current_root_task() ->(Task | None):
+ """Returns the current root :class:`Task`.
+
+ This is the task that is the ultimate parent of all other tasks.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.current_root_task()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def reschedule(task: Task, next_send: Outcome[Any]=_NO_SEND) ->None:
+ """Reschedule the given task with the given
+ :class:`outcome.Outcome`.
+
+ See :func:`wait_task_rescheduled` for the gory details.
+
+ There must be exactly one call to :func:`reschedule` for every call to
+ :func:`wait_task_rescheduled`. (And when counting, keep in mind that
+ returning :data:`Abort.SUCCEEDED` from an abort callback is equivalent
+ to calling :func:`reschedule` once.)
+
+ Args:
+ task (trio.lowlevel.Task): the task to be rescheduled. Must be blocked
+ in a call to :func:`wait_task_rescheduled`.
+ next_send (outcome.Outcome): the value (or error) to return (or
+ raise) from :func:`wait_task_rescheduled`.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.reschedule(task, next_send)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def spawn_system_task(async_fn: Callable[..., Awaitable[object]], *args:
+ object, name: object=None, context: (contextvars.Context | None)=None
+ ) ->Task:
+ """Spawn a "system" task.
+
+ System tasks have a few differences from regular tasks:
+
+ * They don't need an explicit nursery; instead they go into the
+ internal "system nursery".
+
+ * If a system task raises an exception, then it's converted into a
+ :exc:`~trio.TrioInternalError` and *all* tasks are cancelled. If you
+ write a system task, you should be careful to make sure it doesn't
+ crash.
+
+ * System tasks are automatically cancelled when the main task exits.
+
+ * By default, system tasks have :exc:`KeyboardInterrupt` protection
+ *enabled*. If you want your task to be interruptible by control-C,
+ then you need to use :func:`disable_ki_protection` explicitly (and
+ come up with some plan for what to do with a
+ :exc:`KeyboardInterrupt`, given that system tasks aren't allowed to
+ raise exceptions).
+
+ * System tasks do not inherit context variables from their creator.
+
+ Towards the end of a call to :meth:`trio.run`, after the main
+ task and all system tasks have exited, the system nursery
+ becomes closed. At this point, new calls to
+ :func:`spawn_system_task` will raise ``RuntimeError("Nursery
+ is closed to new arrivals")`` instead of creating a system
+ task. It's possible to encounter this state either in
+ a ``finally`` block in an async generator, or in a callback
+ passed to :meth:`TrioToken.run_sync_soon` at the right moment.
+
+ Args:
+ async_fn: An async callable.
+ args: Positional arguments for ``async_fn``. If you want to pass
+ keyword arguments, use :func:`functools.partial`.
+ name: The name for this task. Only used for debugging/introspection
+ (e.g. ``repr(task_obj)``). If this isn't a string,
+ :func:`spawn_system_task` will try to make it one. A common use
+ case is if you're wrapping a function before spawning a new
+ task, you might pass the original function as the ``name=`` to
+ make debugging easier.
+ context: An optional ``contextvars.Context`` object with context variables
+ to use for this task. You would normally get a copy of the current
+ context with ``context = contextvars.copy_context()`` and then you would
+ pass that ``context`` object here.
+
+ Returns:
+ Task: the newly spawned task
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.spawn_system_task(async_fn, *args, name=name, context=context)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+def current_trio_token() ->TrioToken:
+ """Retrieve the :class:`TrioToken` for the current call to
+ :func:`trio.run`.
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return GLOBAL_RUN_CONTEXT.runner.current_trio_token()
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+async def wait_all_tasks_blocked(cushion: float=0.0) ->None:
+ """Block until there are no runnable tasks.
+
+ This is useful in testing code when you want to give other tasks a
+ chance to "settle down". The calling task is blocked, and doesn't wake
+ up until all other tasks are also blocked for at least ``cushion``
+ seconds. (Setting a non-zero ``cushion`` is intended to handle cases
+ like two tasks talking to each other over a local socket, where we
+ want to ignore the potential brief moment between a send and receive
+ when all tasks are blocked.)
+
+ Note that ``cushion`` is measured in *real* time, not the Trio clock
+ time.
+
+ If there are multiple tasks blocked in :func:`wait_all_tasks_blocked`,
+ then the one with the shortest ``cushion`` is the one woken (and
+ this task becoming unblocked resets the timers for the remaining
+ tasks). If there are multiple tasks that have exactly the same
+ ``cushion``, then all are woken.
+
+ You should also consider :class:`trio.testing.Sequencer`, which
+ provides a more explicit way to control execution ordering within a
+ test, and will often produce more readable tests.
+
+ Example:
+ Here's an example of one way to test that Trio's locks are fair: we
+ take the lock in the parent, start a child, wait for the child to be
+ blocked waiting for the lock (!), and then check that we can't
+ release and immediately re-acquire the lock::
+
+ async def lock_taker(lock):
+ await lock.acquire()
+ lock.release()
+
+ async def test_lock_fairness():
+ lock = trio.Lock()
+ await lock.acquire()
+ async with trio.open_nursery() as nursery:
+ nursery.start_soon(lock_taker, lock)
+ # child hasn't run yet, we have the lock
+ assert lock.locked()
+ assert lock._owner is trio.lowlevel.current_task()
+ await trio.testing.wait_all_tasks_blocked()
+ # now the child has run and is blocked on lock.acquire(), we
+ # still have the lock
+ assert lock.locked()
+ assert lock._owner is trio.lowlevel.current_task()
+ lock.release()
+ try:
+ # The child has a prior claim, so we can't have it
+ lock.acquire_nowait()
+ except trio.WouldBlock:
+ assert lock._owner is not trio.lowlevel.current_task()
+ print("PASS")
+ else:
+ print("FAIL")
+
+ """
+ locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
+ try:
+ return await GLOBAL_RUN_CONTEXT.runner.wait_all_tasks_blocked(cushion)
+ except AttributeError:
+ raise RuntimeError("must be called from async context")
+
+
+# fmt: on
From 358d347c8285b6fe1eba900a52b139b379860510 Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Tue, 29 Aug 2023 00:32:37 +0900
Subject: [PATCH 13/19] PR comments
---
trio/_core/_generated_io_windows.py | 8 ++++----
trio/_core/_io_windows.py | 19 ++++++++++---------
trio/_core/_windows_cffi.py | 3 +--
trio/_tools/gen_exports.py | 2 +-
4 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/trio/_core/_generated_io_windows.py b/trio/_core/_generated_io_windows.py
index c358550269..337a926ecc 100644
--- a/trio/_core/_generated_io_windows.py
+++ b/trio/_core/_generated_io_windows.py
@@ -11,7 +11,7 @@
from typing import TYPE_CHECKING, ContextManager
if TYPE_CHECKING:
- import socket
+ from .._file_io import _HasFileNo
from ._windows_cffi import Handle, CData
from typing_extensions import Buffer
@@ -21,7 +21,7 @@
assert not TYPE_CHECKING or sys.platform=="win32"
-async def wait_readable(sock: (socket.socket | int)) ->None:
+async def wait_readable(sock: (_HasFileNo | int)) ->None:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(sock)
@@ -29,7 +29,7 @@ async def wait_readable(sock: (socket.socket | int)) ->None:
raise RuntimeError("must be called from async context")
-async def wait_writable(sock: (socket.socket | int)) ->None:
+async def wait_writable(sock: (_HasFileNo | int)) ->None:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(sock)
@@ -37,7 +37,7 @@ async def wait_writable(sock: (socket.socket | int)) ->None:
raise RuntimeError("must be called from async context")
-def notify_closing(handle: (Handle | int | socket.socket)) ->None:
+def notify_closing(handle: (Handle | int | _HasFileNo)) ->None:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(handle)
diff --git a/trio/_core/_io_windows.py b/trio/_core/_io_windows.py
index 25f73dd146..ba84525506 100644
--- a/trio/_core/_io_windows.py
+++ b/trio/_core/_io_windows.py
@@ -44,6 +44,7 @@
if TYPE_CHECKING:
from typing_extensions import Buffer, TypeAlias
+ from .._file_io import _HasFileNo
from ._traps import Abort, RaiseCancelT
from ._unbounded_queue import UnboundedQueue
@@ -244,8 +245,8 @@ class CKeys(enum.IntEnum):
# operation and start a new one.
@attr.s(slots=True, eq=False)
class AFDWaiters:
- read_task: None = attr.ib(default=None)
- write_task: None = attr.ib(default=None)
+ read_task: Optional[_core.Task] = attr.ib(default=None)
+ write_task: Optional[_core.Task] = attr.ib(default=None)
current_op: Optional[AFDPollOp] = attr.ib(default=None)
@@ -303,7 +304,7 @@ def _check(success: T) -> T:
def _get_underlying_socket(
- sock: socket.socket | int | Handle, *, which: WSAIoctls = WSAIoctls.SIO_BASE_HANDLE
+ sock: _HasFileNo | int | Handle, *, which: WSAIoctls = WSAIoctls.SIO_BASE_HANDLE
) -> Handle:
if hasattr(sock, "fileno"):
sock = sock.fileno()
@@ -326,7 +327,7 @@ def _get_underlying_socket(
return Handle(base_ptr[0])
-def _get_base_socket(sock: socket.socket | int | Handle) -> Handle:
+def _get_base_socket(sock: _HasFileNo | int | Handle) -> Handle:
# There is a development kit for LSPs called Komodia Redirector.
# It does some unusual (some might say evil) things like intercepting
# SIO_BASE_HANDLE (fails) and SIO_BSP_HANDLE_SELECT (returns the same
@@ -407,7 +408,7 @@ def _afd_helper_handle() -> Handle:
@attr.s(frozen=True)
class CompletionKeyEventInfo:
- lpOverlapped: None = attr.ib()
+ lpOverlapped: CData = attr.ib()
dwNumberOfBytesTransferred: int = attr.ib()
@@ -706,7 +707,7 @@ def _refresh_afd(self, base_handle: Handle) -> None:
if afd_group.size >= MAX_AFD_GROUP_SIZE:
self._vacant_afd_groups.remove(afd_group)
- async def _afd_poll(self, sock: socket.socket | int, mode: str) -> None:
+ async def _afd_poll(self, sock: _HasFileNo | int, mode: str) -> None:
base_handle = _get_base_socket(sock)
waiters = self._afd_waiters.get(base_handle)
if waiters is None:
@@ -727,15 +728,15 @@ def abort_fn(_: RaiseCancelT) -> Abort:
await _core.wait_task_rescheduled(abort_fn)
@_public
- async def wait_readable(self, sock: socket.socket | int) -> None:
+ async def wait_readable(self, sock: _HasFileNo | int) -> None:
await self._afd_poll(sock, "read_task")
@_public
- async def wait_writable(self, sock: socket.socket | int) -> None:
+ async def wait_writable(self, sock: _HasFileNo | int) -> None:
await self._afd_poll(sock, "write_task")
@_public
- def notify_closing(self, handle: Handle | int | socket.socket) -> None:
+ def notify_closing(self, handle: Handle | int | _HasFileNo) -> None:
handle = _get_base_socket(handle)
waiters = self._afd_waiters.get(handle)
if waiters is not None:
diff --git a/trio/_core/_windows_cffi.py b/trio/_core/_windows_cffi.py
index bc99aa9f21..8d8379e07d 100644
--- a/trio/_core/_windows_cffi.py
+++ b/trio/_core/_windows_cffi.py
@@ -2,7 +2,6 @@
import enum
import re
-import sys
from typing import TYPE_CHECKING, NewType, Protocol, cast
if TYPE_CHECKING:
@@ -517,7 +516,7 @@ def raise_winerror(
filename: str | None = None,
filename2: str | None = None,
) -> NoReturn:
- assert sys.platform == "win32" # this should work starting next mypy release
+ # assert sys.platform == "win32" # TODO: make this work in MyPy
if winerror is None:
err = ffi.getwinerror() # type: ignore[attr-defined,unused-ignore]
if err is None:
diff --git a/trio/_tools/gen_exports.py b/trio/_tools/gen_exports.py
index 5e2cc8c9de..21ed98191b 100755
--- a/trio/_tools/gen_exports.py
+++ b/trio/_tools/gen_exports.py
@@ -287,7 +287,7 @@ def main() -> None: # pragma: no cover
from typing import TYPE_CHECKING, ContextManager
if TYPE_CHECKING:
- import socket
+ from .._file_io import _HasFileNo
from ._windows_cffi import Handle, CData
from typing_extensions import Buffer
From cf46b8c48e7f6bb6e37c6a51a977ef9919579264 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 29 Aug 2023 15:26:00 +0200
Subject: [PATCH 14/19] regen io_windows and verify_types
---
trio/_core/_generated_io_windows.py | 34 ++++++++++++++++-----------
trio/_tests/verify_types_windows.json | 2 +-
2 files changed, 21 insertions(+), 15 deletions(-)
diff --git a/trio/_core/_generated_io_windows.py b/trio/_core/_generated_io_windows.py
index 9fa4c1befd..ca444373fa 100644
--- a/trio/_core/_generated_io_windows.py
+++ b/trio/_core/_generated_io_windows.py
@@ -3,7 +3,6 @@
# *************************************************************
from __future__ import annotations
-import sys
from typing import TYPE_CHECKING, ContextManager
from ._ki import LOCALS_KEY_KI_PROTECTION_ENABLED
@@ -16,10 +15,12 @@
from ._unbounded_queue import UnboundedQueue
+import sys
+
assert not TYPE_CHECKING or sys.platform == "win32"
-async def wait_readable(sock: (_HasFileNo | int)) ->None:
+async def wait_readable(sock: (_HasFileNo | int)) -> None:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_readable(sock)
@@ -27,7 +28,7 @@ async def wait_readable(sock: (_HasFileNo | int)) ->None:
raise RuntimeError("must be called from async context")
-async def wait_writable(sock: (_HasFileNo | int)) ->None:
+async def wait_writable(sock: (_HasFileNo | int)) -> None:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_writable(sock)
@@ -35,7 +36,7 @@ async def wait_writable(sock: (_HasFileNo | int)) ->None:
raise RuntimeError("must be called from async context")
-def notify_closing(handle: (Handle | int | _HasFileNo)) ->None:
+def notify_closing(handle: (Handle | int | _HasFileNo)) -> None:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return GLOBAL_RUN_CONTEXT.runner.io_manager.notify_closing(handle)
@@ -43,7 +44,7 @@ def notify_closing(handle: (Handle | int | _HasFileNo)) ->None:
raise RuntimeError("must be called from async context")
-def register_with_iocp(handle: (int | CData)) ->None:
+def register_with_iocp(handle: (int | CData)) -> None:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return GLOBAL_RUN_CONTEXT.runner.io_manager.register_with_iocp(handle)
@@ -51,17 +52,21 @@ def register_with_iocp(handle: (int | CData)) ->None:
raise RuntimeError("must be called from async context")
-async def wait_overlapped(handle_: (int | CData), lpOverlapped: (CData | int)
- ) ->object:
+async def wait_overlapped(
+ handle_: (int | CData), lpOverlapped: (CData | int)
+) -> object:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
- return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_overlapped(handle_, lpOverlapped)
+ return await GLOBAL_RUN_CONTEXT.runner.io_manager.wait_overlapped(
+ handle_, lpOverlapped
+ )
except AttributeError:
raise RuntimeError("must be called from async context")
-async def write_overlapped(handle: (int | CData), data: Buffer, file_offset:
- int=0) ->int:
+async def write_overlapped(
+ handle: (int | CData), data: Buffer, file_offset: int = 0
+) -> int:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return await GLOBAL_RUN_CONTEXT.runner.io_manager.write_overlapped(
@@ -71,8 +76,9 @@ async def write_overlapped(handle: (int | CData), data: Buffer, file_offset:
raise RuntimeError("must be called from async context")
-async def readinto_overlapped(handle: (int | CData), buffer: Buffer,
- file_offset: int=0) ->int:
+async def readinto_overlapped(
+ handle: (int | CData), buffer: Buffer, file_offset: int = 0
+) -> int:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return await GLOBAL_RUN_CONTEXT.runner.io_manager.readinto_overlapped(
@@ -82,7 +88,7 @@ async def readinto_overlapped(handle: (int | CData), buffer: Buffer,
raise RuntimeError("must be called from async context")
-def current_iocp() ->int:
+def current_iocp() -> int:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return GLOBAL_RUN_CONTEXT.runner.io_manager.current_iocp()
@@ -90,7 +96,7 @@ def current_iocp() ->int:
raise RuntimeError("must be called from async context")
-def monitor_completion_key() ->ContextManager[tuple[int, UnboundedQueue[object]]]:
+def monitor_completion_key() -> ContextManager[tuple[int, UnboundedQueue[object]]]:
locals()[LOCALS_KEY_KI_PROTECTION_ENABLED] = True
try:
return GLOBAL_RUN_CONTEXT.runner.io_manager.monitor_completion_key()
diff --git a/trio/_tests/verify_types_windows.json b/trio/_tests/verify_types_windows.json
index f059f71476..238c428ec6 100644
--- a/trio/_tests/verify_types_windows.json
+++ b/trio/_tests/verify_types_windows.json
@@ -96,7 +96,7 @@
],
"otherSymbolCounts": {
"withAmbiguousType": 0,
- "withKnownType": 676,
+ "withKnownType": 678,
"withUnknownType": 0
},
"packageName": "trio"
From 5801c8761311c59a9c492c8540bc18eb51806062 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 29 Aug 2023 16:56:02 +0200
Subject: [PATCH 15/19] remove redundant Mapping import
---
trio/_tests/check_type_completeness.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/trio/_tests/check_type_completeness.py b/trio/_tests/check_type_completeness.py
index 81c0993546..1352926be3 100755
--- a/trio/_tests/check_type_completeness.py
+++ b/trio/_tests/check_type_completeness.py
@@ -8,7 +8,6 @@
import sys
from collections.abc import Mapping
from pathlib import Path
-from typing import Mapping
# the result file is not marked in MANIFEST.in so it's not included in the package
failed = False
From a90e4c9f92e38580a3d1ce3a3826aff25514d272 Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Wed, 13 Sep 2023 06:51:25 +0900
Subject: [PATCH 16/19] Attempt to fix some type errors
---
trio/_core/_tests/test_windows.py | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/trio/_core/_tests/test_windows.py b/trio/_core/_tests/test_windows.py
index 7beb59cc21..f0961cf9e4 100644
--- a/trio/_core/_tests/test_windows.py
+++ b/trio/_core/_tests/test_windows.py
@@ -15,7 +15,9 @@
# Mark all the tests in this file as being windows-only
pytestmark = pytest.mark.skipif(not on_windows, reason="windows only")
-assert sys.platform == "win32" or not TYPE_CHECKING # Skip type checking on Windows
+assert (
+ sys.platform == "win32" or not TYPE_CHECKING
+) # Skip type checking when not on Windows
from ... import _core, sleep
from ...testing import wait_all_tasks_blocked
@@ -25,6 +27,7 @@
from .._windows_cffi import (
INVALID_HANDLE_VALUE,
FileFlags,
+ Handle,
ffi,
kernel32,
raise_winerror,
@@ -73,8 +76,10 @@ def test_winerror(monkeypatch: pytest.MonkeyPatch) -> None:
# then we filter out the warning.
@pytest.mark.filterwarnings("ignore:.*UnboundedQueue:trio.TrioDeprecationWarning")
async def test_completion_key_listen() -> None:
+ from .. import _io_windows
+
async def post(key: int) -> None:
- iocp = ffi.cast("HANDLE", _core.current_iocp())
+ iocp = Handle(ffi.cast("HANDLE", _core.current_iocp()))
for i in range(10):
print("post", i)
if i % 3 == 0:
@@ -90,6 +95,7 @@ async def post(key: int) -> None:
async for batch in queue: # pragma: no branch
print("got some", batch)
for info in batch:
+ assert isinstance(info, _io_windows.CompletionKeyEventInfo)
assert info.lpOverlapped == 0
assert info.dwNumberOfBytesTransferred == i
i += 1
@@ -153,8 +159,8 @@ def pipe_with_overlapped_read() -> Generator[tuple[BufferedWriter, int], None, N
write_fd = msvcrt.open_osfhandle(write_handle, 0)
yield os.fdopen(write_fd, "wb", closefd=False), read_handle
finally:
- kernel32.CloseHandle(ffi.cast("HANDLE", read_handle))
- kernel32.CloseHandle(ffi.cast("HANDLE", write_handle))
+ kernel32.CloseHandle(Handle(ffi.cast("HANDLE", read_handle)))
+ kernel32.CloseHandle(Handle(ffi.cast("HANDLE", write_handle)))
@restore_unraisablehook()
From 0737bc70d4615737306ed91eac090532c06cfe73 Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Wed, 13 Sep 2023 06:56:48 +0900
Subject: [PATCH 17/19] Oops; update type completeness file
---
trio/_tests/verify_types_windows.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/trio/_tests/verify_types_windows.json b/trio/_tests/verify_types_windows.json
index 6bda12e278..2bb37a9891 100644
--- a/trio/_tests/verify_types_windows.json
+++ b/trio/_tests/verify_types_windows.json
@@ -96,7 +96,7 @@
],
"otherSymbolCounts": {
"withAmbiguousType": 0,
- "withKnownType": 677,
+ "withKnownType": 682,
"withUnknownType": 0
},
"packageName": "trio"
From 698776db3beb984a1f112ffbbfb329188e3fd191 Mon Sep 17 00:00:00 2001
From: A5rocks
Date: Wed, 13 Sep 2023 13:39:26 +0900
Subject: [PATCH 18/19] PR review pass 1
---
trio/_core/_windows_cffi.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/trio/_core/_windows_cffi.py b/trio/_core/_windows_cffi.py
index 8d8379e07d..d411770971 100644
--- a/trio/_core/_windows_cffi.py
+++ b/trio/_core/_windows_cffi.py
@@ -517,6 +517,9 @@ def raise_winerror(
filename2: str | None = None,
) -> NoReturn:
# assert sys.platform == "win32" # TODO: make this work in MyPy
+ # ... in the meanwhile, ffi.getwinerror() is undefined on non-Windows, necessitating the type
+ # ignores.
+
if winerror is None:
err = ffi.getwinerror() # type: ignore[attr-defined,unused-ignore]
if err is None:
From bc9cbb603faa2448a05980c12a461e69a1acae98 Mon Sep 17 00:00:00 2001
From: CoolCat467 <52022020+CoolCat467@users.noreply.github.com>
Date: Fri, 22 Sep 2023 18:54:47 -0500
Subject: [PATCH 19/19] Update `verify_types_windows.json`
---
trio/_tests/verify_types_windows.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/trio/_tests/verify_types_windows.json b/trio/_tests/verify_types_windows.json
index 8f3dde113d..bab6797c02 100644
--- a/trio/_tests/verify_types_windows.json
+++ b/trio/_tests/verify_types_windows.json
@@ -96,7 +96,7 @@
],
"otherSymbolCounts": {
"withAmbiguousType": 0,
- "withKnownType": 735,
+ "withKnownType": 680,
"withUnknownType": 0
},
"packageName": "trio"