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.
- distributed/__init__.py +148 -0
- distributed/_async_taskgroup.py +159 -0
- distributed/_asyncio.py +71 -0
- distributed/_concurrent_futures_thread.py +166 -0
- distributed/_signals.py +42 -0
- distributed/_stories.py +62 -0
- distributed/_version.py +24 -0
- distributed/active_memory_manager.py +739 -0
- distributed/actor.py +314 -0
- distributed/batched.py +197 -0
- distributed/bokeh.py +3 -0
- distributed/broker.py +104 -0
- distributed/cfexecutor.py +176 -0
- distributed/chaos.py +68 -0
- distributed/cli/__init__.py +0 -0
- distributed/cli/dask_scheduler.py +257 -0
- distributed/cli/dask_spec.py +67 -0
- distributed/cli/dask_ssh.py +209 -0
- distributed/cli/dask_worker.py +546 -0
- distributed/cli/utils.py +38 -0
- distributed/client.py +6436 -0
- distributed/cluster_dump.py +340 -0
- distributed/collections.py +214 -0
- distributed/comm/__init__.py +36 -0
- distributed/comm/addressing.py +303 -0
- distributed/comm/core.py +411 -0
- distributed/comm/inproc.py +383 -0
- distributed/comm/registry.py +92 -0
- distributed/comm/tcp.py +773 -0
- distributed/comm/ucx.py +87 -0
- distributed/comm/utils.py +126 -0
- distributed/comm/ws.py +491 -0
- distributed/compatibility.py +267 -0
- distributed/config.py +217 -0
- distributed/core.py +1737 -0
- distributed/counter.py +65 -0
- distributed/dashboard/__init__.py +0 -0
- distributed/dashboard/components/__init__.py +56 -0
- distributed/dashboard/components/nvml.py +191 -0
- distributed/dashboard/components/rmm.py +211 -0
- distributed/dashboard/components/scheduler.py +4841 -0
- distributed/dashboard/components/shared.py +596 -0
- distributed/dashboard/components/worker.py +484 -0
- distributed/dashboard/core.py +60 -0
- distributed/dashboard/export_tool.js +79 -0
- distributed/dashboard/export_tool.py +29 -0
- distributed/dashboard/scheduler.py +194 -0
- distributed/dashboard/templates/__init__.py +0 -0
- distributed/dashboard/templates/performance_report.html +6 -0
- distributed/dashboard/theme.yaml +9 -0
- distributed/dashboard/utils.py +67 -0
- distributed/dashboard/worker.py +32 -0
- distributed/deploy/__init__.py +13 -0
- distributed/deploy/adaptive.py +302 -0
- distributed/deploy/adaptive_core.py +213 -0
- distributed/deploy/cluster.py +648 -0
- distributed/deploy/local.py +280 -0
- distributed/deploy/old_ssh.py +497 -0
- distributed/deploy/spec.py +707 -0
- distributed/deploy/ssh.py +461 -0
- distributed/deploy/subprocess.py +287 -0
- distributed/deploy/utils.py +41 -0
- distributed/diagnostics/__init__.py +5 -0
- distributed/diagnostics/cluster_dump.py +41 -0
- distributed/diagnostics/cudf.py +25 -0
- distributed/diagnostics/eventstream.py +74 -0
- distributed/diagnostics/graph_layout.py +149 -0
- distributed/diagnostics/memory_sampler.py +231 -0
- distributed/diagnostics/memray.py +257 -0
- distributed/diagnostics/nvml.py +380 -0
- distributed/diagnostics/plugin.py +1104 -0
- distributed/diagnostics/progress.py +424 -0
- distributed/diagnostics/progress_stream.py +203 -0
- distributed/diagnostics/progressbar.py +508 -0
- distributed/diagnostics/rmm.py +43 -0
- distributed/diagnostics/task_stream.py +194 -0
- distributed/diagnostics/websocket.py +71 -0
- distributed/diskutils.py +283 -0
- distributed/distributed-schema.yaml +1250 -0
- distributed/distributed.yaml +356 -0
- distributed/event.py +268 -0
- distributed/exceptions.py +45 -0
- distributed/gc.py +278 -0
- distributed/http/__init__.py +3 -0
- distributed/http/health.py +12 -0
- distributed/http/prometheus.py +45 -0
- distributed/http/proxy.py +145 -0
- distributed/http/routing.py +69 -0
- distributed/http/scheduler/__init__.py +0 -0
- distributed/http/scheduler/api.py +88 -0
- distributed/http/scheduler/info.py +302 -0
- distributed/http/scheduler/json.py +74 -0
- distributed/http/scheduler/missing_bokeh.py +18 -0
- distributed/http/scheduler/prometheus/__init__.py +13 -0
- distributed/http/scheduler/prometheus/core.py +246 -0
- distributed/http/scheduler/prometheus/semaphore.py +87 -0
- distributed/http/scheduler/prometheus/stealing.py +48 -0
- distributed/http/static/__init__.py +0 -0
- distributed/http/static/css/__init__.py +0 -0
- distributed/http/static/css/base.css +152 -0
- distributed/http/static/css/gpu.css +16 -0
- distributed/http/static/css/individual-cluster-map.css +54 -0
- distributed/http/static/css/sortable.min.css +1 -0
- distributed/http/static/css/sortable.min.css.map +1 -0
- distributed/http/static/css/status.css +58 -0
- distributed/http/static/images/__init__.py +0 -0
- distributed/http/static/images/dask-logo.svg +25 -0
- distributed/http/static/images/fa-bars.svg +1 -0
- distributed/http/static/images/favicon.ico +0 -0
- distributed/http/static/images/jupyter.svg +90 -0
- distributed/http/static/images/numpy.png +0 -0
- distributed/http/static/images/pandas.png +0 -0
- distributed/http/static/images/python.png +0 -0
- distributed/http/static/individual-cluster-map.html +27 -0
- distributed/http/static/js/__init__.py +0 -0
- distributed/http/static/js/anime.min.js +8 -0
- distributed/http/static/js/individual-cluster-map.js +367 -0
- distributed/http/static/js/reconnecting-websocket.min.js +8 -0
- distributed/http/static/js/sortable.min.js +3 -0
- distributed/http/statics.py +13 -0
- distributed/http/templates/__init__.py +0 -0
- distributed/http/templates/base.html +88 -0
- distributed/http/templates/call-stack.html +15 -0
- distributed/http/templates/exceptions.html +54 -0
- distributed/http/templates/gpu.html +22 -0
- distributed/http/templates/json-index.html +11 -0
- distributed/http/templates/logs.html +8 -0
- distributed/http/templates/main.html +18 -0
- distributed/http/templates/simple.html +6 -0
- distributed/http/templates/status.html +34 -0
- distributed/http/templates/task.html +167 -0
- distributed/http/templates/worker-table.html +76 -0
- distributed/http/templates/worker.html +67 -0
- distributed/http/templates/workers.html +13 -0
- distributed/http/utils.py +51 -0
- distributed/http/worker/__init__.py +0 -0
- distributed/http/worker/prometheus/__init__.py +13 -0
- distributed/http/worker/prometheus/core.py +280 -0
- distributed/itertools.py +44 -0
- distributed/lock.py +115 -0
- distributed/metrics.py +426 -0
- distributed/multi_lock.py +237 -0
- distributed/nanny.py +1035 -0
- distributed/node.py +192 -0
- distributed/objects.py +47 -0
- distributed/preloading.py +276 -0
- distributed/process.py +389 -0
- distributed/proctitle.py +44 -0
- distributed/profile.py +610 -0
- distributed/protocol/__init__.py +124 -0
- distributed/protocol/arrow.py +59 -0
- distributed/protocol/compression.py +204 -0
- distributed/protocol/core.py +181 -0
- distributed/protocol/cuda.py +44 -0
- distributed/protocol/cupy.py +110 -0
- distributed/protocol/h5py.py +32 -0
- distributed/protocol/keras.py +43 -0
- distributed/protocol/netcdf4.py +54 -0
- distributed/protocol/numba.py +69 -0
- distributed/protocol/numpy.py +219 -0
- distributed/protocol/pickle.py +98 -0
- distributed/protocol/rmm.py +48 -0
- distributed/protocol/scipy.py +36 -0
- distributed/protocol/serialize.py +1003 -0
- distributed/protocol/sparse.py +37 -0
- distributed/protocol/torch.py +70 -0
- distributed/protocol/utils.py +274 -0
- distributed/protocol/utils_test.py +28 -0
- distributed/publish.py +132 -0
- distributed/py.typed +0 -0
- distributed/queues.py +303 -0
- distributed/recreate_tasks.py +192 -0
- distributed/scheduler.py +9480 -0
- distributed/security.py +357 -0
- distributed/semaphore.py +567 -0
- distributed/shuffle/__init__.py +13 -0
- distributed/shuffle/_arrow.py +179 -0
- distributed/shuffle/_buffer.py +264 -0
- distributed/shuffle/_comms.py +76 -0
- distributed/shuffle/_core.py +608 -0
- distributed/shuffle/_disk.py +228 -0
- distributed/shuffle/_exceptions.py +26 -0
- distributed/shuffle/_limiter.py +89 -0
- distributed/shuffle/_memory.py +47 -0
- distributed/shuffle/_merge.py +65 -0
- distributed/shuffle/_pickle.py +42 -0
- distributed/shuffle/_rechunk.py +1159 -0
- distributed/shuffle/_scheduler_plugin.py +557 -0
- distributed/shuffle/_shuffle.py +387 -0
- distributed/shuffle/_worker_plugin.py +435 -0
- distributed/sizeof.py +25 -0
- distributed/spans.py +685 -0
- distributed/spill.py +361 -0
- distributed/stealing.py +613 -0
- distributed/system.py +67 -0
- distributed/system_monitor.py +241 -0
- distributed/threadpoolexecutor.py +193 -0
- distributed/utils.py +1976 -0
- distributed/utils_comm.py +422 -0
- distributed/utils_test.py +2624 -0
- distributed/variable.py +260 -0
- distributed/versions.py +159 -0
- distributed/widgets/__init__.py +11 -0
- distributed/widgets/templates/__init__.py +0 -0
- distributed/widgets/templates/client.html.j2 +53 -0
- distributed/widgets/templates/cluster.html.j2 +38 -0
- distributed/widgets/templates/computation.html.j2 +42 -0
- distributed/widgets/templates/future.html.j2 +14 -0
- distributed/widgets/templates/has_what.html.j2 +24 -0
- distributed/widgets/templates/local_cluster.html.j2 +7 -0
- distributed/widgets/templates/log.html.j2 +11 -0
- distributed/widgets/templates/logs.html.j2 +6 -0
- distributed/widgets/templates/process_interface.html.j2 +31 -0
- distributed/widgets/templates/scheduler.html.j2 +4 -0
- distributed/widgets/templates/scheduler_info.html.j2 +139 -0
- distributed/widgets/templates/security.html.j2 +13 -0
- distributed/widgets/templates/task_state.html.j2 +13 -0
- distributed/widgets/templates/who_has.html.j2 +15 -0
- distributed/widgets/templates/worker_state.html.j2 +5 -0
- distributed/worker.py +3484 -0
- distributed/worker_client.py +91 -0
- distributed/worker_memory.py +550 -0
- distributed/worker_state_machine.py +3953 -0
- dvt_distributed-2026.3.6.dist-info/METADATA +71 -0
- dvt_distributed-2026.3.6.dist-info/RECORD +231 -0
- dvt_distributed-2026.3.6.dist-info/WHEEL +5 -0
- dvt_distributed-2026.3.6.dist-info/entry_points.txt +10 -0
- dvt_distributed-2026.3.6.dist-info/licenses/LICENSE.txt +29 -0
- dvt_distributed-2026.3.6.dist-info/top_level.txt +2 -0
- scripts/__init__.py +0 -0
- scripts/upload_builds.py +309 -0
distributed/__init__.py
ADDED
|
@@ -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)
|
distributed/_asyncio.py
ADDED
|
@@ -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__
|
distributed/_signals.py
ADDED
|
@@ -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])
|
distributed/_stories.py
ADDED
|
@@ -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
|
+
]
|
distributed/_version.py
ADDED
|
@@ -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'
|