@ajksunkang-aios/kgraph-linux-x64 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/bin/kgraph-launcher +15 -3
  2. package/lib/kgraph/scripts/build-bundle.sh +17 -4
  3. package/lib/site-packages/outcome/__init__.py +20 -0
  4. package/lib/site-packages/outcome/_impl.py +239 -0
  5. package/lib/site-packages/outcome/_util.py +33 -0
  6. package/lib/site-packages/outcome/_version.py +7 -0
  7. package/lib/site-packages/outcome/py.typed +0 -0
  8. package/lib/site-packages/outcome-1.3.0.post0.dist-info/INSTALLER +1 -0
  9. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE +3 -0
  10. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.APACHE2 +202 -0
  11. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.MIT +20 -0
  12. package/lib/site-packages/outcome-1.3.0.post0.dist-info/METADATA +63 -0
  13. package/lib/site-packages/outcome-1.3.0.post0.dist-info/RECORD +13 -0
  14. package/lib/site-packages/outcome-1.3.0.post0.dist-info/WHEEL +6 -0
  15. package/lib/site-packages/outcome-1.3.0.post0.dist-info/top_level.txt +1 -0
  16. package/lib/site-packages/sniffio/__init__.py +17 -0
  17. package/lib/site-packages/sniffio/_impl.py +95 -0
  18. package/lib/site-packages/sniffio/_tests/__init__.py +0 -0
  19. package/lib/site-packages/sniffio/_tests/test_sniffio.py +84 -0
  20. package/lib/site-packages/sniffio/_version.py +3 -0
  21. package/lib/site-packages/sniffio/py.typed +0 -0
  22. package/lib/site-packages/sniffio-1.3.1.dist-info/INSTALLER +1 -0
  23. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE +3 -0
  24. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 +202 -0
  25. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.MIT +20 -0
  26. package/lib/site-packages/sniffio-1.3.1.dist-info/METADATA +104 -0
  27. package/lib/site-packages/sniffio-1.3.1.dist-info/RECORD +14 -0
  28. package/lib/site-packages/sniffio-1.3.1.dist-info/WHEEL +5 -0
  29. package/lib/site-packages/sniffio-1.3.1.dist-info/top_level.txt +1 -0
  30. package/lib/site-packages/sortedcontainers/__init__.py +74 -0
  31. package/lib/site-packages/sortedcontainers/sorteddict.py +812 -0
  32. package/lib/site-packages/sortedcontainers/sortedlist.py +2646 -0
  33. package/lib/site-packages/sortedcontainers/sortedset.py +733 -0
  34. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/INSTALLER +1 -0
  35. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/LICENSE +13 -0
  36. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/METADATA +264 -0
  37. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/RECORD +10 -0
  38. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/WHEEL +6 -0
  39. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/top_level.txt +1 -0
  40. package/lib/site-packages/trio/__init__.py +133 -0
  41. package/lib/site-packages/trio/__main__.py +3 -0
  42. package/lib/site-packages/trio/_abc.py +714 -0
  43. package/lib/site-packages/trio/_channel.py +610 -0
  44. package/lib/site-packages/trio/_core/__init__.py +94 -0
  45. package/lib/site-packages/trio/_core/_asyncgens.py +243 -0
  46. package/lib/site-packages/trio/_core/_concat_tb.py +26 -0
  47. package/lib/site-packages/trio/_core/_entry_queue.py +223 -0
  48. package/lib/site-packages/trio/_core/_exceptions.py +169 -0
  49. package/lib/site-packages/trio/_core/_generated_instrumentation.py +50 -0
  50. package/lib/site-packages/trio/_core/_generated_io_epoll.py +98 -0
  51. package/lib/site-packages/trio/_core/_generated_io_kqueue.py +153 -0
  52. package/lib/site-packages/trio/_core/_generated_io_windows.py +204 -0
  53. package/lib/site-packages/trio/_core/_generated_run.py +269 -0
  54. package/lib/site-packages/trio/_core/_generated_windows_ffi.py +10 -0
  55. package/lib/site-packages/trio/_core/_instrumentation.py +117 -0
  56. package/lib/site-packages/trio/_core/_io_common.py +31 -0
  57. package/lib/site-packages/trio/_core/_io_epoll.py +385 -0
  58. package/lib/site-packages/trio/_core/_io_kqueue.py +292 -0
  59. package/lib/site-packages/trio/_core/_io_windows.py +1036 -0
  60. package/lib/site-packages/trio/_core/_ki.py +271 -0
  61. package/lib/site-packages/trio/_core/_local.py +104 -0
  62. package/lib/site-packages/trio/_core/_mock_clock.py +165 -0
  63. package/lib/site-packages/trio/_core/_parking_lot.py +317 -0
  64. package/lib/site-packages/trio/_core/_run.py +3148 -0
  65. package/lib/site-packages/trio/_core/_run_context.py +15 -0
  66. package/lib/site-packages/trio/_core/_tests/__init__.py +0 -0
  67. package/lib/site-packages/trio/_core/_tests/test_asyncgen.py +339 -0
  68. package/lib/site-packages/trio/_core/_tests/test_cancelled.py +222 -0
  69. package/lib/site-packages/trio/_core/_tests/test_exceptiongroup_gc.py +103 -0
  70. package/lib/site-packages/trio/_core/_tests/test_guest_mode.py +755 -0
  71. package/lib/site-packages/trio/_core/_tests/test_instrumentation.py +315 -0
  72. package/lib/site-packages/trio/_core/_tests/test_io.py +522 -0
  73. package/lib/site-packages/trio/_core/_tests/test_ki.py +703 -0
  74. package/lib/site-packages/trio/_core/_tests/test_local.py +118 -0
  75. package/lib/site-packages/trio/_core/_tests/test_mock_clock.py +193 -0
  76. package/lib/site-packages/trio/_core/_tests/test_parking_lot.py +389 -0
  77. package/lib/site-packages/trio/_core/_tests/test_run.py +3024 -0
  78. package/lib/site-packages/trio/_core/_tests/test_thread_cache.py +227 -0
  79. package/lib/site-packages/trio/_core/_tests/test_tutil.py +13 -0
  80. package/lib/site-packages/trio/_core/_tests/test_unbounded_queue.py +154 -0
  81. package/lib/site-packages/trio/_core/_tests/test_windows.py +305 -0
  82. package/lib/site-packages/trio/_core/_tests/tutil.py +117 -0
  83. package/lib/site-packages/trio/_core/_tests/type_tests/nursery_start.py +79 -0
  84. package/lib/site-packages/trio/_core/_tests/type_tests/run.py +51 -0
  85. package/lib/site-packages/trio/_core/_thread_cache.py +317 -0
  86. package/lib/site-packages/trio/_core/_traps.py +318 -0
  87. package/lib/site-packages/trio/_core/_unbounded_queue.py +163 -0
  88. package/lib/site-packages/trio/_core/_wakeup_socketpair.py +75 -0
  89. package/lib/site-packages/trio/_core/_windows_cffi.py +313 -0
  90. package/lib/site-packages/trio/_deprecate.py +171 -0
  91. package/lib/site-packages/trio/_dtls.py +1380 -0
  92. package/lib/site-packages/trio/_file_io.py +513 -0
  93. package/lib/site-packages/trio/_highlevel_generic.py +125 -0
  94. package/lib/site-packages/trio/_highlevel_open_tcp_listeners.py +251 -0
  95. package/lib/site-packages/trio/_highlevel_open_tcp_stream.py +397 -0
  96. package/lib/site-packages/trio/_highlevel_open_unix_stream.py +65 -0
  97. package/lib/site-packages/trio/_highlevel_serve_listeners.py +148 -0
  98. package/lib/site-packages/trio/_highlevel_socket.py +423 -0
  99. package/lib/site-packages/trio/_highlevel_ssl_helpers.py +180 -0
  100. package/lib/site-packages/trio/_path.py +289 -0
  101. package/lib/site-packages/trio/_repl.py +159 -0
  102. package/lib/site-packages/trio/_signals.py +185 -0
  103. package/lib/site-packages/trio/_socket.py +1326 -0
  104. package/lib/site-packages/trio/_ssl.py +964 -0
  105. package/lib/site-packages/trio/_subprocess.py +1178 -0
  106. package/lib/site-packages/trio/_subprocess_platform/__init__.py +123 -0
  107. package/lib/site-packages/trio/_subprocess_platform/kqueue.py +48 -0
  108. package/lib/site-packages/trio/_subprocess_platform/waitid.py +113 -0
  109. package/lib/site-packages/trio/_subprocess_platform/windows.py +11 -0
  110. package/lib/site-packages/trio/_sync.py +908 -0
  111. package/lib/site-packages/trio/_tests/__init__.py +0 -0
  112. package/lib/site-packages/trio/_tests/astrill-codesigning-cert.cer +0 -0
  113. package/lib/site-packages/trio/_tests/check_type_completeness.py +247 -0
  114. package/lib/site-packages/trio/_tests/module_with_deprecations.py +22 -0
  115. package/lib/site-packages/trio/_tests/pytest_plugin.py +54 -0
  116. package/lib/site-packages/trio/_tests/test_abc.py +72 -0
  117. package/lib/site-packages/trio/_tests/test_channel.py +750 -0
  118. package/lib/site-packages/trio/_tests/test_contextvars.py +56 -0
  119. package/lib/site-packages/trio/_tests/test_deprecate.py +277 -0
  120. package/lib/site-packages/trio/_tests/test_deprecate_strict_exception_groups_false.py +64 -0
  121. package/lib/site-packages/trio/_tests/test_dtls.py +950 -0
  122. package/lib/site-packages/trio/_tests/test_exports.py +626 -0
  123. package/lib/site-packages/trio/_tests/test_fakenet.py +317 -0
  124. package/lib/site-packages/trio/_tests/test_file_io.py +269 -0
  125. package/lib/site-packages/trio/_tests/test_highlevel_generic.py +98 -0
  126. package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_listeners.py +419 -0
  127. package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_stream.py +693 -0
  128. package/lib/site-packages/trio/_tests/test_highlevel_open_unix_stream.py +86 -0
  129. package/lib/site-packages/trio/_tests/test_highlevel_serve_listeners.py +186 -0
  130. package/lib/site-packages/trio/_tests/test_highlevel_socket.py +336 -0
  131. package/lib/site-packages/trio/_tests/test_highlevel_ssl_helpers.py +169 -0
  132. package/lib/site-packages/trio/_tests/test_path.py +279 -0
  133. package/lib/site-packages/trio/_tests/test_repl.py +428 -0
  134. package/lib/site-packages/trio/_tests/test_scheduler_determinism.py +47 -0
  135. package/lib/site-packages/trio/_tests/test_signals.py +186 -0
  136. package/lib/site-packages/trio/_tests/test_socket.py +1253 -0
  137. package/lib/site-packages/trio/_tests/test_ssl.py +1371 -0
  138. package/lib/site-packages/trio/_tests/test_subprocess.py +767 -0
  139. package/lib/site-packages/trio/_tests/test_sync.py +735 -0
  140. package/lib/site-packages/trio/_tests/test_testing.py +682 -0
  141. package/lib/site-packages/trio/_tests/test_testing_raisesgroup.py +1128 -0
  142. package/lib/site-packages/trio/_tests/test_threads.py +1173 -0
  143. package/lib/site-packages/trio/_tests/test_timeouts.py +281 -0
  144. package/lib/site-packages/trio/_tests/test_tracing.py +88 -0
  145. package/lib/site-packages/trio/_tests/test_trio.py +8 -0
  146. package/lib/site-packages/trio/_tests/test_unix_pipes.py +288 -0
  147. package/lib/site-packages/trio/_tests/test_util.py +349 -0
  148. package/lib/site-packages/trio/_tests/test_wait_for_object.py +225 -0
  149. package/lib/site-packages/trio/_tests/test_windows_pipes.py +112 -0
  150. package/lib/site-packages/trio/_tests/tools/__init__.py +0 -0
  151. package/lib/site-packages/trio/_tests/tools/test_gen_exports.py +179 -0
  152. package/lib/site-packages/trio/_tests/tools/test_mypy_annotate.py +140 -0
  153. package/lib/site-packages/trio/_tests/tools/test_sync_requirements.py +80 -0
  154. package/lib/site-packages/trio/_tests/type_tests/check_wraps.py +9 -0
  155. package/lib/site-packages/trio/_tests/type_tests/open_memory_channel.py +4 -0
  156. package/lib/site-packages/trio/_tests/type_tests/path.py +140 -0
  157. package/lib/site-packages/trio/_tests/type_tests/subprocesses.py +23 -0
  158. package/lib/site-packages/trio/_tests/type_tests/task_status.py +29 -0
  159. package/lib/site-packages/trio/_threads.py +610 -0
  160. package/lib/site-packages/trio/_timeouts.py +197 -0
  161. package/lib/site-packages/trio/_tools/__init__.py +0 -0
  162. package/lib/site-packages/trio/_tools/gen_exports.py +401 -0
  163. package/lib/site-packages/trio/_tools/mypy_annotate.py +126 -0
  164. package/lib/site-packages/trio/_tools/sync_requirements.py +98 -0
  165. package/lib/site-packages/trio/_tools/windows_ffi_build.py +220 -0
  166. package/lib/site-packages/trio/_unix_pipes.py +197 -0
  167. package/lib/site-packages/trio/_util.py +385 -0
  168. package/lib/site-packages/trio/_version.py +3 -0
  169. package/lib/site-packages/trio/_wait_for_object.py +67 -0
  170. package/lib/site-packages/trio/_windows_pipes.py +144 -0
  171. package/lib/site-packages/trio/abc.py +23 -0
  172. package/lib/site-packages/trio/from_thread.py +13 -0
  173. package/lib/site-packages/trio/lowlevel.py +95 -0
  174. package/lib/site-packages/trio/py.typed +0 -0
  175. package/lib/site-packages/trio/socket.py +602 -0
  176. package/lib/site-packages/trio/testing/__init__.py +58 -0
  177. package/lib/site-packages/trio/testing/_check_streams.py +570 -0
  178. package/lib/site-packages/trio/testing/_checkpoints.py +69 -0
  179. package/lib/site-packages/trio/testing/_fake_net.py +584 -0
  180. package/lib/site-packages/trio/testing/_memory_streams.py +633 -0
  181. package/lib/site-packages/trio/testing/_network.py +36 -0
  182. package/lib/site-packages/trio/testing/_raises_group.py +1015 -0
  183. package/lib/site-packages/trio/testing/_sequencer.py +87 -0
  184. package/lib/site-packages/trio/testing/_trio_test.py +50 -0
  185. package/lib/site-packages/trio/to_thread.py +4 -0
  186. package/lib/site-packages/trio-0.33.0.dist-info/INSTALLER +1 -0
  187. package/lib/site-packages/trio-0.33.0.dist-info/METADATA +186 -0
  188. package/lib/site-packages/trio-0.33.0.dist-info/RECORD +156 -0
  189. package/lib/site-packages/trio-0.33.0.dist-info/REQUESTED +0 -0
  190. package/lib/site-packages/trio-0.33.0.dist-info/WHEEL +5 -0
  191. package/lib/site-packages/trio-0.33.0.dist-info/entry_points.txt +2 -0
  192. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE +3 -0
  193. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.APACHE2 +202 -0
  194. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.MIT +22 -0
  195. package/lib/site-packages/trio-0.33.0.dist-info/top_level.txt +1 -0
  196. package/package.json +1 -1
