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"