@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,65 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from contextlib import contextmanager
5
+ from typing import TYPE_CHECKING, Protocol, TypeVar
6
+
7
+ import trio
8
+ from trio.socket import SOCK_STREAM, socket
9
+
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Generator
12
+
13
+
14
+ class Closable(Protocol):
15
+ def close(self) -> None: ...
16
+
17
+
18
+ CloseT = TypeVar("CloseT", bound=Closable)
19
+
20
+
21
+ try:
22
+ from trio.socket import AF_UNIX
23
+
24
+ has_unix = True
25
+ except ImportError:
26
+ has_unix = False
27
+
28
+
29
+ @contextmanager
30
+ def close_on_error(obj: CloseT) -> Generator[CloseT, None, None]:
31
+ try:
32
+ yield obj
33
+ except:
34
+ obj.close()
35
+ raise
36
+
37
+
38
+ async def open_unix_socket(
39
+ filename: str | bytes | os.PathLike[str] | os.PathLike[bytes],
40
+ ) -> trio.SocketStream:
41
+ """Opens a connection to the specified
42
+ `Unix domain socket <https://en.wikipedia.org/wiki/Unix_domain_socket>`__.
43
+
44
+ You must have read/write permission on the specified file to connect.
45
+
46
+ Args:
47
+ filename (str or bytes): The filename to open the connection to.
48
+
49
+ Returns:
50
+ SocketStream: a :class:`~trio.abc.Stream` connected to the given file.
51
+
52
+ Raises:
53
+ OSError: If the socket file could not be connected to.
54
+ RuntimeError: If AF_UNIX sockets are not supported.
55
+ """
56
+ if not has_unix:
57
+ raise RuntimeError("Unix sockets are not supported on this platform")
58
+
59
+ # much more simplified logic vs tcp sockets - one socket type and only one
60
+ # possible location to connect to
61
+ sock = socket(AF_UNIX, SOCK_STREAM)
62
+ with close_on_error(sock):
63
+ await sock.connect(os.fspath(filename))
64
+
65
+ return trio.SocketStream(sock)
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ import errno
4
+ import logging
5
+ import os
6
+ from collections.abc import Awaitable, Callable
7
+ from typing import Any, NoReturn, TypeVar
8
+
9
+ import trio
10
+
11
+ # Errors that accept(2) can return, and which indicate that the system is
12
+ # overloaded
13
+ ACCEPT_CAPACITY_ERRNOS = {
14
+ errno.EMFILE,
15
+ errno.ENFILE,
16
+ errno.ENOMEM,
17
+ errno.ENOBUFS,
18
+ }
19
+
20
+ # How long to sleep when we get one of those errors
21
+ SLEEP_TIME = 0.100
22
+
23
+ # The logger we use to complain when this happens
24
+ LOGGER = logging.getLogger("trio.serve_listeners")
25
+
26
+
27
+ StreamT = TypeVar("StreamT", bound=trio.abc.AsyncResource)
28
+ ListenerT = TypeVar("ListenerT", bound=trio.abc.Listener[Any]) # type: ignore[explicit-any]
29
+ Handler = Callable[[StreamT], Awaitable[object]]
30
+
31
+
32
+ async def _run_handler(stream: StreamT, handler: Handler[StreamT]) -> None:
33
+ try:
34
+ await handler(stream)
35
+ finally:
36
+ await trio.aclose_forcefully(stream)
37
+
38
+
39
+ async def _serve_one_listener(
40
+ listener: trio.abc.Listener[StreamT],
41
+ handler_nursery: trio.Nursery,
42
+ handler: Handler[StreamT],
43
+ ) -> NoReturn:
44
+ async with listener:
45
+ while True:
46
+ try:
47
+ stream = await listener.accept()
48
+ except OSError as exc:
49
+ if exc.errno in ACCEPT_CAPACITY_ERRNOS:
50
+ LOGGER.error(
51
+ "accept returned %s (%s); retrying in %s seconds",
52
+ errno.errorcode[exc.errno],
53
+ os.strerror(exc.errno),
54
+ SLEEP_TIME,
55
+ exc_info=True,
56
+ )
57
+ await trio.sleep(SLEEP_TIME)
58
+ else:
59
+ raise
60
+ else:
61
+ handler_nursery.start_soon(_run_handler, stream, handler)
62
+
63
+
64
+ # This cannot be typed correctly, we need generic typevar bounds / HKT to indicate the
65
+ # relationship between StreamT & ListenerT.
66
+ # https://github.com/python/typing/issues/1226
67
+ # https://github.com/python/typing/issues/548
68
+
69
+
70
+ async def serve_listeners( # type: ignore[explicit-any]
71
+ handler: Handler[StreamT],
72
+ listeners: list[ListenerT],
73
+ *,
74
+ handler_nursery: trio.Nursery | None = None,
75
+ task_status: trio.TaskStatus[list[ListenerT]] = trio.TASK_STATUS_IGNORED,
76
+ ) -> NoReturn:
77
+ r"""Listen for incoming connections on ``listeners``, and for each one
78
+ start a task running ``handler(stream)``.
79
+
80
+ .. warning::
81
+
82
+ If ``handler`` raises an exception, then this function doesn't do
83
+ anything special to catch it – so by default the exception will
84
+ propagate out and crash your server. If you don't want this, then catch
85
+ exceptions inside your ``handler``, or use a ``handler_nursery`` object
86
+ that responds to exceptions in some other way.
87
+
88
+ Args:
89
+
90
+ handler: An async callable, that will be invoked like
91
+ ``handler_nursery.start_soon(handler, stream)`` for each incoming
92
+ connection.
93
+
94
+ listeners: A list of :class:`~trio.abc.Listener` objects.
95
+ :func:`serve_listeners` takes responsibility for closing them.
96
+
97
+ handler_nursery: The nursery used to start handlers, or any object with
98
+ a ``start_soon`` method. If ``None`` (the default), then
99
+ :func:`serve_listeners` will create a new nursery internally and use
100
+ that.
101
+
102
+ task_status: This function can be used with ``nursery.start``, which
103
+ will return ``listeners``.
104
+
105
+ Returns:
106
+
107
+ This function never returns unless cancelled.
108
+
109
+ Resource handling:
110
+
111
+ If ``handler`` neglects to close the ``stream``, then it will be closed
112
+ using :func:`trio.aclose_forcefully`.
113
+
114
+ Error handling:
115
+
116
+ Most errors coming from :meth:`~trio.abc.Listener.accept` are allowed to
117
+ propagate out (crashing the server in the process). However, some errors –
118
+ those which indicate that the server is temporarily overloaded – are
119
+ handled specially. These are :class:`OSError`\s with one of the following
120
+ errnos:
121
+
122
+ * ``EMFILE``: process is out of file descriptors
123
+ * ``ENFILE``: system is out of file descriptors
124
+ * ``ENOBUFS``, ``ENOMEM``: the kernel hit some sort of memory limitation
125
+ when trying to create a socket object
126
+
127
+ When :func:`serve_listeners` gets one of these errors, then it:
128
+
129
+ * Logs the error to the standard library logger ``trio.serve_listeners``
130
+ (level = ERROR, with exception information included). By default this
131
+ causes it to be printed to stderr.
132
+ * Waits 100 ms before calling ``accept`` again, in hopes that the
133
+ system will recover.
134
+
135
+ """
136
+ async with trio.open_nursery() as nursery:
137
+ if handler_nursery is None:
138
+ handler_nursery = nursery
139
+ for listener in listeners:
140
+ nursery.start_soon(_serve_one_listener, listener, handler_nursery, handler)
141
+ # The listeners are already queueing connections when we're called,
142
+ # but we wait until the end to call started() just in case we get an
143
+ # error or whatever.
144
+ task_status.started(listeners)
145
+
146
+ raise AssertionError(
147
+ "_serve_one_listener should never complete",
148
+ ) # pragma: no cover
@@ -0,0 +1,423 @@
1
+ # "High-level" networking interface
2
+ from __future__ import annotations
3
+
4
+ import errno
5
+ from contextlib import contextmanager, suppress
6
+ from typing import TYPE_CHECKING, overload
7
+
8
+ import trio
9
+
10
+ from . import socket as tsocket
11
+ from ._util import ConflictDetector, final
12
+ from .abc import HalfCloseableStream, Listener
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Generator
16
+
17
+ from ._socket import SocketType
18
+
19
+ import sys
20
+
21
+ if sys.version_info >= (3, 12):
22
+ # NOTE: this isn't in the `TYPE_CHECKING` since for some reason
23
+ # sphinx doesn't autoreload this module for SocketStream
24
+ # (hypothesis: it's our module renaming magic)
25
+ from collections.abc import Buffer
26
+ elif TYPE_CHECKING:
27
+ from typing_extensions import Buffer
28
+
29
+ # XX TODO: this number was picked arbitrarily. We should do experiments to
30
+ # tune it. (Or make it dynamic -- one idea is to start small and increase it
31
+ # if we observe single reads filling up the whole buffer, at least within some
32
+ # limits.)
33
+ DEFAULT_RECEIVE_SIZE = 65536
34
+
35
+ _closed_stream_errnos = {
36
+ # Unix
37
+ errno.EBADF,
38
+ # Windows
39
+ errno.ENOTSOCK,
40
+ }
41
+
42
+
43
+ @contextmanager
44
+ def _translate_socket_errors_to_stream_errors() -> Generator[None, None, None]:
45
+ try:
46
+ yield
47
+ except OSError as exc:
48
+ if exc.errno in _closed_stream_errnos:
49
+ raise trio.ClosedResourceError("this socket was already closed") from None
50
+ else:
51
+ raise trio.BrokenResourceError(f"socket connection broken: {exc}") from exc
52
+
53
+
54
+ @final
55
+ class SocketStream(HalfCloseableStream):
56
+ """An implementation of the :class:`trio.abc.HalfCloseableStream`
57
+ interface based on a raw network socket.
58
+
59
+ Args:
60
+ socket: The Trio socket object to wrap. Must have type ``SOCK_STREAM``,
61
+ and be connected.
62
+
63
+ By default for TCP sockets, :class:`SocketStream` enables ``TCP_NODELAY``,
64
+ and (on platforms where it's supported) enables ``TCP_NOTSENT_LOWAT`` with
65
+ a reasonable buffer size (currently 16 KiB) – see `issue #72
66
+ <https://github.com/python-trio/trio/issues/72>`__ for discussion. You can
67
+ of course override these defaults by calling :meth:`setsockopt`.
68
+
69
+ Once a :class:`SocketStream` object is constructed, it implements the full
70
+ :class:`trio.abc.HalfCloseableStream` interface. In addition, it provides
71
+ a few extra features:
72
+
73
+ .. attribute:: socket
74
+
75
+ The Trio socket object that this stream wraps.
76
+
77
+ """
78
+
79
+ def __init__(self, socket: SocketType) -> None:
80
+ if not isinstance(socket, tsocket.SocketType):
81
+ raise TypeError("SocketStream requires a Trio socket object")
82
+ if socket.type != tsocket.SOCK_STREAM:
83
+ raise ValueError("SocketStream requires a SOCK_STREAM socket")
84
+
85
+ self.socket = socket
86
+ self._send_conflict_detector = ConflictDetector(
87
+ "another task is currently sending data on this SocketStream",
88
+ )
89
+
90
+ # Socket defaults:
91
+
92
+ # Not supported on e.g. unix domain sockets
93
+ with suppress(OSError):
94
+ self.setsockopt(tsocket.IPPROTO_TCP, tsocket.TCP_NODELAY, True)
95
+
96
+ if hasattr(tsocket, "TCP_NOTSENT_LOWAT"):
97
+ # 16 KiB is pretty arbitrary and could probably do with some
98
+ # tuning. (Apple is also setting this by default in CFNetwork
99
+ # apparently -- I'm curious what value they're using, though I
100
+ # couldn't find it online trivially. CFNetwork-129.20 source
101
+ # has no mentions of TCP_NOTSENT_LOWAT. This presentation says
102
+ # "typically 8 kilobytes":
103
+ # http://devstreaming.apple.com/videos/wwdc/2015/719ui2k57m/719/719_your_app_and_next_generation_networks.pdf?dl=1
104
+ # ). The theory is that you want it to be bandwidth *
105
+ # rescheduling interval.
106
+ with suppress(OSError):
107
+ self.setsockopt(tsocket.IPPROTO_TCP, tsocket.TCP_NOTSENT_LOWAT, 2**14)
108
+
109
+ async def send_all(self, data: bytes | bytearray | memoryview) -> None:
110
+ if self.socket.did_shutdown_SHUT_WR:
111
+ raise trio.ClosedResourceError("can't send data after sending EOF")
112
+ with self._send_conflict_detector:
113
+ with _translate_socket_errors_to_stream_errors():
114
+ with memoryview(data) as data:
115
+ if not data:
116
+ if self.socket.fileno() == -1:
117
+ raise trio.ClosedResourceError("socket was already closed")
118
+ await trio.lowlevel.checkpoint()
119
+ return
120
+ total_sent = 0
121
+ while total_sent < len(data):
122
+ with data[total_sent:] as remaining:
123
+ sent = await self.socket.send(remaining)
124
+ total_sent += sent
125
+
126
+ async def wait_send_all_might_not_block(self) -> None:
127
+ with self._send_conflict_detector:
128
+ if self.socket.fileno() == -1:
129
+ raise trio.ClosedResourceError
130
+ with _translate_socket_errors_to_stream_errors():
131
+ await self.socket.wait_writable()
132
+
133
+ async def send_eof(self) -> None:
134
+ with self._send_conflict_detector:
135
+ await trio.lowlevel.checkpoint()
136
+ # On macOS, calling shutdown a second time raises ENOTCONN, but
137
+ # send_eof needs to be idempotent.
138
+ if self.socket.did_shutdown_SHUT_WR:
139
+ return
140
+ with _translate_socket_errors_to_stream_errors():
141
+ self.socket.shutdown(tsocket.SHUT_WR)
142
+
143
+ async def receive_some(self, max_bytes: int | None = None) -> bytes:
144
+ if max_bytes is None:
145
+ max_bytes = DEFAULT_RECEIVE_SIZE
146
+ if max_bytes < 1:
147
+ raise ValueError("max_bytes must be >= 1")
148
+ with _translate_socket_errors_to_stream_errors():
149
+ return await self.socket.recv(max_bytes)
150
+
151
+ async def aclose(self) -> None:
152
+ self.socket.close()
153
+ await trio.lowlevel.checkpoint()
154
+
155
+ # __aenter__, __aexit__ inherited from HalfCloseableStream are OK
156
+
157
+ @overload
158
+ def setsockopt(self, level: int, option: int, value: int | Buffer) -> None: ...
159
+
160
+ @overload
161
+ def setsockopt(self, level: int, option: int, value: None, length: int) -> None: ...
162
+
163
+ # TODO: rename `length` to `optlen`
164
+ def setsockopt(
165
+ self,
166
+ level: int,
167
+ option: int,
168
+ value: int | Buffer | None,
169
+ length: int | None = None,
170
+ ) -> None:
171
+ """Set an option on the underlying socket.
172
+
173
+ See :meth:`socket.socket.setsockopt` for details.
174
+
175
+ """
176
+ if length is None:
177
+ if value is None:
178
+ raise TypeError(
179
+ "invalid value for argument 'value', must not be None when specifying length",
180
+ )
181
+ return self.socket.setsockopt(level, option, value)
182
+ if value is not None:
183
+ raise TypeError(
184
+ f"invalid value for argument 'value': {value!r}, must be None when specifying optlen",
185
+ )
186
+ return self.socket.setsockopt(level, option, value, length)
187
+
188
+ @overload
189
+ def getsockopt(self, level: int, option: int) -> int: ...
190
+
191
+ @overload
192
+ def getsockopt(self, level: int, option: int, buffersize: int) -> bytes: ...
193
+
194
+ def getsockopt(self, level: int, option: int, buffersize: int = 0) -> int | bytes:
195
+ """Check the current value of an option on the underlying socket.
196
+
197
+ See :meth:`socket.socket.getsockopt` for details.
198
+
199
+ """
200
+ # This is to work around
201
+ # https://bitbucket.org/pypy/pypy/issues/2561
202
+ # We should be able to drop it when the next PyPy3 beta is released.
203
+ if buffersize == 0:
204
+ return self.socket.getsockopt(level, option)
205
+ else:
206
+ return self.socket.getsockopt(level, option, buffersize)
207
+
208
+
209
+ ################################################################
210
+ # SocketListener
211
+ ################################################################
212
+
213
+ # Accept error handling
214
+ # =====================
215
+ #
216
+ # Literature review
217
+ # -----------------
218
+ #
219
+ # Here's a list of all the possible errors that accept() can return, according
220
+ # to the POSIX spec or the Linux, FreeBSD, macOS, and Windows docs:
221
+ #
222
+ # Can't happen with a Trio socket:
223
+ # - EAGAIN/(WSA)EWOULDBLOCK
224
+ # - EINTR
225
+ # - WSANOTINITIALISED
226
+ # - WSAEINPROGRESS: a blocking call is already in progress
227
+ # - WSAEINTR: someone called WSACancelBlockingCall, but we don't make blocking
228
+ # calls in the first place
229
+ #
230
+ # Something is wrong with our call:
231
+ # - EBADF: not a file descriptor
232
+ # - (WSA)EINVAL: socket isn't listening, or (Linux, BSD) bad flags
233
+ # - (WSA)ENOTSOCK: not a socket
234
+ # - (WSA)EOPNOTSUPP: this kind of socket doesn't support accept
235
+ # - (Linux, FreeBSD, Windows) EFAULT: the sockaddr pointer points to readonly
236
+ # memory
237
+ #
238
+ # Something is wrong with the environment:
239
+ # - (WSA)EMFILE: this process hit its fd limit
240
+ # - ENFILE: the system hit its fd limit
241
+ # - (WSA)ENOBUFS, ENOMEM: unspecified memory problems
242
+ #
243
+ # Something is wrong with the connection we were going to accept. There's a
244
+ # ton of variability between systems here:
245
+ # - ECONNABORTED: documented everywhere, but apparently only the BSDs do this
246
+ # (signals a connection was closed/reset before being accepted)
247
+ # - EPROTO: unspecified protocol error
248
+ # - (Linux) EPERM: firewall rule prevented connection
249
+ # - (Linux) ENETDOWN, EPROTO, ENOPROTOOPT, EHOSTDOWN, ENONET, EHOSTUNREACH,
250
+ # EOPNOTSUPP, ENETUNREACH, ENOSR, ESOCKTNOSUPPORT, EPROTONOSUPPORT,
251
+ # ETIMEDOUT, ... or any other error that the socket could give, because
252
+ # apparently if an error happens on a connection before it's accept()ed,
253
+ # Linux will report that error from accept().
254
+ # - (Windows) WSAECONNRESET, WSAENETDOWN
255
+ #
256
+ #
257
+ # Code review
258
+ # -----------
259
+ #
260
+ # What do other libraries do?
261
+ #
262
+ # Twisted on Unix or when using nonblocking I/O on Windows:
263
+ # - ignores EPERM, with comment about Linux firewalls
264
+ # - logs and ignores EMFILE, ENOBUFS, ENFILE, ENOMEM, ECONNABORTED
265
+ # Comment notes that ECONNABORTED is a BSDism and that Linux returns the
266
+ # socket before having it fail, and macOS just silently discards it.
267
+ # - other errors are raised, which is logged + kills the socket
268
+ # ref: src/twisted/internet/tcp.py, Port.doRead
269
+ #
270
+ # Twisted using IOCP on Windows:
271
+ # - logs and ignores all errors
272
+ # ref: src/twisted/internet/iocpreactor/tcp.py, Port.handleAccept
273
+ #
274
+ # Tornado:
275
+ # - ignore ECONNABORTED (comments notes that it was observed on FreeBSD)
276
+ # - everything else raised, but all this does (by default) is cause it to be
277
+ # logged and then ignored
278
+ # (ref: tornado/netutil.py, tornado/ioloop.py)
279
+ #
280
+ # libuv on Unix:
281
+ # - ignores ECONNABORTED
282
+ # - does a "trick" for EMFILE or ENFILE
283
+ # - all other errors passed to the connection_cb to be handled
284
+ # (ref: src/unix/stream.c:uv__server_io, uv__emfile_trick)
285
+ #
286
+ # libuv on Windows:
287
+ # src/win/tcp.c:uv_tcp_queue_accept
288
+ # this calls AcceptEx, and then arranges to call:
289
+ # src/win/tcp.c:uv_process_tcp_accept_req
290
+ # this gets the result from AcceptEx. If the original AcceptEx call failed,
291
+ # then "we stop accepting connections and report this error to the
292
+ # connection callback". I think this is for things like ENOTSOCK. If
293
+ # AcceptEx successfully queues an overlapped operation, and then that
294
+ # reports an error, it's just discarded.
295
+ #
296
+ # asyncio, selector mode:
297
+ # - ignores EWOULDBLOCK, EINTR, ECONNABORTED
298
+ # - on EMFILE, ENFILE, ENOBUFS, ENOMEM, logs an error and then disables the
299
+ # listening loop for 1 second
300
+ # - everything else raises, but then the event loop just logs and ignores it
301
+ # (selector_events.py: BaseSelectorEventLoop._accept_connection)
302
+ #
303
+ #
304
+ # What should we do?
305
+ # ------------------
306
+ #
307
+ # When accept() returns an error, we can either ignore it or raise it.
308
+ #
309
+ # We have a long list of errors that should be ignored, and a long list of
310
+ # errors that should be raised. The big question is what to do with an error
311
+ # that isn't on either list. On Linux apparently you can get nearly arbitrary
312
+ # errors from accept() and they should be ignored, because it just indicates a
313
+ # socket that crashed before it began, and there isn't really anything to be
314
+ # done about this, plus on other platforms you may not get any indication at
315
+ # all, so programs have to tolerate not getting any indication too. OTOH if we
316
+ # get an unexpected error then it could indicate something arbitrarily bad --
317
+ # after all, it's unexpected.
318
+ #
319
+ # Given that we know that other libraries seem to be getting along fine with a
320
+ # fairly minimal list of errors to ignore, I think we'll be OK if we write
321
+ # down that list and then raise on everything else.
322
+ #
323
+ # The other question is what to do about the capacity problem errors: EMFILE,
324
+ # ENFILE, ENOBUFS, ENOMEM. Just flat out ignoring these is clearly not optimal
325
+ # -- at the very least you want to log them, and probably you want to take
326
+ # some remedial action. And if we ignore them then it prevents higher levels
327
+ # from doing anything clever with them. So we raise them.
328
+
329
+ _ignorable_accept_errno_names = [
330
+ # Linux can do this when the a connection is denied by the firewall
331
+ "EPERM",
332
+ # BSDs with an early close/reset
333
+ "ECONNABORTED",
334
+ # All the other miscellany noted above -- may not happen in practice, but
335
+ # whatever.
336
+ "EPROTO",
337
+ "ENETDOWN",
338
+ "ENOPROTOOPT",
339
+ "EHOSTDOWN",
340
+ "ENONET",
341
+ "EHOSTUNREACH",
342
+ "EOPNOTSUPP",
343
+ "ENETUNREACH",
344
+ "ENOSR",
345
+ "ESOCKTNOSUPPORT",
346
+ "EPROTONOSUPPORT",
347
+ "ETIMEDOUT",
348
+ "ECONNRESET",
349
+ ]
350
+
351
+ # Not all errnos are defined on all platforms
352
+ _ignorable_accept_errnos: set[int] = set()
353
+ for name in _ignorable_accept_errno_names:
354
+ with suppress(AttributeError):
355
+ _ignorable_accept_errnos.add(getattr(errno, name))
356
+
357
+
358
+ @final
359
+ class SocketListener(Listener[SocketStream]):
360
+ """A :class:`~trio.abc.Listener` that uses a listening socket to accept
361
+ incoming connections as :class:`SocketStream` objects.
362
+
363
+ Args:
364
+ socket: The Trio socket object to wrap. Must have type ``SOCK_STREAM``,
365
+ and be listening.
366
+
367
+ Note that the :class:`SocketListener` "takes ownership" of the given
368
+ socket; closing the :class:`SocketListener` will also close the socket.
369
+
370
+ .. attribute:: socket
371
+
372
+ The Trio socket object that this stream wraps.
373
+
374
+ """
375
+
376
+ def __init__(self, socket: SocketType) -> None:
377
+ if not isinstance(socket, tsocket.SocketType):
378
+ raise TypeError("SocketListener requires a Trio socket object")
379
+ if socket.type != tsocket.SOCK_STREAM:
380
+ raise ValueError("SocketListener requires a SOCK_STREAM socket")
381
+ try:
382
+ listening = socket.getsockopt(tsocket.SOL_SOCKET, tsocket.SO_ACCEPTCONN)
383
+ except OSError:
384
+ # SO_ACCEPTCONN fails on macOS; we just have to trust the user.
385
+ pass
386
+ else:
387
+ if not listening:
388
+ raise ValueError("SocketListener requires a listening socket")
389
+
390
+ self.socket = socket
391
+
392
+ async def accept(self) -> SocketStream:
393
+ """Accept an incoming connection.
394
+
395
+ Returns:
396
+ :class:`SocketStream`
397
+
398
+ Raises:
399
+ OSError: if the underlying call to ``accept`` raises an unexpected
400
+ error.
401
+ ClosedResourceError: if you already closed the socket.
402
+
403
+ This method handles routine errors like ``ECONNABORTED``, but passes
404
+ other errors on to its caller. In particular, it does *not* make any
405
+ special effort to handle resource exhaustion errors like ``EMFILE``,
406
+ ``ENFILE``, ``ENOBUFS``, ``ENOMEM``.
407
+
408
+ """
409
+ while True:
410
+ try:
411
+ sock, _ = await self.socket.accept()
412
+ except OSError as exc:
413
+ if exc.errno in _closed_stream_errnos:
414
+ raise trio.ClosedResourceError from None
415
+ if exc.errno not in _ignorable_accept_errnos:
416
+ raise
417
+ else:
418
+ return SocketStream(sock)
419
+
420
+ async def aclose(self) -> None:
421
+ """Close this listener and its underlying socket."""
422
+ self.socket.close()
423
+ await trio.lowlevel.checkpoint()