@@ -0,0 +1,1036 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import itertools
5
+ import socket
6
+ import sys
7
+ from contextlib import contextmanager
8
+ from typing import TYPE_CHECKING, Literal, Protocol, TypeAlias, TypeVar, cast
9
+
10
+ import attrs
11
+ from outcome import Value
12
+
13
+ from .. import _core
14
+ from ._io_common import wake_all
15
+ from ._run import _public
16
+ from ._windows_cffi import (
17
+ INVALID_HANDLE_VALUE,
18
+ AFDPollFlags,
19
+ CData,
20
+ CompletionModes,
21
+ CType,
22
+ ErrorCodes,
23
+ FileFlags,
24
+ Handle,
25
+ IoControlCodes,
26
+ WSAIoctls,
27
+ _handle,
28
+ _Overlapped,
29
+ ffi,
30
+ kernel32,
31
+ ntdll,
32
+ raise_winerror,
33
+ ws2_32,
34
+ )
35
+
36
+ if TYPE_CHECKING:
37
+ from collections.abc import Callable, Iterator
38
+
39
+ from typing_extensions import Buffer
40
+
41
+ from .._file_io import _HasFileNo
42
+ from ._traps import Abort, RaiseCancelT
43
+ from ._unbounded_queue import UnboundedQueue
44
+
45
+ EventResult: TypeAlias = int
46
+ T = TypeVar("T")
47
+
48
+ # There's a lot to be said about the overall design of a Windows event
49
+ # loop. See
50
+ #
51
+ # https://github.com/python-trio/trio/issues/52
52
+ #
53
+ # for discussion. This now just has some lower-level notes:
54
+ #
55
+ # How IOCP fits together:
56
+ #
57
+ # The general model is that you call some function like ReadFile or WriteFile
58
+ # to tell the kernel that you want it to perform some operation, and the
59
+ # kernel goes off and does that in the background, then at some point later it
60
+ # sends you a notification that the operation is complete. There are some more
61
+ # exotic APIs that don't quite fit this pattern, but most APIs do.
62
+ #
63
+ # Each background operation is tracked using an OVERLAPPED struct, that
64
+ # uniquely identifies that particular operation.
65
+ #
66
+ # An "IOCP" (or "I/O completion port") is an object that lets the kernel send
67
+ # us these notifications -- basically it's just a kernel->userspace queue.
68
+ #
69
+ # Each IOCP notification is represented by an OVERLAPPED_ENTRY struct, which
70
+ # contains 3 fields:
71
+ # - The "completion key". This is an opaque integer that we pick, and use
72
+ # however is convenient.
73
+ # - pointer to the OVERLAPPED struct for the completed operation.
74
+ # - dwNumberOfBytesTransferred (an integer).
75
+ #
76
+ # And in addition, for regular I/O, the OVERLAPPED structure gets filled in
77
+ # with:
78
+ # - result code (named "Internal")
79
+ # - number of bytes transferred (named "InternalHigh"); usually redundant
80
+ # with dwNumberOfBytesTransferred.
81
+ #
82
+ # There are also some other entries in OVERLAPPED which only matter on input:
83
+ # - Offset and OffsetHigh which are inputs to {Read,Write}File and
84
+ # otherwise always zero
85
+ # - hEvent which is for if you aren't using IOCP; we always set it to zero.
86
+ #
87
+ # That describes the usual pattern for operations and the usual meaning of
88
+ # these struct fields, but really these are just some arbitrary chunks of
89
+ # bytes that get passed back and forth, so some operations like to overload
90
+ # them to mean something else.
91
+ #
92
+ # You can also directly queue an OVERLAPPED_ENTRY object to an IOCP by calling
93
+ # PostQueuedCompletionStatus. When you use this you get to set all the
94
+ # OVERLAPPED_ENTRY fields to arbitrary values.
95
+ #
96
+ # You can request to cancel any operation if you know which handle it was
97
+ # issued on + the OVERLAPPED struct that identifies it (via CancelIoEx). This
98
+ # request might fail because the operation has already completed, or it might
99
+ # be queued to happen in the background, so you only find out whether it
100
+ # succeeded or failed later, when we get back the notification for the
101
+ # operation being complete.
102
+ #
103
+ # There are three types of operations that we support:
104
+ #
105
+ # == Regular I/O operations on handles (e.g. files or named pipes) ==
106
+ #
107
+ # Implemented by: register_with_iocp, wait_overlapped
108
+ #
109
+ # To use these, you have to register the handle with your IOCP first. Once
110
+ # it's registered, any operations on that handle will automatically send
111
+ # completion events to that IOCP, with a completion key that you specify *when
112
+ # the handle is registered* (so you can't use different completion keys for
113
+ # different operations).
114
+ #
115
+ # We give these two dedicated completion keys: CKeys.WAIT_OVERLAPPED for
116
+ # regular operations, and CKeys.LATE_CANCEL that's used to make
117
+ # wait_overlapped cancellable even if the user forgot to call
118
+ # register_with_iocp. The problem here is that after we request the cancel,
119
+ # wait_overlapped keeps blocking until it sees the completion notification...
120
+ # but if the user forgot to register_with_iocp, then the completion will never
121
+ # come, so the cancellation will never resolve. To avoid this, whenever we try
122
+ # to cancel an I/O operation and the cancellation fails, we use
123
+ # PostQueuedCompletionStatus to send a CKeys.LATE_CANCEL notification. If this
124
+ # arrives before the real completion, we assume the user forgot to call
125
+ # register_with_iocp on their handle, and raise an error accordingly.
126
+ #
127
+ # == Socket state notifications ==
128
+ #
129
+ # Implemented by: wait_readable, wait_writable
130
+ #
131
+ # The public APIs that windows provides for this are all really awkward and
132
+ # don't integrate with IOCP. So we drop down to a lower level, and talk
133
+ # directly to the socket device driver in the kernel, which is called "AFD".
134
+ # Unfortunately, this is a totally undocumented internal API. Fortunately
135
+ # libuv also does this, so we can be pretty confident that MS won't break it
136
+ # on us, and there is a *little* bit of information out there if you go
137
+ # digging.
138
+ #
139
+ # Basically: we open a magic file that refers to the AFD driver, register the
140
+ # magic file with our IOCP, and then we can issue regular overlapped I/O
141
+ # operations on that handle. Specifically, the operation we use is called
142
+ # IOCTL_AFD_POLL, which lets us pass in a buffer describing which events we're
143
+ # interested in on a given socket (readable, writable, etc.). Later, when the
144
+ # operation completes, the kernel rewrites the buffer we passed in to record
145
+ # which events happened, and uses IOCP as normal to notify us that this
146
+ # operation has completed.
147
+ #
148
+ # Unfortunately, the Windows kernel seems to have bugs if you try to issue
149
+ # multiple simultaneous IOCTL_AFD_POLL operations on the same socket (see
150
+ # https://github.com/python-trio/trio/wiki/notes-to-self#afd-labpy).
151
+ # So if a user calls wait_readable and
152
+ # wait_writable at the same time, we have to combine those into a single
153
+ # IOCTL_AFD_POLL. This means we can't just use the wait_overlapped machinery.
154
+ # Instead we have some dedicated code to handle these operations, and a
155
+ # dedicated completion key CKeys.AFD_POLL.
156
+ #
157
+ # Sources of information:
158
+ # - https://github.com/python-trio/trio/issues/52
159
+ # - Wepoll: https://github.com/piscisaureus/wepoll/
160
+ # - libuv: https://github.com/libuv/libuv/
161
+ # - ReactOS: https://github.com/reactos/reactos/
162
+ # - Ancient leaked copies of the Windows NT and Winsock source code:
163
+ # https://github.com/pustladi/Windows-2000/blob/661d000d50637ed6fab2329d30e31775046588a9/private/net/sockets/winsock2/wsp/msafd/select.c#L59-L655
164
+ # https://github.com/metoo10987/WinNT4/blob/f5c14e6b42c8f45c20fe88d14c61f9d6e0386b8e/private/ntos/afd/poll.c#L68-L707
165
+ # - The WSAEventSelect docs (this exposes a finer-grained set of events than
166
+ # select(), so if you squint you can treat it as a source of information on
167
+ # the fine-grained AFD poll types)
168
+ #
169
+ #
170
+ # == Everything else ==
171
+ #
172
+ # There are also some weirder APIs for interacting with IOCP. For example, the
173
+ # "Job" API lets you specify an IOCP handle and "completion key", and then in
174
+ # the future whenever certain events happen it sends uses IOCP to send a
175
+ # notification. These notifications don't correspond to any particular
176
+ # operation; they're just spontaneous messages you get. The
177
+ # "dwNumberOfBytesTransferred" field gets repurposed to carry an identifier
178
+ # for the message type (e.g. JOB_OBJECT_MSG_EXIT_PROCESS), and the
179
+ # "lpOverlapped" field gets repurposed to carry some arbitrary data that
180
+ # depends on the message type (e.g. the pid of the process that exited).
181
+ #
182
+ # To handle these, we have monitor_completion_key, where we hand out an
183
+ # unassigned completion key, let users set it up however they want, and then
184
+ # get any events that arrive on that key.
185
+ #
186
+ # (Note: monitor_completion_key is not documented or fully baked; expect it to
187
+ # change in the future.)
188
+
189
+
190
+ # Our completion keys
191
+ class CKeys(enum.IntEnum):
192
+ AFD_POLL = 0
193
+ WAIT_OVERLAPPED = 1
194
+ LATE_CANCEL = 2
195
+ FORCE_WAKEUP = 3
196
+ USER_DEFINED = 4 # and above
197
+
198
+
199
+ # AFD_POLL has a finer-grained set of events than other APIs. We collapse them
200
+ # down into Unix-style "readable" and "writable".
201
+ #
202
+ # Note: AFD_POLL_LOCAL_CLOSE isn't a reliable substitute for notify_closing(),
203
+ # because even if the user closes the socket *handle*, the socket *object*
204
+ # could still remain open, e.g. if the socket was dup'ed (possibly into
205
+ # another process). Explicitly calling notify_closing() guarantees that
206
+ # everyone waiting on the *handle* wakes up, which is what you'd expect.
207
+ #
208
+ # However, we can't avoid getting LOCAL_CLOSE notifications -- the kernel
209
+ # delivers them whether we ask for them or not -- so better to include them
210
+ # here for documentation, and so that when we check (delivered & requested) we
211
+ # get a match.
212
+
213
+ READABLE_FLAGS = (
214
+ AFDPollFlags.AFD_POLL_RECEIVE
215
+ | AFDPollFlags.AFD_POLL_ACCEPT
216
+ | AFDPollFlags.AFD_POLL_DISCONNECT # other side sent an EOF
217
+ | AFDPollFlags.AFD_POLL_ABORT
218
+ | AFDPollFlags.AFD_POLL_LOCAL_CLOSE
219
+ )
220
+
221
+ WRITABLE_FLAGS = (
222
+ AFDPollFlags.AFD_POLL_SEND
223
+ | AFDPollFlags.AFD_POLL_CONNECT_FAIL
224
+ | AFDPollFlags.AFD_POLL_ABORT
225
+ | AFDPollFlags.AFD_POLL_LOCAL_CLOSE
226
+ )
227
+
228
+
229
+ # Annoyingly, while the API makes it *seem* like you can happily issue as many
230
+ # independent AFD_POLL operations as you want without them interfering with
231
+ # each other, in fact if you issue two AFD_POLL operations for the same socket
232
+ # at the same time with notification going to the same IOCP port, then Windows
233
+ # gets super confused. For example, if we issue one operation from
234
+ # wait_readable, and another independent operation from wait_writable, then
235
+ # Windows may complete the wait_writable operation when the socket becomes
236
+ # readable.
237
+ #
238
+ # To avoid this, we have to coalesce all the operations on a single socket
239
+ # into one, and when the set of waiters changes we have to throw away the old
240
+ # operation and start a new one.
241
+ @attrs.define(eq=False)
242
+ class AFDWaiters:
243
+ read_task: _core.Task | None = None
244
+ write_task: _core.Task | None = None
245
+ current_op: AFDPollOp | None = None
246
+
247
+
248
+ # Just used for internal type checking.
249
+ class _AFDHandle(Protocol):
250
+ Handle: Handle
251
+ Status: int
252
+ Events: int
253
+
254
+
255
+ # Just used for internal type checking.
256
+ class _AFDPollInfo(Protocol):
257
+ Timeout: int
258
+ NumberOfHandles: int
259
+ Exclusive: int
260
+ Handles: list[_AFDHandle]
261
+
262
+
263
+ # We also need to bundle up all the info for a single op into a standalone
264
+ # object, because we need to keep all these objects alive until the operation
265
+ # finishes, even if we're throwing it away.
266
+ @attrs.frozen(eq=False)
267
+ class AFDPollOp:
268
+ lpOverlapped: CData
269
+ poll_info: _AFDPollInfo
270
+ waiters: AFDWaiters
271
+ afd_group: AFDGroup
272
+
273
+
274
+ # The Windows kernel has a weird issue when using AFD handles. If you have N
275
+ # instances of wait_readable/wait_writable registered with a single AFD handle,
276
+ # then cancelling any one of them takes something like O(N**2) time. So if we
277
+ # used just a single AFD handle, then cancellation would quickly become very
278
+ # expensive, e.g. a program with N active sockets would take something like
279
+ # O(N**3) time to unwind after control-C. The solution is to spread our sockets
280
+ # out over multiple AFD handles, so that N doesn't grow too large for any
281
+ # individual handle.
282
+ MAX_AFD_GROUP_SIZE = 500 # at 1000, the cubic scaling is just starting to bite
283
+
284
+
285
+ @attrs.define(eq=False)
286
+ class AFDGroup:
287
+ size: int
288
+ handle: Handle
289
+
290
+
291
+ assert not TYPE_CHECKING or sys.platform == "win32"
292
+
293
+
294
+ @attrs.frozen(eq=False)
295
+ class _WindowsStatistics:
296
+ tasks_waiting_read: int
297
+ tasks_waiting_write: int
298
+ tasks_waiting_overlapped: int
299
+ completion_key_monitors: int
300
+ backend: Literal["windows"] = attrs.field(init=False, default="windows")
301
+
302
+
303
+ # Maximum number of events to dequeue from the completion port on each pass
304
+ # through the run loop. Somewhat arbitrary. Should be large enough to collect
305
+ # a good set of tasks on each loop, but not so large to waste tons of memory.
306
+ # (Each WindowsIOManager holds a buffer whose size is ~32x this number.)
307
+ MAX_EVENTS = 1000
308
+
309
+
310
+ def _check(success: T) -> T:
311
+ if not success:
312
+ raise_winerror()
313
+ return success
314
+
315
+
316
+ def _get_underlying_socket(
317
+ sock: _HasFileNo | int | Handle,
318
+ *,
319
+ which: WSAIoctls = WSAIoctls.SIO_BASE_HANDLE,
320
+ ) -> Handle:
321
+ if hasattr(sock, "fileno"):
322
+ sock = sock.fileno()
323
+ base_ptr = ffi.new("HANDLE *")
324
+ out_size = ffi.new("DWORD *")
325
+ failed = ws2_32.WSAIoctl(
326
+ ffi.cast("SOCKET", sock),
327
+ which,
328
+ ffi.NULL,
329
+ 0,
330
+ base_ptr,
331
+ ffi.sizeof("HANDLE"),
332
+ out_size,
333
+ ffi.NULL,
334
+ ffi.NULL,
335
+ )
336
+ if failed:
337
+ code = ws2_32.WSAGetLastError()
338
+ raise_winerror(code)
339
+ return Handle(base_ptr[0])
340
+
341
+
342
+ def _get_base_socket(sock: _HasFileNo | int | Handle) -> Handle:
343
+ # There is a development kit for LSPs called Komodia Redirector.
344
+ # It does some unusual (some might say evil) things like intercepting
345
+ # SIO_BASE_HANDLE (fails) and SIO_BSP_HANDLE_SELECT (returns the same
346
+ # socket) in a misguided attempt to prevent bypassing it. It's been used
347
+ # in malware including the infamous Lenovo Superfish incident from 2015,
348
+ # but unfortunately is also used in some legitimate products such as
349
+ # parental control tools and Astrill VPN. Komodia happens to not
350
+ # block SIO_BSP_HANDLE_POLL, so we'll try SIO_BASE_HANDLE and fall back
351
+ # to SIO_BSP_HANDLE_POLL if it doesn't work.
352
+ # References:
353
+ # - https://github.com/piscisaureus/wepoll/blob/0598a791bf9cbbf480793d778930fc635b044980/wepoll.c#L2223
354
+ # - https://github.com/tokio-rs/mio/issues/1314
355
+
356
+ while True:
357
+ try:
358
+ # If this is not a Komodia-intercepted socket, we can just use
359
+ # SIO_BASE_HANDLE.
360
+ return _get_underlying_socket(sock)
361
+ except OSError as ex:
362
+ if ex.winerror == ErrorCodes.ERROR_NOT_SOCKET:
363
+ # SIO_BASE_HANDLE might fail even without LSP intervention,
364
+ # if we get something that's not a socket.
365
+ raise
366
+ if hasattr(sock, "fileno"):
367
+ sock = sock.fileno()
368
+ sock = _handle(sock)
369
+ next_sock = _get_underlying_socket(
370
+ sock,
371
+ which=WSAIoctls.SIO_BSP_HANDLE_POLL,
372
+ )
373
+ if next_sock == sock:
374
+ # If BSP_HANDLE_POLL returns the same socket we already had,
375
+ # then there's no layering going on and we need to fail
376
+ # to prevent an infinite loop.
377
+ raise RuntimeError(
378
+ "Unexpected network configuration detected: "
379
+ "SIO_BASE_HANDLE failed and SIO_BSP_HANDLE_POLL didn't "
380
+ "return a different socket. Please file a bug at "
381
+ "https://github.com/python-trio/trio/issues/new, "
382
+ "and include the output of running: "
383
+ "netsh winsock show catalog",
384
+ ) from ex
385
+ # Otherwise we've gotten at least one layer deeper, so
386
+ # loop back around to keep digging.
387
+ sock = next_sock
388
+
389
+
390
+ def _afd_helper_handle() -> Handle:
391
+ # The "AFD" driver is exposed at the NT path "\Device\Afd". We're using
392
+ # the Win32 CreateFile, though, so we have to pass a Win32 path. \\.\ is
393
+ # how Win32 refers to the NT \GLOBAL??\ directory, and GLOBALROOT is a
394
+ # symlink inside that directory that points to the root of the NT path
395
+ # system. So by sticking that in front of the NT path, we get a Win32
396
+ # path. Alternatively, we could use NtCreateFile directly, since it takes
397
+ # an NT path. But we already wrap CreateFileW so this was easier.
398
+ # References:
399
+ # https://blogs.msdn.microsoft.com/jeremykuhne/2016/05/02/dos-to-nt-a-paths-journey/
400
+ # https://stackoverflow.com/a/21704022
401
+ #
402
+ # I'm actually not sure what the \Trio part at the end of the path does.
403
+ # Wepoll uses \Device\Afd\Wepoll, so I just copied them. (I'm guessing it
404
+ # might be visible in some debug tools, and is otherwise arbitrary?)
405
+ rawname = r"\\.\GLOBALROOT\Device\Afd\Trio".encode("utf-16le") + b"\0\0"
406
+ rawname_buf = ffi.from_buffer(rawname)
407
+
408
+ handle = kernel32.CreateFileW(
409
+ ffi.cast("LPCWSTR", rawname_buf),
410
+ FileFlags.SYNCHRONIZE,
411
+ FileFlags.FILE_SHARE_READ | FileFlags.FILE_SHARE_WRITE,
412
+ ffi.NULL, # no security attributes
413
+ FileFlags.OPEN_EXISTING,
414
+ FileFlags.FILE_FLAG_OVERLAPPED,
415
+ ffi.NULL, # no template file
416
+ )
417
+ if handle == INVALID_HANDLE_VALUE: # pragma: no cover
418
+ raise_winerror()
419
+ return handle
420
+
421
+
422
+ @attrs.frozen(slots=False)
423
+ class CompletionKeyEventInfo:
424
+ lpOverlapped: CData | int
425
+ dwNumberOfBytesTransferred: int
426
+
427
+
428
+ class WindowsIOManager:
429
+ def __init__(self) -> None:
430
+ # If this method raises an exception, then __del__ could run on a
431
+ # half-initialized object. So we initialize everything that __del__
432
+ # touches to safe values up front, before we do anything that can
433
+ # fail.
434
+ self._iocp = None
435
+ self._all_afd_handles: list[Handle] = []
436
+
437
+ self._iocp = _check(
438
+ kernel32.CreateIoCompletionPort(INVALID_HANDLE_VALUE, ffi.NULL, 0, 0),
439
+ )
440
+ self._events = ffi.new("OVERLAPPED_ENTRY[]", MAX_EVENTS)
441
+
442
+ self._vacant_afd_groups: set[AFDGroup] = set()
443
+ # {lpOverlapped: AFDPollOp}
444
+ self._afd_ops: dict[CData, AFDPollOp] = {}
445
+ # {socket handle: AFDWaiters}
446
+ self._afd_waiters: dict[Handle, AFDWaiters] = {}
447
+
448
+ # {lpOverlapped: task}
449
+ self._overlapped_waiters: dict[CData, _core.Task] = {}
450
+ self._posted_too_late_to_cancel: set[CData] = set()
451
+
452
+ self._completion_key_queues: dict[int, UnboundedQueue[object]] = {}
453
+ self._completion_key_counter = itertools.count(CKeys.USER_DEFINED)
454
+
455
+ with socket.socket() as s:
456
+ # We assume we're not working with any LSP that changes
457
+ # how select() is supposed to work. Validate this by
458
+ # ensuring that the result of SIO_BSP_HANDLE_SELECT (the
459
+ # LSP-hookable mechanism for "what should I use for
460
+ # select()?") matches that of SIO_BASE_HANDLE ("what is
461
+ # the real non-hooked underlying socket here?").
462
+ #
463
+ # This doesn't work for Komodia-based LSPs; see the comments
464
+ # in _get_base_socket() for details. But we have special
465
+ # logic for those, so we just skip this check if
466
+ # SIO_BASE_HANDLE fails.
467
+
468
+ # LSPs can in theory override this, but we believe that it never
469
+ # actually happens in the wild (except Komodia)
470
+ select_handle = _get_underlying_socket(
471
+ s,
472
+ which=WSAIoctls.SIO_BSP_HANDLE_SELECT,
473
+ )
474
+ try:
475
+ # LSPs shouldn't override this...
476
+ base_handle = _get_underlying_socket(s, which=WSAIoctls.SIO_BASE_HANDLE)
477
+ except OSError:
478
+ # But Komodia-based LSPs do anyway, in a way that causes
479
+ # a failure with WSAEFAULT. We have special handling for
480
+ # them in _get_base_socket(). Make sure it works.
481
+ _get_base_socket(s)
482
+ else:
483
+ if base_handle != select_handle:
484
+ raise RuntimeError(
485
+ "Unexpected network configuration detected: "
486
+ "SIO_BASE_HANDLE and SIO_BSP_HANDLE_SELECT differ. "
487
+ "Please file a bug at "
488
+ "https://github.com/python-trio/trio/issues/new, "
489
+ "and include the output of running: "
490
+ "netsh winsock show catalog",
491
+ )
492
+
493
+ def close(self) -> None:
494
+ try:
495
+ if self._iocp is not None:
496
+ iocp = self._iocp
497
+ self._iocp = None
498
+ _check(kernel32.CloseHandle(iocp))
499
+ finally:
500
+ while self._all_afd_handles:
501
+ afd_handle = self._all_afd_handles.pop()
502
+ _check(kernel32.CloseHandle(afd_handle))
503
+
504
+ def __del__(self) -> None:
505
+ self.close()
506
+
507
+ def statistics(self) -> _WindowsStatistics:
508
+ tasks_waiting_read = 0
509
+ tasks_waiting_write = 0
510
+ for waiter in self._afd_waiters.values():
511
+ if waiter.read_task is not None:
512
+ tasks_waiting_read += 1
513
+ if waiter.write_task is not None:
514
+ tasks_waiting_write += 1
515
+ return _WindowsStatistics(
516
+ tasks_waiting_read=tasks_waiting_read,
517
+ tasks_waiting_write=tasks_waiting_write,
518
+ tasks_waiting_overlapped=len(self._overlapped_waiters),
519
+ completion_key_monitors=len(self._completion_key_queues),
520
+ )
521
+
522
+ def force_wakeup(self) -> None:
523
+ assert self._iocp is not None
524
+ _check(
525
+ kernel32.PostQueuedCompletionStatus(
526
+ self._iocp,
527
+ 0,
528
+ CKeys.FORCE_WAKEUP,
529
+ ffi.NULL,
530
+ ),
531
+ )
532
+
533
+ def get_events(self, timeout: float) -> EventResult:
534
+ received = ffi.new("PULONG")
535
+ milliseconds = round(1000 * timeout)
536
+ if timeout > 0 and milliseconds == 0:
537
+ milliseconds = 1
538
+ try:
539
+ assert self._iocp is not None
540
+ _check(
541
+ kernel32.GetQueuedCompletionStatusEx(
542
+ self._iocp,
543
+ self._events,
544
+ MAX_EVENTS,
545
+ received,
546
+ milliseconds,
547
+ 0,
548
+ ),
549
+ )
550
+ except OSError as exc:
551
+ if exc.winerror != ErrorCodes.WAIT_TIMEOUT: # pragma: no cover
552
+ raise
553
+ return 0
554
+ result = received[0]
555
+ assert isinstance(result, int)
556
+ return result
557
+
558
+ def process_events(self, received: EventResult) -> None:
559
+ for i in range(received):
560
+ entry = self._events[i]
561
+ if entry.lpCompletionKey == CKeys.AFD_POLL:
562
+ lpo = entry.lpOverlapped
563
+ op = self._afd_ops.pop(lpo)
564
+ waiters = op.waiters
565
+ if waiters.current_op is not op:
566
+ # Stale op, nothing to do
567
+ pass
568
+ else:
569
+ waiters.current_op = None
570
+ # I don't think this can happen, so if it does let's crash
571
+ # and get a debug trace.
572
+ if lpo.Internal != 0: # pragma: no cover
573
+ code = ntdll.RtlNtStatusToDosError(lpo.Internal)
574
+ raise_winerror(code)
575
+ flags = op.poll_info.Handles[0].Events
576
+ if waiters.read_task and flags & READABLE_FLAGS:
577
+ _core.reschedule(waiters.read_task)
578
+ waiters.read_task = None
579
+ if waiters.write_task and flags & WRITABLE_FLAGS:
580
+ _core.reschedule(waiters.write_task)
581
+ waiters.write_task = None
582
+ self._refresh_afd(op.poll_info.Handles[0].Handle)
583
+ elif entry.lpCompletionKey == CKeys.WAIT_OVERLAPPED:
584
+ # Regular I/O event, dispatch on lpOverlapped
585
+ waiter = self._overlapped_waiters.pop(entry.lpOverlapped)
586
+ overlapped = entry.lpOverlapped
587
+ transferred = entry.dwNumberOfBytesTransferred
588
+ info = CompletionKeyEventInfo(
589
+ lpOverlapped=overlapped,
590
+ dwNumberOfBytesTransferred=transferred,
591
+ )
592
+ _core.reschedule(waiter, Value(info))
593
+ elif entry.lpCompletionKey == CKeys.LATE_CANCEL:
594
+ # Post made by a regular I/O event's abort_fn
595
+ # after it failed to cancel the I/O. If we still
596
+ # have a waiter with this lpOverlapped, we didn't
597
+ # get the regular I/O completion and almost
598
+ # certainly the user forgot to call
599
+ # register_with_iocp.
600
+ self._posted_too_late_to_cancel.remove(entry.lpOverlapped)
601
+ try:
602
+ waiter = self._overlapped_waiters.pop(entry.lpOverlapped)
603
+ except KeyError:
604
+ # Looks like the actual completion got here before this
605
+ # fallback post did -- we're in the "expected" case of
606
+ # too-late-to-cancel, where the user did nothing wrong.
607
+ # Nothing more to do.
608
+ pass
609
+ else:
610
+ exc = _core.TrioInternalError(
611
+ f"Failed to cancel overlapped I/O in {waiter.name} and didn't "
612
+ "receive the completion either. Did you forget to "
613
+ "call register_with_iocp()?",
614
+ )
615
+ # Raising this out of handle_io ensures that
616
+ # the user will see our message even if some
617
+ # other task is in an uncancellable wait due
618
+ # to the same underlying forgot-to-register
619
+ # issue (if their CancelIoEx succeeds, we
620
+ # have no way of noticing that their completion
621
+ # won't arrive). Unfortunately it loses the
622
+ # task traceback. If you're debugging this
623
+ # error and can't tell where it's coming from,
624
+ # try changing this line to
625
+ # _core.reschedule(waiter, outcome.Error(exc))
626
+ raise exc
627
+ elif entry.lpCompletionKey == CKeys.FORCE_WAKEUP:
628
+ pass
629
+ else:
630
+ # dispatch on lpCompletionKey
631
+ queue = self._completion_key_queues[entry.lpCompletionKey]
632
+ overlapped = int(ffi.cast("uintptr_t", entry.lpOverlapped))
633
+ transferred = entry.dwNumberOfBytesTransferred
634
+ info = CompletionKeyEventInfo(
635
+ lpOverlapped=overlapped,
636
+ dwNumberOfBytesTransferred=transferred,
637
+ )
638
+ queue.put_nowait(info)
639
+
640
+ def _register_with_iocp(self, handle_: int | CData, completion_key: int) -> None:
641
+ handle = _handle(handle_)
642
+ assert self._iocp is not None
643
+ _check(kernel32.CreateIoCompletionPort(handle, self._iocp, completion_key, 0))
644
+ # Supposedly this makes things slightly faster, by disabling the
645
+ # ability to do WaitForSingleObject(handle). We would never want to do
646
+ # that anyway, so might as well get the extra speed (if any).
647
+ # Ref: http://www.lenholgate.com/blog/2009/09/interesting-blog-posts-on-high-performance-servers.html
648
+ _check(
649
+ kernel32.SetFileCompletionNotificationModes(
650
+ handle,
651
+ CompletionModes.FILE_SKIP_SET_EVENT_ON_HANDLE,
652
+ ),
653
+ )
654
+
655
+ ################################################################
656
+ # AFD stuff
657
+ ################################################################
658
+
659
+ def _refresh_afd(self, base_handle: Handle) -> None:
660
+ waiters = self._afd_waiters[base_handle]
661
+ if waiters.current_op is not None:
662
+ afd_group = waiters.current_op.afd_group
663
+ try:
664
+ _check(
665
+ kernel32.CancelIoEx(
666
+ afd_group.handle,
667
+ waiters.current_op.lpOverlapped,
668
+ ),
669
+ )
670
+ except OSError as exc:
671
+ if exc.winerror != ErrorCodes.ERROR_NOT_FOUND:
672
+ # I don't think this is possible, so if it happens let's
673
+ # crash noisily.
674
+ raise # pragma: no cover
675
+ waiters.current_op = None
676
+ afd_group.size -= 1
677
+ self._vacant_afd_groups.add(afd_group)
678
+
679
+ flags = 0
680
+ if waiters.read_task is not None:
681
+ flags |= READABLE_FLAGS
682
+ if waiters.write_task is not None:
683
+ flags |= WRITABLE_FLAGS
684
+
685
+ if not flags:
686
+ del self._afd_waiters[base_handle]
687
+ else:
688
+ try:
689
+ afd_group = self._vacant_afd_groups.pop()
690
+ except KeyError:
691
+ afd_group = AFDGroup(0, _afd_helper_handle())
692
+ self._register_with_iocp(afd_group.handle, CKeys.AFD_POLL)
693
+ self._all_afd_handles.append(afd_group.handle)
694
+ self._vacant_afd_groups.add(afd_group)
695
+
696
+ lpOverlapped = ffi.new("LPOVERLAPPED")
697
+
698
+ poll_info = cast("_AFDPollInfo", ffi.new("AFD_POLL_INFO *"))
699
+ poll_info.Timeout = 2**63 - 1 # INT64_MAX
700
+ poll_info.NumberOfHandles = 1
701
+ poll_info.Exclusive = 0
702
+ poll_info.Handles[0].Handle = base_handle
703
+ poll_info.Handles[0].Status = 0
704
+ poll_info.Handles[0].Events = flags
705
+
706
+ try:
707
+ _check(
708
+ kernel32.DeviceIoControl(
709
+ afd_group.handle,
710
+ IoControlCodes.IOCTL_AFD_POLL,
711
+ cast("CType", poll_info), # type: ignore[arg-type]
712
+ ffi.sizeof("AFD_POLL_INFO"),
713
+ cast("CType", poll_info), # type: ignore[arg-type]
714
+ ffi.sizeof("AFD_POLL_INFO"),
715
+ ffi.NULL,
716
+ lpOverlapped,
717
+ ),
718
+ )
719
+ except OSError as exc:
720
+ if exc.winerror != ErrorCodes.ERROR_IO_PENDING:
721
+ # This could happen if the socket handle got closed behind
722
+ # our back while a wait_* call was pending, and we tried
723
+ # to re-issue the call. Clear our state and wake up any
724
+ # pending calls.
725
+ del self._afd_waiters[base_handle]
726
+ # Do this last, because it could raise.
727
+ wake_all(waiters, exc)
728
+ return
729
+ op = AFDPollOp(lpOverlapped, poll_info, waiters, afd_group)
730
+ waiters.current_op = op
731
+ self._afd_ops[lpOverlapped] = op
732
+ afd_group.size += 1
733
+ if afd_group.size >= MAX_AFD_GROUP_SIZE:
734
+ self._vacant_afd_groups.remove(afd_group)
735
+
736
+ async def _afd_poll(self, sock: _HasFileNo | int, mode: str) -> None:
737
+ base_handle = _get_base_socket(sock)
738
+ waiters = self._afd_waiters.get(base_handle)
739
+ if waiters is None:
740
+ waiters = AFDWaiters()
741
+ self._afd_waiters[base_handle] = waiters
742
+ if getattr(waiters, mode) is not None:
743
+ raise _core.BusyResourceError
744
+ setattr(waiters, mode, _core.current_task())
745
+ # Could potentially raise if the handle is somehow invalid; that's OK,
746
+ # we let it escape.
747
+ self._refresh_afd(base_handle)
748
+
749
+ def abort_fn(_: RaiseCancelT) -> Abort:
750
+ setattr(waiters, mode, None)
751
+ self._refresh_afd(base_handle)
752
+ return _core.Abort.SUCCEEDED
753
+
754
+ await _core.wait_task_rescheduled(abort_fn)
755
+
756
+ @_public
757
+ async def wait_readable(self, sock: _HasFileNo | int) -> None:
758
+ """Block until the kernel reports that the given object is readable.
759
+
760
+ On Unix systems, ``sock`` must either be an integer file descriptor,
761
+ or else an object with a ``.fileno()`` method which returns an
762
+ integer file descriptor. Any kind of file descriptor can be passed,
763
+ though the exact semantics will depend on your kernel. For example,
764
+ this probably won't do anything useful for on-disk files.
765
+
766
+ On Windows systems, ``sock`` must either be an integer ``SOCKET``
767
+ handle, or else an object with a ``.fileno()`` method which returns
768
+ an integer ``SOCKET`` handle. File descriptors aren't supported,
769
+ and neither are handles that refer to anything besides a
770
+ ``SOCKET``.
771
+
772
+ :raises trio.BusyResourceError:
773
+ if another task is already waiting for the given socket to
774
+ become readable.
775
+ :raises trio.ClosedResourceError:
776
+ if another task calls :func:`notify_closing` while this
777
+ function is still working.
778
+ """
779
+ await self._afd_poll(sock, "read_task")
780
+
781
+ @_public
782
+ async def wait_writable(self, sock: _HasFileNo | int) -> None:
783
+ """Block until the kernel reports that the given object is writable.
784
+
785
+ See `wait_readable` for the definition of ``sock``.
786
+
787
+ :raises trio.BusyResourceError:
788
+ if another task is already waiting for the given socket to
789
+ become writable.
790
+ :raises trio.ClosedResourceError:
791
+ if another task calls :func:`notify_closing` while this
792
+ function is still working.
793
+ """
794
+ await self._afd_poll(sock, "write_task")
795
+
796
+ @_public
797
+ def notify_closing(self, handle: Handle | int | _HasFileNo) -> None:
798
+ """Notify waiters of the given object that it will be closed.
799
+
800
+ Call this before closing a file descriptor (on Unix) or socket (on
801
+ Windows). This will cause any `wait_readable` or `wait_writable`
802
+ calls on the given object to immediately wake up and raise
803
+ `~trio.ClosedResourceError`.
804
+
805
+ This doesn't actually close the object – you still have to do that
806
+ yourself afterwards. Also, you want to be careful to make sure no
807
+ new tasks start waiting on the object in between when you call this
808
+ and when it's actually closed. So to close something properly, you
809
+ usually want to do these steps in order:
810
+
811
+ 1. Explicitly mark the object as closed, so that any new attempts
812
+ to use it will abort before they start.
813
+ 2. Call `notify_closing` to wake up any already-existing users.
814
+ 3. Actually close the object.
815
+
816
+ It's also possible to do them in a different order if that's more
817
+ convenient, *but only if* you make sure not to have any checkpoints in
818
+ between the steps. This way they all happen in a single atomic
819
+ step, so other tasks won't be able to tell what order they happened
820
+ in anyway.
821
+ """
822
+ handle = _get_base_socket(handle)
823
+ waiters = self._afd_waiters.get(handle)
824
+ if waiters is not None:
825
+ wake_all(waiters, _core.ClosedResourceError())
826
+ self._refresh_afd(handle)
827
+
828
+ ################################################################
829
+ # Regular overlapped operations
830
+ ################################################################
831
+
832
+ @_public
833
+ def register_with_iocp(self, handle: int | CData) -> None:
834
+ """TODO: these are implemented, but are currently more of a sketch than
835
+ anything real. See `#26
836
+ <https://github.com/python-trio/trio/issues/26>`__ and `#52
837
+ <https://github.com/python-trio/trio/issues/52>`__.
838
+ """
839
+ self._register_with_iocp(handle, CKeys.WAIT_OVERLAPPED)
840
+
841
+ @_public
842
+ async def wait_overlapped(
843
+ self,
844
+ handle_: int | CData,
845
+ lpOverlapped: CData | int,
846
+ ) -> object:
847
+ """TODO: these are implemented, but are currently more of a sketch than
848
+ anything real. See `#26
849
+ <https://github.com/python-trio/trio/issues/26>`__ and `#52
850
+ <https://github.com/python-trio/trio/issues/52>`__.
851
+ """
852
+ handle = _handle(handle_)
853
+ if isinstance(lpOverlapped, int): # TODO: test this line
854
+ lpOverlapped = ffi.cast("LPOVERLAPPED", lpOverlapped)
855
+ if lpOverlapped in self._overlapped_waiters: # TODO: test this line
856
+ raise _core.BusyResourceError(
857
+ "another task is already waiting on that lpOverlapped",
858
+ )
859
+ task = _core.current_task()
860
+ self._overlapped_waiters[lpOverlapped] = task
861
+ raise_cancel = None
862
+
863
+ def abort(raise_cancel_: RaiseCancelT) -> Abort:
864
+ nonlocal raise_cancel
865
+ raise_cancel = raise_cancel_
866
+ try:
867
+ _check(kernel32.CancelIoEx(handle, lpOverlapped))
868
+ except OSError as exc:
869
+ if exc.winerror == ErrorCodes.ERROR_NOT_FOUND:
870
+ assert self._iocp is not None
871
+ # Too late to cancel. If this happens because the
872
+ # operation is already completed, we don't need to do
873
+ # anything; we'll get a notification of that completion
874
+ # soon. But another possibility is that the operation was
875
+ # performed on a handle that wasn't registered with our
876
+ # IOCP (ie, the user forgot to call register_with_iocp),
877
+ # in which case we're just never going to see the
878
+ # completion. To avoid an uncancellable infinite sleep in
879
+ # the latter case, we'll PostQueuedCompletionStatus here,
880
+ # and if our post arrives before the original completion
881
+ # does, we'll assume the handle wasn't registered.
882
+ _check(
883
+ kernel32.PostQueuedCompletionStatus(
884
+ self._iocp,
885
+ 0,
886
+ CKeys.LATE_CANCEL,
887
+ lpOverlapped,
888
+ ),
889
+ )
890
+ # Keep the lpOverlapped referenced so its address
891
+ # doesn't get reused until our posted completion
892
+ # status has been processed. Otherwise, we can
893
+ # get confused about which completion goes with
894
+ # which I/O.
895
+ self._posted_too_late_to_cancel.add(lpOverlapped)
896
+ else: # pragma: no cover
897
+ raise _core.TrioInternalError(
898
+ "CancelIoEx failed with unexpected error",
899
+ ) from exc
900
+ return _core.Abort.FAILED
901
+
902
+ # TODO: what type does this return?
903
+ info = await _core.wait_task_rescheduled(abort)
904
+ lpOverlappedTyped = cast("_Overlapped", lpOverlapped)
905
+ if lpOverlappedTyped.Internal != 0:
906
+ # the lpOverlapped reports the error as an NT status code,
907
+ # which we must convert back to a Win32 error code before
908
+ # it will produce the right sorts of exceptions
909
+ code = ntdll.RtlNtStatusToDosError(lpOverlappedTyped.Internal)
910
+ if code == ErrorCodes.ERROR_OPERATION_ABORTED:
911
+ if raise_cancel is not None:
912
+ raise_cancel()
913
+ else:
914
+ # We didn't request this cancellation, so assume
915
+ # it happened due to the underlying handle being
916
+ # closed before the operation could complete.
917
+ raise _core.ClosedResourceError("another task closed this resource")
918
+ else:
919
+ raise_winerror(code)
920
+ return info
921
+
922
+ async def _perform_overlapped(
923
+ self,
924
+ handle: int | CData,
925
+ submit_fn: Callable[[_Overlapped], None],
926
+ ) -> _Overlapped:
927
+ # submit_fn(lpOverlapped) submits some I/O
928
+ # it may raise an OSError with ERROR_IO_PENDING
929
+ # the handle must already be registered using
930
+ # register_with_iocp(handle)
931
+ # This always does a schedule point, but it's possible that the
932
+ # operation will not be cancellable, depending on how Windows is
933
+ # feeling today. So we need to check for cancellation manually.
934
+ await _core.checkpoint_if_cancelled()
935
+ lpOverlapped = cast("_Overlapped", ffi.new("LPOVERLAPPED"))
936
+ try:
937
+ submit_fn(lpOverlapped)
938
+ except OSError as exc:
939
+ if exc.winerror != ErrorCodes.ERROR_IO_PENDING:
940
+ raise
941
+ await self.wait_overlapped(handle, cast("CData", lpOverlapped))
942
+ return lpOverlapped
943
+
944
+ @_public
945
+ async def write_overlapped(
946
+ self,
947
+ handle: int | CData,
948
+ data: Buffer,
949
+ file_offset: int = 0,
950
+ ) -> int:
951
+ """TODO: these are implemented, but are currently more of a sketch than
952
+ anything real. See `#26
953
+ <https://github.com/python-trio/trio/issues/26>`__ and `#52
954
+ <https://github.com/python-trio/trio/issues/52>`__.
955
+ """
956
+ with ffi.from_buffer(data) as cbuf:
957
+
958
+ def submit_write(lpOverlapped: _Overlapped) -> None:
959
+ # yes, these are the real documented names
960
+ offset_fields = lpOverlapped.DUMMYUNIONNAME.DUMMYSTRUCTNAME
961
+ offset_fields.Offset = file_offset & 0xFFFFFFFF
962
+ offset_fields.OffsetHigh = file_offset >> 32
963
+ _check(
964
+ kernel32.WriteFile(
965
+ _handle(handle),
966
+ ffi.cast("LPCVOID", cbuf),
967
+ len(cbuf),
968
+ ffi.NULL,
969
+ lpOverlapped,
970
+ ),
971
+ )
972
+
973
+ lpOverlapped = await self._perform_overlapped(handle, submit_write)
974
+ # this is "number of bytes transferred"
975
+ return lpOverlapped.InternalHigh
976
+
977
+ @_public
978
+ async def readinto_overlapped(
979
+ self,
980
+ handle: int | CData,
981
+ buffer: Buffer,
982
+ file_offset: int = 0,
983
+ ) -> int:
984
+ """TODO: these are implemented, but are currently more of a sketch than
985
+ anything real. See `#26
986
+ <https://github.com/python-trio/trio/issues/26>`__ and `#52
987
+ <https://github.com/python-trio/trio/issues/52>`__.
988
+ """
989
+ with ffi.from_buffer(buffer, require_writable=True) as cbuf:
990
+
991
+ def submit_read(lpOverlapped: _Overlapped) -> None:
992
+ offset_fields = lpOverlapped.DUMMYUNIONNAME.DUMMYSTRUCTNAME
993
+ offset_fields.Offset = file_offset & 0xFFFFFFFF
994
+ offset_fields.OffsetHigh = file_offset >> 32
995
+ _check(
996
+ kernel32.ReadFile(
997
+ _handle(handle),
998
+ ffi.cast("LPVOID", cbuf),
999
+ len(cbuf),
1000
+ ffi.NULL,
1001
+ lpOverlapped,
1002
+ ),
1003
+ )
1004
+
1005
+ lpOverlapped = await self._perform_overlapped(handle, submit_read)
1006
+ return lpOverlapped.InternalHigh
1007
+
1008
+ ################################################################
1009
+ # Raw IOCP operations
1010
+ ################################################################
1011
+
1012
+ @_public
1013
+ def current_iocp(self) -> int:
1014
+ """TODO: these are implemented, but are currently more of a sketch than
1015
+ anything real. See `#26
1016
+ <https://github.com/python-trio/trio/issues/26>`__ and `#52
1017
+ <https://github.com/python-trio/trio/issues/52>`__.
1018
+ """
1019
+ assert self._iocp is not None
1020
+ return int(ffi.cast("uintptr_t", self._iocp))
1021
+
1022
+ @contextmanager
1023
+ @_public
1024
+ def monitor_completion_key(self) -> Iterator[tuple[int, UnboundedQueue[object]]]:
1025
+ """TODO: these are implemented, but are currently more of a sketch than
1026
+ anything real. See `#26
1027
+ <https://github.com/python-trio/trio/issues/26>`__ and `#52
1028
+ <https://github.com/python-trio/trio/issues/52>`__.
1029
+ """
1030
+ key = next(self._completion_key_counter)
1031
+ queue = _core.UnboundedQueue[object]()
1032
+ self._completion_key_queues[key] = queue
1033
+ try:
1034
+ yield (key, queue)
1035
+ finally:
1036
+ del self._completion_key_queues[key]