dvt-distributed 2026.3.6__py3-none-any.whl

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 (231) hide show
  1. distributed/__init__.py +148 -0
  2. distributed/_async_taskgroup.py +159 -0
  3. distributed/_asyncio.py +71 -0
  4. distributed/_concurrent_futures_thread.py +166 -0
  5. distributed/_signals.py +42 -0
  6. distributed/_stories.py +62 -0
  7. distributed/_version.py +24 -0
  8. distributed/active_memory_manager.py +739 -0
  9. distributed/actor.py +314 -0
  10. distributed/batched.py +197 -0
  11. distributed/bokeh.py +3 -0
  12. distributed/broker.py +104 -0
  13. distributed/cfexecutor.py +176 -0
  14. distributed/chaos.py +68 -0
  15. distributed/cli/__init__.py +0 -0
  16. distributed/cli/dask_scheduler.py +257 -0
  17. distributed/cli/dask_spec.py +67 -0
  18. distributed/cli/dask_ssh.py +209 -0
  19. distributed/cli/dask_worker.py +546 -0
  20. distributed/cli/utils.py +38 -0
  21. distributed/client.py +6436 -0
  22. distributed/cluster_dump.py +340 -0
  23. distributed/collections.py +214 -0
  24. distributed/comm/__init__.py +36 -0
  25. distributed/comm/addressing.py +303 -0
  26. distributed/comm/core.py +411 -0
  27. distributed/comm/inproc.py +383 -0
  28. distributed/comm/registry.py +92 -0
  29. distributed/comm/tcp.py +773 -0
  30. distributed/comm/ucx.py +87 -0
  31. distributed/comm/utils.py +126 -0
  32. distributed/comm/ws.py +491 -0
  33. distributed/compatibility.py +267 -0
  34. distributed/config.py +217 -0
  35. distributed/core.py +1737 -0
  36. distributed/counter.py +65 -0
  37. distributed/dashboard/__init__.py +0 -0
  38. distributed/dashboard/components/__init__.py +56 -0
  39. distributed/dashboard/components/nvml.py +191 -0
  40. distributed/dashboard/components/rmm.py +211 -0
  41. distributed/dashboard/components/scheduler.py +4841 -0
  42. distributed/dashboard/components/shared.py +596 -0
  43. distributed/dashboard/components/worker.py +484 -0
  44. distributed/dashboard/core.py +60 -0
  45. distributed/dashboard/export_tool.js +79 -0
  46. distributed/dashboard/export_tool.py +29 -0
  47. distributed/dashboard/scheduler.py +194 -0
  48. distributed/dashboard/templates/__init__.py +0 -0
  49. distributed/dashboard/templates/performance_report.html +6 -0
  50. distributed/dashboard/theme.yaml +9 -0
  51. distributed/dashboard/utils.py +67 -0
  52. distributed/dashboard/worker.py +32 -0
  53. distributed/deploy/__init__.py +13 -0
  54. distributed/deploy/adaptive.py +302 -0
  55. distributed/deploy/adaptive_core.py +213 -0
  56. distributed/deploy/cluster.py +648 -0
  57. distributed/deploy/local.py +280 -0
  58. distributed/deploy/old_ssh.py +497 -0
  59. distributed/deploy/spec.py +707 -0
  60. distributed/deploy/ssh.py +461 -0
  61. distributed/deploy/subprocess.py +287 -0
  62. distributed/deploy/utils.py +41 -0
  63. distributed/diagnostics/__init__.py +5 -0
  64. distributed/diagnostics/cluster_dump.py +41 -0
  65. distributed/diagnostics/cudf.py +25 -0
  66. distributed/diagnostics/eventstream.py +74 -0
  67. distributed/diagnostics/graph_layout.py +149 -0
  68. distributed/diagnostics/memory_sampler.py +231 -0
  69. distributed/diagnostics/memray.py +257 -0
  70. distributed/diagnostics/nvml.py +380 -0
  71. distributed/diagnostics/plugin.py +1104 -0
  72. distributed/diagnostics/progress.py +424 -0
  73. distributed/diagnostics/progress_stream.py +203 -0
  74. distributed/diagnostics/progressbar.py +508 -0
  75. distributed/diagnostics/rmm.py +43 -0
  76. distributed/diagnostics/task_stream.py +194 -0
  77. distributed/diagnostics/websocket.py +71 -0
  78. distributed/diskutils.py +283 -0
  79. distributed/distributed-schema.yaml +1250 -0
  80. distributed/distributed.yaml +356 -0
  81. distributed/event.py +268 -0
  82. distributed/exceptions.py +45 -0
  83. distributed/gc.py +278 -0
  84. distributed/http/__init__.py +3 -0
  85. distributed/http/health.py +12 -0
  86. distributed/http/prometheus.py +45 -0
  87. distributed/http/proxy.py +145 -0
  88. distributed/http/routing.py +69 -0
  89. distributed/http/scheduler/__init__.py +0 -0
  90. distributed/http/scheduler/api.py +88 -0
  91. distributed/http/scheduler/info.py +302 -0
  92. distributed/http/scheduler/json.py +74 -0
  93. distributed/http/scheduler/missing_bokeh.py +18 -0
  94. distributed/http/scheduler/prometheus/__init__.py +13 -0
  95. distributed/http/scheduler/prometheus/core.py +246 -0
  96. distributed/http/scheduler/prometheus/semaphore.py +87 -0
  97. distributed/http/scheduler/prometheus/stealing.py +48 -0
  98. distributed/http/static/__init__.py +0 -0
  99. distributed/http/static/css/__init__.py +0 -0
  100. distributed/http/static/css/base.css +152 -0
  101. distributed/http/static/css/gpu.css +16 -0
  102. distributed/http/static/css/individual-cluster-map.css +54 -0
  103. distributed/http/static/css/sortable.min.css +1 -0
  104. distributed/http/static/css/sortable.min.css.map +1 -0
  105. distributed/http/static/css/status.css +58 -0
  106. distributed/http/static/images/__init__.py +0 -0
  107. distributed/http/static/images/dask-logo.svg +25 -0
  108. distributed/http/static/images/fa-bars.svg +1 -0
  109. distributed/http/static/images/favicon.ico +0 -0
  110. distributed/http/static/images/jupyter.svg +90 -0
  111. distributed/http/static/images/numpy.png +0 -0
  112. distributed/http/static/images/pandas.png +0 -0
  113. distributed/http/static/images/python.png +0 -0
  114. distributed/http/static/individual-cluster-map.html +27 -0
  115. distributed/http/static/js/__init__.py +0 -0
  116. distributed/http/static/js/anime.min.js +8 -0
  117. distributed/http/static/js/individual-cluster-map.js +367 -0
  118. distributed/http/static/js/reconnecting-websocket.min.js +8 -0
  119. distributed/http/static/js/sortable.min.js +3 -0
  120. distributed/http/statics.py +13 -0
  121. distributed/http/templates/__init__.py +0 -0
  122. distributed/http/templates/base.html +88 -0
  123. distributed/http/templates/call-stack.html +15 -0
  124. distributed/http/templates/exceptions.html +54 -0
  125. distributed/http/templates/gpu.html +22 -0
  126. distributed/http/templates/json-index.html +11 -0
  127. distributed/http/templates/logs.html +8 -0
  128. distributed/http/templates/main.html +18 -0
  129. distributed/http/templates/simple.html +6 -0
  130. distributed/http/templates/status.html +34 -0
  131. distributed/http/templates/task.html +167 -0
  132. distributed/http/templates/worker-table.html +76 -0
  133. distributed/http/templates/worker.html +67 -0
  134. distributed/http/templates/workers.html +13 -0
  135. distributed/http/utils.py +51 -0
  136. distributed/http/worker/__init__.py +0 -0
  137. distributed/http/worker/prometheus/__init__.py +13 -0
  138. distributed/http/worker/prometheus/core.py +280 -0
  139. distributed/itertools.py +44 -0
  140. distributed/lock.py +115 -0
  141. distributed/metrics.py +426 -0
  142. distributed/multi_lock.py +237 -0
  143. distributed/nanny.py +1035 -0
  144. distributed/node.py +192 -0
  145. distributed/objects.py +47 -0
  146. distributed/preloading.py +276 -0
  147. distributed/process.py +389 -0
  148. distributed/proctitle.py +44 -0
  149. distributed/profile.py +610 -0
  150. distributed/protocol/__init__.py +124 -0
  151. distributed/protocol/arrow.py +59 -0
  152. distributed/protocol/compression.py +204 -0
  153. distributed/protocol/core.py +181 -0
  154. distributed/protocol/cuda.py +44 -0
  155. distributed/protocol/cupy.py +110 -0
  156. distributed/protocol/h5py.py +32 -0
  157. distributed/protocol/keras.py +43 -0
  158. distributed/protocol/netcdf4.py +54 -0
  159. distributed/protocol/numba.py +69 -0
  160. distributed/protocol/numpy.py +219 -0
  161. distributed/protocol/pickle.py +98 -0
  162. distributed/protocol/rmm.py +48 -0
  163. distributed/protocol/scipy.py +36 -0
  164. distributed/protocol/serialize.py +1003 -0
  165. distributed/protocol/sparse.py +37 -0
  166. distributed/protocol/torch.py +70 -0
  167. distributed/protocol/utils.py +274 -0
  168. distributed/protocol/utils_test.py +28 -0
  169. distributed/publish.py +132 -0
  170. distributed/py.typed +0 -0
  171. distributed/queues.py +303 -0
  172. distributed/recreate_tasks.py +192 -0
  173. distributed/scheduler.py +9480 -0
  174. distributed/security.py +357 -0
  175. distributed/semaphore.py +567 -0
  176. distributed/shuffle/__init__.py +13 -0
  177. distributed/shuffle/_arrow.py +179 -0
  178. distributed/shuffle/_buffer.py +264 -0
  179. distributed/shuffle/_comms.py +76 -0
  180. distributed/shuffle/_core.py +608 -0
  181. distributed/shuffle/_disk.py +228 -0
  182. distributed/shuffle/_exceptions.py +26 -0
  183. distributed/shuffle/_limiter.py +89 -0
  184. distributed/shuffle/_memory.py +47 -0
  185. distributed/shuffle/_merge.py +65 -0
  186. distributed/shuffle/_pickle.py +42 -0
  187. distributed/shuffle/_rechunk.py +1159 -0
  188. distributed/shuffle/_scheduler_plugin.py +557 -0
  189. distributed/shuffle/_shuffle.py +387 -0
  190. distributed/shuffle/_worker_plugin.py +435 -0
  191. distributed/sizeof.py +25 -0
  192. distributed/spans.py +685 -0
  193. distributed/spill.py +361 -0
  194. distributed/stealing.py +613 -0
  195. distributed/system.py +67 -0
  196. distributed/system_monitor.py +241 -0
  197. distributed/threadpoolexecutor.py +193 -0
  198. distributed/utils.py +1976 -0
  199. distributed/utils_comm.py +422 -0
  200. distributed/utils_test.py +2624 -0
  201. distributed/variable.py +260 -0
  202. distributed/versions.py +159 -0
  203. distributed/widgets/__init__.py +11 -0
  204. distributed/widgets/templates/__init__.py +0 -0
  205. distributed/widgets/templates/client.html.j2 +53 -0
  206. distributed/widgets/templates/cluster.html.j2 +38 -0
  207. distributed/widgets/templates/computation.html.j2 +42 -0
  208. distributed/widgets/templates/future.html.j2 +14 -0
  209. distributed/widgets/templates/has_what.html.j2 +24 -0
  210. distributed/widgets/templates/local_cluster.html.j2 +7 -0
  211. distributed/widgets/templates/log.html.j2 +11 -0
  212. distributed/widgets/templates/logs.html.j2 +6 -0
  213. distributed/widgets/templates/process_interface.html.j2 +31 -0
  214. distributed/widgets/templates/scheduler.html.j2 +4 -0
  215. distributed/widgets/templates/scheduler_info.html.j2 +139 -0
  216. distributed/widgets/templates/security.html.j2 +13 -0
  217. distributed/widgets/templates/task_state.html.j2 +13 -0
  218. distributed/widgets/templates/who_has.html.j2 +15 -0
  219. distributed/widgets/templates/worker_state.html.j2 +5 -0
  220. distributed/worker.py +3484 -0
  221. distributed/worker_client.py +91 -0
  222. distributed/worker_memory.py +550 -0
  223. distributed/worker_state_machine.py +3953 -0
  224. dvt_distributed-2026.3.6.dist-info/METADATA +71 -0
  225. dvt_distributed-2026.3.6.dist-info/RECORD +231 -0
  226. dvt_distributed-2026.3.6.dist-info/WHEEL +5 -0
  227. dvt_distributed-2026.3.6.dist-info/entry_points.txt +10 -0
  228. dvt_distributed-2026.3.6.dist-info/licenses/LICENSE.txt +29 -0
  229. dvt_distributed-2026.3.6.dist-info/top_level.txt +2 -0
  230. scripts/__init__.py +0 -0
  231. scripts/upload_builds.py +309 -0
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ # isort: off
4
+ from distributed import config # load distributed configuration first
5
+ from distributed import widgets # load distributed widgets second
6
+
7
+ # isort: on
8
+
9
+ import atexit
10
+ import weakref
11
+
12
+ # This finalizer registers an atexit handler that has to happen before
13
+ # distributed registers its handlers, otherwise we observe hangs on
14
+ # cluster shutdown when using the UCX comms backend. See
15
+ # https://github.com/dask/distributed/issues/7726 for more discussion
16
+ # of the problem and the search for long term solutions
17
+
18
+ weakref.finalize(lambda: None, lambda: None)
19
+ import dask
20
+ from dask.config import config # type: ignore
21
+
22
+ from distributed.actor import Actor, ActorFuture, BaseActorFuture
23
+ from distributed.client import (
24
+ Client,
25
+ CompatibleExecutor,
26
+ Future,
27
+ as_completed,
28
+ default_client,
29
+ fire_and_forget,
30
+ futures_of,
31
+ get_task_metadata,
32
+ get_task_stream,
33
+ performance_report,
34
+ wait,
35
+ )
36
+ from distributed.core import Status, connect, rpc
37
+ from distributed.deploy import (
38
+ Adaptive,
39
+ LocalCluster,
40
+ SpecCluster,
41
+ SSHCluster,
42
+ SubprocessCluster,
43
+ )
44
+ from distributed.diagnostics.plugin import (
45
+ CondaInstall,
46
+ Environ,
47
+ InstallPlugin,
48
+ NannyPlugin,
49
+ PipInstall,
50
+ SchedulerPlugin,
51
+ UploadDirectory,
52
+ UploadFile,
53
+ WorkerPlugin,
54
+ )
55
+ from distributed.diagnostics.progressbar import progress
56
+ from distributed.event import Event
57
+ from distributed.lock import Lock
58
+ from distributed.multi_lock import MultiLock
59
+ from distributed.nanny import Nanny
60
+ from distributed.queues import Queue
61
+ from distributed.scheduler import KilledWorker, Scheduler
62
+ from distributed.security import Security
63
+ from distributed.semaphore import Semaphore
64
+ from distributed.spans import span
65
+ from distributed.threadpoolexecutor import rejoin
66
+ from distributed.utils import CancelledError, TimeoutError, sync
67
+ from distributed.variable import Variable
68
+ from distributed.worker import (
69
+ Reschedule,
70
+ Worker,
71
+ get_client,
72
+ get_worker,
73
+ print,
74
+ secede,
75
+ warn,
76
+ )
77
+ from distributed.worker_client import local_client, worker_client
78
+
79
+ try:
80
+ # Backwards compatibility with versioneer
81
+ from distributed._version import __commit_id__ as __git_revision__
82
+ from distributed._version import __version__
83
+ except ImportError:
84
+ __git_revision__ = "unknown"
85
+ __version__ = "unknown"
86
+
87
+
88
+ __all__ = [
89
+ "Actor",
90
+ "ActorFuture",
91
+ "Adaptive",
92
+ "BaseActorFuture",
93
+ "CancelledError",
94
+ "Client",
95
+ "CompatibleExecutor",
96
+ "CondaInstall",
97
+ "Environ",
98
+ "Event",
99
+ "Future",
100
+ "KilledWorker",
101
+ "LocalCluster",
102
+ "Lock",
103
+ "MultiLock",
104
+ "Nanny",
105
+ "NannyPlugin",
106
+ "InstallPlugin",
107
+ "PipInstall",
108
+ "Queue",
109
+ "Reschedule",
110
+ "SSHCluster",
111
+ "Scheduler",
112
+ "SchedulerPlugin",
113
+ "Security",
114
+ "Semaphore",
115
+ "SpecCluster",
116
+ "Status",
117
+ "SubprocessCluster",
118
+ "TimeoutError",
119
+ "UploadDirectory",
120
+ "UploadFile",
121
+ "Variable",
122
+ "Worker",
123
+ "WorkerPlugin",
124
+ "as_completed",
125
+ "config",
126
+ "connect",
127
+ "dask",
128
+ "default_client",
129
+ "fire_and_forget",
130
+ "futures_of",
131
+ "get_client",
132
+ "get_task_metadata",
133
+ "get_task_stream",
134
+ "get_worker",
135
+ "local_client",
136
+ "performance_report",
137
+ "print",
138
+ "progress",
139
+ "rejoin",
140
+ "rpc",
141
+ "secede",
142
+ "span",
143
+ "sync",
144
+ "wait",
145
+ "warn",
146
+ "widgets",
147
+ "worker_client",
148
+ ]
@@ -0,0 +1,159 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import threading
5
+ from collections.abc import Callable, Coroutine
6
+ from typing import TYPE_CHECKING, Any, TypeVar
7
+
8
+ if TYPE_CHECKING:
9
+ from typing_extensions import ParamSpec
10
+
11
+ P = ParamSpec("P")
12
+ R = TypeVar("R")
13
+ T = TypeVar("T")
14
+ Coro = Coroutine[Any, Any, T]
15
+
16
+
17
+ class _LoopBoundMixin:
18
+ """Backport of the private asyncio.mixins._LoopBoundMixin from 3.11"""
19
+
20
+ _global_lock = threading.Lock()
21
+
22
+ _loop = None
23
+
24
+ def _get_loop(self):
25
+ loop = asyncio.get_running_loop()
26
+
27
+ if self._loop is None:
28
+ with self._global_lock:
29
+ if self._loop is None:
30
+ self._loop = loop
31
+ if loop is not self._loop:
32
+ raise RuntimeError(f"{self!r} is bound to a different event loop")
33
+ return loop
34
+
35
+
36
+ class AsyncTaskGroupClosedError(RuntimeError):
37
+ pass
38
+
39
+
40
+ def _delayed(corofunc: Callable[P, Coro[T]], delay: float) -> Callable[P, Coro[T]]:
41
+ """Decorator to delay the evaluation of a coroutine function by the given delay in seconds."""
42
+
43
+ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
44
+ await asyncio.sleep(delay)
45
+ return await corofunc(*args, **kwargs)
46
+
47
+ return wrapper
48
+
49
+
50
+ class AsyncTaskGroup(_LoopBoundMixin):
51
+ """Collection tracking all currently running asynchronous tasks within a group"""
52
+
53
+ #: If True, the group is closed and does not allow adding new tasks.
54
+ closed: bool
55
+
56
+ def __init__(self) -> None:
57
+ self.closed = False
58
+ self._ongoing_tasks: set[asyncio.Task[None]] = set()
59
+
60
+ def call_soon(
61
+ self, afunc: Callable[P, Coro[None]], /, *args: P.args, **kwargs: P.kwargs
62
+ ) -> None:
63
+ """Schedule a coroutine function to be executed as an `asyncio.Task`.
64
+
65
+ The coroutine function `afunc` is scheduled with `args` arguments and `kwargs` keyword arguments
66
+ as an `asyncio.Task`.
67
+
68
+ Parameters
69
+ ----------
70
+ afunc
71
+ Coroutine function to schedule.
72
+ *args
73
+ Arguments to be passed to `afunc`.
74
+ **kwargs
75
+ Keyword arguments to be passed to `afunc`
76
+
77
+ Returns
78
+ -------
79
+ None
80
+
81
+ Raises
82
+ ------
83
+ AsyncTaskGroupClosedError
84
+ If the task group is closed.
85
+ """
86
+ if self.closed: # Avoid creating a coroutine
87
+ raise AsyncTaskGroupClosedError(
88
+ "Cannot schedule a new coroutine function as the group is already closed."
89
+ )
90
+ task = self._get_loop().create_task(afunc(*args, **kwargs))
91
+ task.add_done_callback(self._ongoing_tasks.remove)
92
+ self._ongoing_tasks.add(task)
93
+ return None
94
+
95
+ def call_later(
96
+ self,
97
+ delay: float,
98
+ afunc: Callable[P, Coro[None]],
99
+ /,
100
+ *args: P.args,
101
+ **kwargs: P.kwargs,
102
+ ) -> None:
103
+ """Schedule a coroutine function to be executed after `delay` seconds as an `asyncio.Task`.
104
+
105
+ The coroutine function `afunc` is scheduled with `args` arguments and `kwargs` keyword arguments
106
+ as an `asyncio.Task` that is executed after `delay` seconds.
107
+
108
+ Parameters
109
+ ----------
110
+ delay
111
+ Delay in seconds.
112
+ afunc
113
+ Coroutine function to schedule.
114
+ *args
115
+ Arguments to be passed to `afunc`.
116
+ **kwargs
117
+ Keyword arguments to be passed to `afunc`
118
+
119
+ Returns
120
+ -------
121
+ The None
122
+
123
+ Raises
124
+ ------
125
+ AsyncTaskGroupClosedError
126
+ If the task group is closed.
127
+ """
128
+ self.call_soon(_delayed(afunc, delay), *args, **kwargs)
129
+
130
+ def close(self) -> None:
131
+ """Closes the task group so that no new tasks can be scheduled.
132
+
133
+ Existing tasks continue to run.
134
+ """
135
+ self.closed = True
136
+
137
+ async def stop(self) -> None:
138
+ """Close the group and stop all currently running tasks.
139
+
140
+ Closes the task group and cancels all tasks. All tasks are cancelled
141
+ an additional time for each time this task is cancelled.
142
+ """
143
+ self.close()
144
+
145
+ current_task = asyncio.current_task(self._get_loop())
146
+ err = None
147
+ while tasks_to_stop := (self._ongoing_tasks - {current_task}):
148
+ for task in tasks_to_stop:
149
+ task.cancel()
150
+ try:
151
+ await asyncio.wait(tasks_to_stop)
152
+ except asyncio.CancelledError as e:
153
+ err = e
154
+
155
+ if err is not None:
156
+ raise err
157
+
158
+ def __len__(self):
159
+ return len(self._ongoing_tasks)
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import AsyncIterator
5
+ from contextlib import asynccontextmanager
6
+
7
+
8
+ class RLock:
9
+ """asyncio reentrant lock, which allows the same owner (or two owners that compare
10
+ as equal) to be inside the critical section at the same time.
11
+
12
+ Note that here the owner is an explicit, generic object, whereas in
13
+ :func:`threading.RLock` and :func:`multiprocessing.RLock` it's hardcoded
14
+ respectively to the thread ID and process ID.
15
+
16
+ **Usage**::
17
+
18
+ lock = RLock()
19
+ async with lock("my-owner"):
20
+ ...
21
+
22
+ **Tip**
23
+
24
+ You can mix reentrant and non-reentrant owners; all you need to do is create an
25
+ owner that doesn't compare as equal to other instances of itself::
26
+
27
+ lock = RLock()
28
+
29
+ async def non_reentrant():
30
+ async with lock(object()):
31
+ ...
32
+
33
+ async def reentrant():
34
+ async with lock("foo"):
35
+ ...
36
+
37
+ In the above example, at any time you may have inside the critical section
38
+ at most one call to ``non_reentrant`` and no calls to ``reentrant``, or any number
39
+ of calls to ``reentrant`` but no calls to ``non_reentrant``.
40
+ """
41
+
42
+ _owner: object
43
+ _count: int
44
+ _lock: asyncio.Lock
45
+
46
+ def __init__(self):
47
+ self._owner = None
48
+ self._count = 0
49
+ self._lock = asyncio.Lock()
50
+
51
+ async def acquire(self, owner: object) -> None:
52
+ if self._count == 0 or self._owner != owner:
53
+ await self._lock.acquire()
54
+ self._owner = owner
55
+ self._count += 1
56
+
57
+ def release(self, owner: object) -> None:
58
+ if self._count == 0 or self._owner != owner:
59
+ raise RuntimeError("release unlocked lock or mismatched owner")
60
+ self._count -= 1
61
+ if self._count == 0:
62
+ self._owner = None
63
+ self._lock.release()
64
+
65
+ @asynccontextmanager
66
+ async def __call__(self, owner: object) -> AsyncIterator[None]:
67
+ await self.acquire(owner)
68
+ try:
69
+ yield
70
+ finally:
71
+ self.release(owner)
@@ -0,0 +1,166 @@
1
+ # This was copied from CPython 3.6
2
+
3
+ # Copyright 2009 Brian Quinlan. All Rights Reserved.
4
+ # Licensed to PSF under a Contributor Agreement.
5
+
6
+ """Implements ThreadPoolExecutor."""
7
+
8
+ from __future__ import annotations
9
+
10
+ __author__ = "Brian Quinlan (brian@sweetapp.com)"
11
+
12
+ import atexit
13
+ import itertools
14
+ import os
15
+ import queue
16
+ import threading
17
+ import weakref
18
+ from concurrent.futures import _base
19
+
20
+ # Workers are created as daemon threads. This is done to allow the interpreter
21
+ # to exit when there are still idle threads in a ThreadPoolExecutor's thread
22
+ # pool (i.e. shutdown() was not called). However, allowing workers to die with
23
+ # the interpreter has two undesirable properties:
24
+ # - The workers would still be running during interpreter shutdown,
25
+ # meaning that they would fail in unpredictable ways.
26
+ # - The workers could be killed while evaluating a work item, which could
27
+ # be bad if the callable being evaluated has external side-effects e.g.
28
+ # writing to a file.
29
+ #
30
+ # To work around this problem, an exit handler is installed which tells the
31
+ # workers to exit when their work queues are empty and then waits until the
32
+ # threads finish.
33
+
34
+ _threads_queues: weakref.WeakKeyDictionary[threading.Thread, queue.Queue] = (
35
+ weakref.WeakKeyDictionary()
36
+ )
37
+ _shutdown = False
38
+
39
+
40
+ def _python_exit():
41
+ global _shutdown
42
+ _shutdown = True
43
+ items = list(_threads_queues.items())
44
+ for _, q in items:
45
+ q.put(None)
46
+ for t, _ in items:
47
+ t.join()
48
+
49
+
50
+ atexit.register(_python_exit)
51
+
52
+
53
+ class _WorkItem:
54
+ def __init__(self, future, fn, args, kwargs):
55
+ self.future = future
56
+ self.fn = fn
57
+ self.args = args
58
+ self.kwargs = kwargs
59
+
60
+ def run(self):
61
+ if not self.future.set_running_or_notify_cancel(): # pragma: no cover
62
+ return
63
+
64
+ try:
65
+ result = self.fn(*self.args, **self.kwargs)
66
+ except BaseException as e:
67
+ self.future.set_exception(e)
68
+ else:
69
+ self.future.set_result(result)
70
+
71
+
72
+ def _worker(executor_reference, work_queue):
73
+ try:
74
+ while True:
75
+ work_item = work_queue.get(block=True)
76
+ if work_item is not None:
77
+ work_item.run()
78
+ # Delete references to object. See issue16284
79
+ del work_item
80
+ continue
81
+ executor = executor_reference()
82
+ # Exit if:
83
+ # - The interpreter is shutting down OR
84
+ # - The executor that owns the worker has been collected OR
85
+ # - The executor that owns the worker has been shutdown.
86
+ if _shutdown or executor is None or executor._shutdown:
87
+ # Notice other workers
88
+ work_queue.put(None)
89
+ return
90
+ del executor
91
+ except BaseException:
92
+ _base.LOGGER.critical("Exception in worker", exc_info=True)
93
+
94
+
95
+ class ThreadPoolExecutor(_base.Executor):
96
+ # Used to assign unique thread names when thread_name_prefix is not supplied.
97
+ _counter = itertools.count()
98
+
99
+ def __init__(self, max_workers=None, thread_name_prefix=""):
100
+ """Initializes a new ThreadPoolExecutor instance.
101
+
102
+ Args:
103
+ max_workers: The maximum number of threads that can be used to
104
+ execute the given calls.
105
+ thread_name_prefix: An optional name prefix to give our threads.
106
+ """
107
+ if max_workers is None:
108
+ # Use this number because ThreadPoolExecutor is often
109
+ # used to overlap I/O instead of CPU work.
110
+ max_workers = (os.cpu_count() or 1) * 5
111
+ if max_workers <= 0:
112
+ raise ValueError("max_workers must be greater than 0")
113
+
114
+ self._max_workers = max_workers
115
+ self._work_queue = queue.Queue()
116
+ self._threads = set()
117
+ self._shutdown = False
118
+ self._shutdown_lock = threading.Lock()
119
+ self._thread_name_prefix = thread_name_prefix or (
120
+ "ThreadPoolExecutor-%d" % next(self._counter)
121
+ )
122
+
123
+ def submit(self, fn, *args, **kwargs):
124
+ with self._shutdown_lock:
125
+ if self._shutdown: # pragma: no cover
126
+ raise RuntimeError("cannot schedule new futures after shutdown")
127
+
128
+ f = _base.Future()
129
+ w = _WorkItem(f, fn, args, kwargs)
130
+
131
+ self._work_queue.put(w)
132
+ self._adjust_thread_count()
133
+ return f
134
+
135
+ submit.__doc__ = _base.Executor.submit.__doc__
136
+
137
+ def _adjust_thread_count(self):
138
+ # When the executor gets lost, the weakref callback will wake up
139
+ # the worker threads.
140
+ def weakref_cb(_, q=self._work_queue):
141
+ q.put(None)
142
+
143
+ # TODO(bquinlan): Should avoid creating new threads if there are more
144
+ # idle threads than items in the work queue.
145
+ num_threads = len(self._threads)
146
+ if num_threads < self._max_workers:
147
+ thread_name = "%s_%d" % (self._thread_name_prefix or self, num_threads)
148
+ t = threading.Thread(
149
+ name=thread_name,
150
+ target=_worker,
151
+ args=(weakref.ref(self, weakref_cb), self._work_queue),
152
+ )
153
+ t.daemon = True
154
+ t.start()
155
+ self._threads.add(t)
156
+ _threads_queues[t] = self._work_queue
157
+
158
+ def shutdown(self, wait=True):
159
+ with self._shutdown_lock:
160
+ self._shutdown = True
161
+ self._work_queue.put(None)
162
+ if wait:
163
+ for t in self._threads:
164
+ t.join()
165
+
166
+ shutdown.__doc__ = _base.Executor.shutdown.__doc__
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ import signal
6
+ from typing import Any
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ async def wait_for_signals() -> int:
12
+ """Wait for sigint or sigterm by setting global signal handlers"""
13
+ signals = (signal.SIGINT, signal.SIGTERM)
14
+ loop = asyncio.get_running_loop()
15
+ event = asyncio.Event()
16
+
17
+ old_handlers: dict[int, Any] = {}
18
+ caught_signal: int | None = None
19
+
20
+ def handle_signal(signum, frame):
21
+ # *** Do not log or print anything in here
22
+ # https://stackoverflow.com/questions/45680378/how-to-explain-the-reentrant-runtimeerror-caused-by-printing-in-signal-handlers
23
+ nonlocal caught_signal
24
+ caught_signal = signum
25
+ # Restore old signal handler to allow for quicker exit
26
+ # if the user sends the signal again.
27
+ signal.signal(signum, old_handlers[signum])
28
+ loop.call_soon_threadsafe(event.set)
29
+
30
+ for sig in signals:
31
+ old_handlers[sig] = signal.signal(sig, handle_signal)
32
+
33
+ try:
34
+ await event.wait()
35
+ assert caught_signal
36
+ logger.info(
37
+ "Received signal %s (%d)", signal.Signals(caught_signal).name, caught_signal
38
+ )
39
+ return caught_signal
40
+ finally:
41
+ for sig in signals:
42
+ signal.signal(sig, old_handlers[sig])
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Collection, Iterable
4
+ from typing import TYPE_CHECKING
5
+
6
+ from dask.typing import Key
7
+
8
+ if TYPE_CHECKING:
9
+ # Circular import
10
+ from distributed.scheduler import Transition
11
+
12
+
13
+ def scheduler_story(
14
+ keys_or_stimuli: set[Key | str], transition_log: Iterable[Transition]
15
+ ) -> list[Transition]:
16
+ """Creates a story from the scheduler transition log given a set of keys
17
+ describing tasks or stimuli.
18
+
19
+ Parameters
20
+ ----------
21
+ keys_or_stimuli : set[str]
22
+ Task keys or stimulus_id's
23
+ log : iterable
24
+ The scheduler transition log
25
+
26
+ Returns
27
+ -------
28
+ story : list[tuple]
29
+ """
30
+ return [
31
+ t
32
+ for t in transition_log
33
+ if t[0] in keys_or_stimuli or keys_or_stimuli.intersection(t[3])
34
+ ]
35
+
36
+
37
+ def worker_story(keys_or_stimuli: Collection[Key | str], log: Iterable[tuple]) -> list:
38
+ """Creates a story from the worker log given a set of keys
39
+ describing tasks or stimuli.
40
+
41
+ Parameters
42
+ ----------
43
+ keys_or_stimuli : set[str]
44
+ Task keys or stimulus_id's
45
+ log : iterable
46
+ The worker log
47
+
48
+ Returns
49
+ -------
50
+ story : list[str]
51
+ """
52
+ return [
53
+ msg
54
+ for msg in log
55
+ if any(key in msg for key in keys_or_stimuli)
56
+ or any(
57
+ key in c
58
+ for key in keys_or_stimuli
59
+ for c in msg
60
+ if isinstance(c, (tuple, list, set))
61
+ )
62
+ ]
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '2026.3.6'
22
+ __version_tuple__ = version_tuple = (2026, 3, 6)
23
+
24
+ __commit_id__ = commit_id = 'geb11c4c6b'