@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,251 @@
1
+ from __future__ import annotations
2
+
3
+ import errno
4
+ import sys
5
+ from typing import TYPE_CHECKING
6
+
7
+ import trio
8
+ from trio import TaskStatus
9
+
10
+ from . import socket as tsocket
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Awaitable, Callable
14
+
15
+ if sys.version_info < (3, 11):
16
+ from exceptiongroup import ExceptionGroup
17
+
18
+
19
+ # Default backlog size:
20
+ #
21
+ # Having the backlog too low can cause practical problems (a perfectly healthy
22
+ # service that starts failing to accept connections if they arrive in a
23
+ # burst).
24
+ #
25
+ # Having it too high doesn't really cause any problems. Like any buffer, you
26
+ # want backlog queue to be zero usually, and it won't save you if you're
27
+ # getting connection attempts faster than you can call accept() on an ongoing
28
+ # basis. But unlike other buffers, this one doesn't really provide any
29
+ # backpressure. If a connection gets stuck waiting in the backlog queue, then
30
+ # from the peer's point of view the connection succeeded but then their
31
+ # send/recv will stall until we get to it, possibly for a long time. OTOH if
32
+ # there isn't room in the backlog queue, then their connect stalls, possibly
33
+ # for a long time, which is pretty much the same thing.
34
+ #
35
+ # A large backlog can also use a bit more kernel memory, but this seems fairly
36
+ # negligible these days.
37
+ #
38
+ # So this suggests we should make the backlog as large as possible. This also
39
+ # matches what Golang does. However, they do it in a weird way, where they
40
+ # have a bunch of code to sniff out the configured upper limit for backlog on
41
+ # different operating systems. But on every system, passing in a too-large
42
+ # backlog just causes it to be silently truncated to the configured maximum,
43
+ # so this is unnecessary -- we can just pass in "infinity" and get the maximum
44
+ # that way. (Verified on Windows, Linux, macOS using
45
+ # https://github.com/python-trio/trio/wiki/notes-to-self#measure-listen-backlogpy
46
+ def _compute_backlog(backlog: int | None) -> int:
47
+ # Many systems (Linux, BSDs, ...) store the backlog in a uint16 and are
48
+ # missing overflow protection, so we apply our own overflow protection.
49
+ # https://github.com/golang/go/issues/5030
50
+ if not isinstance(backlog, int) and backlog is not None:
51
+ raise TypeError(f"backlog must be an int or None, not {backlog!r}")
52
+ if backlog is None:
53
+ return 0xFFFF
54
+ return min(backlog, 0xFFFF)
55
+
56
+
57
+ async def open_tcp_listeners(
58
+ port: int,
59
+ *,
60
+ host: str | bytes | None = None,
61
+ backlog: int | None = None,
62
+ ) -> list[trio.SocketListener]:
63
+ """Create :class:`SocketListener` objects to listen for TCP connections.
64
+
65
+ Args:
66
+
67
+ port (int): The port to listen on.
68
+
69
+ If you use 0 as your port, then the kernel will automatically pick
70
+ an arbitrary open port. But be careful: if you use this feature when
71
+ binding to multiple IP addresses, then each IP address will get its
72
+ own random port, and the returned listeners will probably be
73
+ listening on different ports. In particular, this will happen if you
74
+ use ``host=None`` – which is the default – because in this case
75
+ :func:`open_tcp_listeners` will bind to both the IPv4 wildcard
76
+ address (``0.0.0.0``) and also the IPv6 wildcard address (``::``).
77
+
78
+ host (str, bytes, or None): The local interface to bind to. This is
79
+ passed to :func:`~socket.getaddrinfo` with the ``AI_PASSIVE`` flag
80
+ set.
81
+
82
+ If you want to bind to the wildcard address on both IPv4 and IPv6,
83
+ in order to accept connections on all available interfaces, then
84
+ pass ``None``. This is the default.
85
+
86
+ If you have a specific interface you want to bind to, pass its IP
87
+ address or hostname here. If a hostname resolves to multiple IP
88
+ addresses, this function will open one listener on each of them.
89
+
90
+ If you want to use only IPv4, or only IPv6, but want to accept on
91
+ all interfaces, pass the family-specific wildcard address:
92
+ ``"0.0.0.0"`` for IPv4-only and ``"::"`` for IPv6-only.
93
+
94
+ backlog (int or None): The listen backlog to use. If you leave this as
95
+ ``None`` then Trio will pick a good default. (Currently: whatever
96
+ your system has configured as the maximum backlog.)
97
+
98
+ Returns:
99
+ list of :class:`SocketListener`
100
+
101
+ Raises:
102
+ :class:`TypeError` if invalid arguments.
103
+
104
+ """
105
+ # getaddrinfo sometimes allows port=None, sometimes not (depending on
106
+ # whether host=None). And on some systems it treats "" as 0, others it
107
+ # doesn't:
108
+ # http://klickverbot.at/blog/2012/01/getaddrinfo-edge-case-behavior-on-windows-linux-and-osx/
109
+ if not isinstance(port, int):
110
+ raise TypeError(f"port must be an int not {port!r}")
111
+
112
+ computed_backlog = _compute_backlog(backlog)
113
+
114
+ addresses = await tsocket.getaddrinfo(
115
+ host,
116
+ port,
117
+ type=tsocket.SOCK_STREAM,
118
+ flags=tsocket.AI_PASSIVE,
119
+ )
120
+
121
+ listeners = []
122
+ unsupported_address_families = []
123
+ try:
124
+ for family, type_, proto, _, sockaddr in addresses:
125
+ try:
126
+ sock = tsocket.socket(family, type_, proto)
127
+ except OSError as ex:
128
+ if ex.errno == errno.EAFNOSUPPORT:
129
+ # If a system only supports IPv4, or only IPv6, it
130
+ # is still likely that getaddrinfo will return
131
+ # both an IPv4 and an IPv6 address. As long as at
132
+ # least one of the returned addresses can be
133
+ # turned into a socket, we won't complain about a
134
+ # failure to create the other.
135
+ unsupported_address_families.append(ex)
136
+ continue
137
+ else:
138
+ raise
139
+ try:
140
+ # See https://github.com/python-trio/trio/issues/39
141
+ if sys.platform != "win32":
142
+ sock.setsockopt(tsocket.SOL_SOCKET, tsocket.SO_REUSEADDR, 1)
143
+
144
+ if family == tsocket.AF_INET6:
145
+ sock.setsockopt(tsocket.IPPROTO_IPV6, tsocket.IPV6_V6ONLY, 1)
146
+
147
+ await sock.bind(sockaddr)
148
+ sock.listen(computed_backlog)
149
+
150
+ listeners.append(trio.SocketListener(sock))
151
+ except:
152
+ sock.close()
153
+ raise
154
+ except:
155
+ for listener in listeners:
156
+ listener.socket.close()
157
+ raise
158
+
159
+ if unsupported_address_families and not listeners:
160
+ msg = (
161
+ "This system doesn't support any of the kinds of "
162
+ "socket that that address could use"
163
+ )
164
+ raise OSError(errno.EAFNOSUPPORT, msg) from ExceptionGroup(
165
+ msg,
166
+ unsupported_address_families,
167
+ )
168
+
169
+ return listeners
170
+
171
+
172
+ async def serve_tcp(
173
+ handler: Callable[[trio.SocketStream], Awaitable[object]],
174
+ port: int,
175
+ *,
176
+ host: str | bytes | None = None,
177
+ backlog: int | None = None,
178
+ handler_nursery: trio.Nursery | None = None,
179
+ task_status: TaskStatus[list[trio.SocketListener]] = trio.TASK_STATUS_IGNORED,
180
+ ) -> None:
181
+ """Listen for incoming TCP connections, and for each one start a task
182
+ running ``handler(stream)``.
183
+
184
+ This is a thin convenience wrapper around :func:`open_tcp_listeners` and
185
+ :func:`serve_listeners` – see them for full details.
186
+
187
+ .. warning::
188
+
189
+ If ``handler`` raises an exception, then this function doesn't do
190
+ anything special to catch it – so by default the exception will
191
+ propagate out and crash your server. If you don't want this, then catch
192
+ exceptions inside your ``handler``, or use a ``handler_nursery`` object
193
+ that responds to exceptions in some other way.
194
+
195
+ When used with ``nursery.start`` you get back the newly opened listeners.
196
+ So, for example, if you want to start a server in your test suite and then
197
+ connect to it to check that it's working properly, you can use something
198
+ like::
199
+
200
+ from trio import SocketListener, SocketStream
201
+ from trio.testing import open_stream_to_socket_listener
202
+
203
+ async with trio.open_nursery() as nursery:
204
+ listeners: list[SocketListener] = await nursery.start(serve_tcp, handler, 0)
205
+ client_stream: SocketStream = await open_stream_to_socket_listener(listeners[0])
206
+
207
+ # Then send and receive data on 'client_stream', for example:
208
+ await client_stream.send_all(b"GET / HTTP/1.0\\r\\n\\r\\n")
209
+
210
+ This avoids several common pitfalls:
211
+
212
+ 1. It lets the kernel pick a random open port, so your test suite doesn't
213
+ depend on any particular port being open.
214
+
215
+ 2. It waits for the server to be accepting connections on that port before
216
+ ``start`` returns, so there's no race condition where the incoming
217
+ connection arrives before the server is ready.
218
+
219
+ 3. It uses the Listener object to find out which port was picked, so it
220
+ can connect to the right place.
221
+
222
+ Args:
223
+ handler: The handler to start for each incoming connection. Passed to
224
+ :func:`serve_listeners`.
225
+
226
+ port: The port to listen on. Use 0 to let the kernel pick an open port.
227
+ Passed to :func:`open_tcp_listeners`.
228
+
229
+ host (str, bytes, or None): The host interface to listen on; use
230
+ ``None`` to bind to the wildcard address. Passed to
231
+ :func:`open_tcp_listeners`.
232
+
233
+ backlog: The listen backlog, or None to have a good default picked.
234
+ Passed to :func:`open_tcp_listeners`.
235
+
236
+ handler_nursery: The nursery to start handlers in, or None to use an
237
+ internal nursery. Passed to :func:`serve_listeners`.
238
+
239
+ task_status: This function can be used with ``nursery.start``.
240
+
241
+ Returns:
242
+ This function only returns when cancelled.
243
+
244
+ """
245
+ listeners = await trio.open_tcp_listeners(port, host=host, backlog=backlog)
246
+ await trio.serve_listeners(
247
+ handler,
248
+ listeners,
249
+ handler_nursery=handler_nursery,
250
+ task_status=task_status,
251
+ )
@@ -0,0 +1,397 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from contextlib import contextmanager, suppress
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import trio
8
+ from trio.socket import SOCK_STREAM, SocketType, getaddrinfo, socket
9
+
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Generator, MutableSequence
12
+ from socket import AddressFamily, SocketKind
13
+
14
+ from trio._socket import AddressFormat
15
+
16
+ if sys.version_info < (3, 11):
17
+ from exceptiongroup import BaseExceptionGroup, ExceptionGroup
18
+
19
+
20
+ # Implementation of RFC 6555 "Happy eyeballs"
21
+ # https://tools.ietf.org/html/rfc6555
22
+ #
23
+ # Basically, the problem here is that if we want to connect to some host, and
24
+ # DNS returns multiple IP addresses, then we don't know which of them will
25
+ # actually work -- it can happen that some of them are reachable, and some of
26
+ # them are not. One particularly common situation where this happens is on a
27
+ # host that thinks it has ipv6 connectivity, but really doesn't. But in
28
+ # principle this could happen for any kind of multi-home situation (e.g. the
29
+ # route to one mirror is down but another is up).
30
+ #
31
+ # The naive algorithm (e.g. the stdlib's socket.create_connection) would be to
32
+ # pick one of the IP addresses and try to connect; if that fails, try the
33
+ # next; etc. The problem with this is that TCP is stubborn, and if the first
34
+ # address is a blackhole then it might take a very long time (tens of seconds)
35
+ # before that connection attempt fails.
36
+ #
37
+ # That's where RFC 6555 comes in. It tells us that what we do is:
38
+ # - get the list of IPs from getaddrinfo, trusting the order it gives us (with
39
+ # one exception noted in section 5.4)
40
+ # - start a connection attempt to the first IP
41
+ # - when this fails OR if it's still going after DELAY seconds, then start a
42
+ # connection attempt to the second IP
43
+ # - when this fails OR if it's still going after another DELAY seconds, then
44
+ # start a connection attempt to the third IP
45
+ # - ... repeat until we run out of IPs.
46
+ #
47
+ # Our implementation is similarly straightforward: we spawn a chain of tasks,
48
+ # where each one (a) waits until the previous connection has failed or DELAY
49
+ # seconds have passed, (b) spawns the next task, (c) attempts to connect. As
50
+ # soon as any task crashes or succeeds, we cancel all the tasks and return.
51
+ #
52
+ # Note: this currently doesn't attempt to cache any results, so if you make
53
+ # multiple connections to the same host it'll re-run the happy-eyeballs
54
+ # algorithm each time. RFC 6555 is pretty confusing about whether this is
55
+ # allowed. Section 4 describes an algorithm that attempts ipv4 and ipv6
56
+ # simultaneously, and then says "The client MUST cache information regarding
57
+ # the outcome of each connection attempt, and it uses that information to
58
+ # avoid thrashing the network with subsequent attempts." Then section 4.2 says
59
+ # "implementations MUST prefer the first IP address family returned by the
60
+ # host's address preference policy, unless implementing a stateful
61
+ # algorithm". Here "stateful" means "one that caches information about
62
+ # previous attempts". So my reading of this is that IF you're starting ipv4
63
+ # and ipv6 at the same time then you MUST cache the result for ~ten minutes,
64
+ # but IF you're "preferring" one protocol by trying it first (like we are),
65
+ # then you don't need to cache.
66
+ #
67
+ # Caching is quite tricky: to get it right you need to do things like detect
68
+ # when the network interfaces are reconfigured, and if you get it wrong then
69
+ # connection attempts basically just don't work. So we don't even try.
70
+
71
+ # "Firefox and Chrome use 300 ms"
72
+ # https://tools.ietf.org/html/rfc6555#section-6
73
+ # Though
74
+ # https://www.researchgate.net/profile/Vaibhav_Bajpai3/publication/304568993_Measuring_the_Effects_of_Happy_Eyeballs/links/5773848e08ae6f328f6c284c/Measuring-the-Effects-of-Happy-Eyeballs.pdf
75
+ # claims that Firefox actually uses 0 ms, unless an about:config option is
76
+ # toggled and then it uses 250 ms.
77
+ DEFAULT_DELAY = 0.250
78
+
79
+ # How should we call getaddrinfo? In particular, should we use AI_ADDRCONFIG?
80
+ #
81
+ # The idea of AI_ADDRCONFIG is that it only returns addresses that might
82
+ # work. E.g., if getaddrinfo knows that you don't have any IPv6 connectivity,
83
+ # then it doesn't return any IPv6 addresses. And this is kinda nice, because
84
+ # it means maybe you can skip sending AAAA requests entirely. But in practice,
85
+ # it doesn't really work right.
86
+ #
87
+ # - on Linux/glibc, empirically, the default is to return all addresses, and
88
+ # with AI_ADDRCONFIG then it only returns IPv6 addresses if there is at least
89
+ # one non-loopback IPv6 address configured... but this can be a link-local
90
+ # address, so in practice I guess this is basically always configured if IPv6
91
+ # is enabled at all. OTOH if you pass in "::1" as the target address with
92
+ # AI_ADDRCONFIG and there's no *external* IPv6 address configured, you get an
93
+ # error. So AI_ADDRCONFIG mostly doesn't do anything, even when you would want
94
+ # it to, and when it does do something it might break things that would have
95
+ # worked.
96
+ #
97
+ # - on Windows 10, empirically, if no IPv6 address is configured then by
98
+ # default they are also suppressed from getaddrinfo (flags=0 and
99
+ # flags=AI_ADDRCONFIG seem to do the same thing). If you pass AI_ALL, then you
100
+ # get the full list.
101
+ # ...except for localhost! getaddrinfo("localhost", "80") gives me ::1, even
102
+ # though there's no ipv6 and other queries only return ipv4.
103
+ # If you pass in and IPv6 IP address as the target address, then that's always
104
+ # returned OK, even with AI_ADDRCONFIG set and no IPv6 configured.
105
+ #
106
+ # But I guess other versions of windows messed this up, judging from these bug
107
+ # reports:
108
+ # https://bugs.chromium.org/p/chromium/issues/detail?id=5234
109
+ # https://bugs.chromium.org/p/chromium/issues/detail?id=32522#c50
110
+ #
111
+ # So basically the options are either to use AI_ADDRCONFIG and then add some
112
+ # complicated special cases to work around its brokenness, or else don't use
113
+ # AI_ADDRCONFIG and accept that sometimes on legacy/misconfigured networks
114
+ # we'll waste 300 ms trying to connect to a blackholed destination.
115
+ #
116
+ # Twisted and Tornado always uses default flags. I think we'll do the same.
117
+
118
+
119
+ @contextmanager
120
+ def close_all() -> Generator[set[SocketType], None, None]:
121
+ sockets_to_close: set[SocketType] = set()
122
+ try:
123
+ yield sockets_to_close
124
+ finally:
125
+ errs = []
126
+ for sock in sockets_to_close:
127
+ try:
128
+ sock.close()
129
+ except BaseException as exc:
130
+ errs.append(exc)
131
+ if len(errs) == 1:
132
+ raise errs[0]
133
+ elif errs:
134
+ raise BaseExceptionGroup("", errs)
135
+
136
+
137
+ def reorder_for_rfc_6555_section_5_4( # type: ignore[explicit-any]
138
+ targets: MutableSequence[tuple[AddressFamily, SocketKind, int, str, Any]],
139
+ ) -> None:
140
+ # RFC 6555 section 5.4 says that if getaddrinfo returns multiple address
141
+ # families (e.g. IPv4 and IPv6), then you should make sure that your first
142
+ # and second attempts use different families:
143
+ #
144
+ # https://tools.ietf.org/html/rfc6555#section-5.4
145
+ #
146
+ # This function post-processes the results from getaddrinfo, in-place, to
147
+ # satisfy this requirement.
148
+ for i in range(1, len(targets)):
149
+ if targets[i][0] != targets[0][0]:
150
+ # Found the first entry with a different address family; move it
151
+ # so that it becomes the second item on the list.
152
+ if i != 1:
153
+ targets.insert(1, targets.pop(i))
154
+ break
155
+
156
+
157
+ def format_host_port(host: str | bytes, port: int | str) -> str:
158
+ host = host.decode("ascii") if isinstance(host, bytes) else host
159
+ if ":" in host:
160
+ return f"[{host}]:{port}"
161
+ else:
162
+ return f"{host}:{port}"
163
+
164
+
165
+ # Twisted's HostnameEndpoint has a good set of configurations:
166
+ # https://twistedmatrix.com/documents/current/api/twisted.internet.endpoints.HostnameEndpoint.html
167
+ #
168
+ # - per-connection timeout
169
+ # this doesn't seem useful -- we let you set a timeout on the whole thing
170
+ # using Trio's normal mechanisms, and that seems like enough
171
+ # - delay between attempts
172
+ # - bind address (but not port!)
173
+ # they *don't* support multiple address bindings, like giving the ipv4 and
174
+ # ipv6 addresses of the host.
175
+ # I think maybe our semantics should be: we accept a list of bind addresses,
176
+ # and we bind to the first one that is compatible with the
177
+ # connection attempt we want to make, and if none are compatible then we
178
+ # don't try to connect to that target.
179
+ #
180
+ # XX TODO: implement bind address support
181
+ #
182
+ # Actually, the best option is probably to be explicit: {AF_INET: "...",
183
+ # AF_INET6: "..."}
184
+ # this might be simpler after
185
+ async def open_tcp_stream(
186
+ host: str | bytes,
187
+ port: int,
188
+ *,
189
+ happy_eyeballs_delay: float | None = DEFAULT_DELAY,
190
+ local_address: str | None = None,
191
+ ) -> trio.SocketStream:
192
+ """Connect to the given host and port over TCP.
193
+
194
+ If the given ``host`` has multiple IP addresses associated with it, then
195
+ we have a problem: which one do we use?
196
+
197
+ One approach would be to attempt to connect to the first one, and then if
198
+ that fails, attempt to connect to the second one ... until we've tried all
199
+ of them. But the problem with this is that if the first IP address is
200
+ unreachable (for example, because it's an IPv6 address and our network
201
+ discards IPv6 packets), then we might end up waiting tens of seconds for
202
+ the first connection attempt to timeout before we try the second address.
203
+
204
+ Another approach would be to attempt to connect to all of the addresses at
205
+ the same time, in parallel, and then use whichever connection succeeds
206
+ first, abandoning the others. This would be fast, but create a lot of
207
+ unnecessary load on the network and the remote server.
208
+
209
+ This function strikes a balance between these two extremes: it works its
210
+ way through the available addresses one at a time, like the first
211
+ approach; but, if ``happy_eyeballs_delay`` seconds have passed and it's
212
+ still waiting for an attempt to succeed or fail, then it gets impatient
213
+ and starts the next connection attempt in parallel. As soon as any one
214
+ connection attempt succeeds, all the other attempts are cancelled. This
215
+ avoids unnecessary load because most connections will succeed after just
216
+ one or two attempts, but if one of the addresses is unreachable then it
217
+ doesn't slow us down too much.
218
+
219
+ This is known as a "happy eyeballs" algorithm, and our particular variant
220
+ is modelled after how Chrome connects to webservers; see `RFC 6555
221
+ <https://tools.ietf.org/html/rfc6555>`__ for more details.
222
+
223
+ Args:
224
+ host (str or bytes): The host to connect to. Can be an IPv4 address,
225
+ IPv6 address, or a hostname.
226
+
227
+ port (int): The port to connect to.
228
+
229
+ happy_eyeballs_delay (float or None): How many seconds to wait for each
230
+ connection attempt to succeed or fail before getting impatient and
231
+ starting another one in parallel. Set to `None` if you want
232
+ to limit to only one connection attempt at a time (like
233
+ :func:`socket.create_connection`). Default: 0.25 (250 ms).
234
+
235
+ local_address (None or str): The local IP address or hostname to use as
236
+ the source for outgoing connections. If ``None``, we let the OS pick
237
+ the source IP.
238
+
239
+ This is useful in some exotic networking configurations where your
240
+ host has multiple IP addresses, and you want to force the use of a
241
+ specific one.
242
+
243
+ Note that if you pass an IPv4 ``local_address``, then you won't be
244
+ able to connect to IPv6 hosts, and vice-versa. If you want to take
245
+ advantage of this to force the use of IPv4 or IPv6 without
246
+ specifying an exact source address, you can use the IPv4 wildcard
247
+ address ``local_address="0.0.0.0"``, or the IPv6 wildcard address
248
+ ``local_address="::"``.
249
+
250
+ Returns:
251
+ SocketStream: a :class:`~trio.abc.Stream` connected to the given server.
252
+
253
+ Raises:
254
+ OSError: if the connection fails.
255
+
256
+ See also:
257
+ open_ssl_over_tcp_stream
258
+
259
+ """
260
+
261
+ # To keep our public API surface smaller, rule out some cases that
262
+ # getaddrinfo will accept in some circumstances, but that act weird or
263
+ # have non-portable behavior or are just plain not useful.
264
+ if not isinstance(host, (str, bytes)):
265
+ raise ValueError(f"host must be str or bytes, not {host!r}")
266
+ if not isinstance(port, int):
267
+ raise TypeError(f"port must be int, not {port!r}")
268
+
269
+ if happy_eyeballs_delay is None:
270
+ happy_eyeballs_delay = DEFAULT_DELAY
271
+
272
+ targets = await getaddrinfo(host, port, type=SOCK_STREAM)
273
+
274
+ # I don't think this can actually happen -- if there are no results,
275
+ # getaddrinfo should have raised OSError instead of returning an empty
276
+ # list. But let's be paranoid and handle it anyway:
277
+ if not targets:
278
+ msg = f"no results found for hostname lookup: {format_host_port(host, port)}"
279
+ raise OSError(msg)
280
+
281
+ reorder_for_rfc_6555_section_5_4(targets)
282
+
283
+ # This list records all the connection failures that we ignored.
284
+ oserrors: list[OSError] = []
285
+
286
+ # Keeps track of the socket that we're going to complete with,
287
+ # need to make sure this isn't automatically closed
288
+ winning_socket: SocketType | None = None
289
+
290
+ # Try connecting to the specified address. Possible outcomes:
291
+ # - success: record connected socket in winning_socket and cancel
292
+ # concurrent attempts
293
+ # - failure: record exception in oserrors, set attempt_failed allowing
294
+ # the next connection attempt to start early
295
+ # code needs to ensure sockets can be closed appropriately in the
296
+ # face of crash or cancellation
297
+ async def attempt_connect(
298
+ socket_args: tuple[AddressFamily, SocketKind, int],
299
+ sockaddr: AddressFormat,
300
+ attempt_failed: trio.Event,
301
+ ) -> None:
302
+ nonlocal winning_socket
303
+
304
+ try:
305
+ sock = socket(*socket_args)
306
+ open_sockets.add(sock)
307
+
308
+ if local_address is not None:
309
+ # TCP connections are identified by a 4-tuple:
310
+ #
311
+ # (local IP, local port, remote IP, remote port)
312
+ #
313
+ # So if a single local IP wants to make multiple connections
314
+ # to the same (remote IP, remote port) pair, then those
315
+ # connections have to use different local ports, or else TCP
316
+ # won't be able to tell them apart. OTOH, if you have multiple
317
+ # connections to different remote IP/ports, then those
318
+ # connections can share a local port.
319
+ #
320
+ # Normally, when you call bind(), the kernel will immediately
321
+ # assign a specific local port to your socket. At this point
322
+ # the kernel doesn't know which (remote IP, remote port)
323
+ # you're going to use, so it has to pick a local port that
324
+ # *no* other connection is using. That's the only way to
325
+ # guarantee that this local port will be usable later when we
326
+ # call connect(). (Alternatively, you can set SO_REUSEADDR to
327
+ # allow multiple nascent connections to share the same port,
328
+ # but then connect() might fail with EADDRNOTAVAIL if we get
329
+ # unlucky and our TCP 4-tuple ends up colliding with another
330
+ # unrelated connection.)
331
+ #
332
+ # So calling bind() before connect() works, but it disables
333
+ # sharing of local ports. This is inefficient: it makes you
334
+ # more likely to run out of local ports.
335
+ #
336
+ # But on some versions of Linux, we can re-enable sharing of
337
+ # local ports by setting a special flag. This flag tells
338
+ # bind() to only bind the IP, and not the port. That way,
339
+ # connect() is allowed to pick the the port, and it can do a
340
+ # better job of it because it knows the remote IP/port.
341
+ with suppress(OSError, AttributeError):
342
+ sock.setsockopt(
343
+ trio.socket.IPPROTO_IP,
344
+ trio.socket.IP_BIND_ADDRESS_NO_PORT,
345
+ 1,
346
+ )
347
+ try:
348
+ await sock.bind((local_address, 0))
349
+ except OSError:
350
+ raise OSError(
351
+ f"local_address={local_address!r} is incompatible "
352
+ f"with remote address {sockaddr!r}",
353
+ ) from None
354
+
355
+ await sock.connect(sockaddr)
356
+
357
+ # Success! Save the winning socket and cancel all outstanding
358
+ # connection attempts.
359
+ winning_socket = sock
360
+ nursery.cancel_scope.cancel(reason="successfully found a socket")
361
+ except OSError as exc:
362
+ # This connection attempt failed, but the next one might
363
+ # succeed. Save the error for later so we can report it if
364
+ # everything fails, and tell the next attempt that it should go
365
+ # ahead (if it hasn't already).
366
+ oserrors.append(exc)
367
+ attempt_failed.set()
368
+
369
+ with close_all() as open_sockets:
370
+ # nursery spawns a task for each connection attempt, will be
371
+ # cancelled by the task that gets a successful connection
372
+ async with trio.open_nursery() as nursery:
373
+ for address_family, socket_type, proto, _, addr in targets:
374
+ # create an event to indicate connection failure,
375
+ # allowing the next target to be tried early
376
+ attempt_failed = trio.Event()
377
+
378
+ nursery.start_soon(
379
+ attempt_connect,
380
+ (address_family, socket_type, proto),
381
+ addr,
382
+ attempt_failed,
383
+ )
384
+
385
+ # give this attempt at most this time before moving on
386
+ with trio.move_on_after(happy_eyeballs_delay):
387
+ await attempt_failed.wait()
388
+
389
+ # nothing succeeded
390
+ if winning_socket is None:
391
+ assert len(oserrors) == len(targets)
392
+ msg = f"all attempts to connect to {format_host_port(host, port)} failed"
393
+ raise OSError(msg) from ExceptionGroup(msg, oserrors)
394
+ else:
395
+ stream = trio.SocketStream(winning_socket)
396
+ open_sockets.remove(winning_socket)
397
+ return stream