executorlib 1.10.3__tar.gz → 1.11.0__tar.gz
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.
- {executorlib-1.10.3 → executorlib-1.11.0}/PKG-INFO +20 -20
- {executorlib-1.10.3 → executorlib-1.11.0}/pyproject.toml +23 -23
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/_version.py +2 -2
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/executor/flux.py +4 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/executor/single.py +4 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/executor/slurm.py +4 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/hdf.py +14 -10
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/inputcheck.py +17 -0
- executorlib-1.11.0/src/executorlib/standalone/queue.py +36 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/select.py +2 -1
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/file/shared.py +158 -120
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/blockallocation.py +2 -2
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/spawner_pysqa.py +7 -1
- executorlib-1.10.3/src/executorlib/standalone/queue.py +0 -19
- {executorlib-1.10.3 → executorlib-1.11.0}/.gitignore +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/LICENSE +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/README.md +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/__init__.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/api.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/backend/__init__.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/backend/cache_parallel.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/backend/cache_serial.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/backend/interactive_parallel.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/backend/interactive_serial.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/executor/__init__.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/executor/base.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/__init__.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/batched.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/command.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/command_pysqa.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/error.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/interactive/__init__.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/interactive/arguments.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/interactive/backend.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/interactive/communication.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/interactive/spawner.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/serialize.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/validate.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/__init__.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/base.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/file/__init__.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/file/backend.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/file/spawner_pysqa.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/file/spawner_subprocess.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/file/task_scheduler.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/__init__.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/dependency.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/dependency_plot.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/onetoone.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/shared.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/spawner_flux.py +0 -0
- {executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/spawner_slurm.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: executorlib
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.11.0
|
|
4
4
|
Summary: Up-scale python functions for high performance computing (HPC) with executorlib.
|
|
5
5
|
Project-URL: Homepage, https://github.com/pyiron/executorlib
|
|
6
6
|
Project-URL: Documentation, https://executorlib.readthedocs.io
|
|
@@ -48,32 +48,32 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
48
48
|
Classifier: Programming Language :: Python :: 3.14
|
|
49
49
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
50
50
|
Requires-Python: <3.15,>3.9
|
|
51
|
-
Requires-Dist: cloudpickle
|
|
52
|
-
Requires-Dist: pyzmq
|
|
51
|
+
Requires-Dist: cloudpickle<3.2.0,>=2.0.0
|
|
52
|
+
Requires-Dist: pyzmq<27.3.0,>=25.0.0
|
|
53
53
|
Provides-Extra: all
|
|
54
|
-
Requires-Dist: h5py
|
|
55
|
-
Requires-Dist: ipython
|
|
56
|
-
Requires-Dist: mpi4py
|
|
57
|
-
Requires-Dist: networkx
|
|
58
|
-
Requires-Dist: pydantic
|
|
59
|
-
Requires-Dist: pygraphviz
|
|
60
|
-
Requires-Dist: pysqa==1.
|
|
54
|
+
Requires-Dist: h5py<3.17.0,>=3.6.0; extra == 'all'
|
|
55
|
+
Requires-Dist: ipython<9.10.0,>=7.33.0; extra == 'all'
|
|
56
|
+
Requires-Dist: mpi4py<4.2.0,>=3.1.4; extra == 'all'
|
|
57
|
+
Requires-Dist: networkx<3.7.0,>=2.8.8; extra == 'all'
|
|
58
|
+
Requires-Dist: pydantic<2.15.0,>=2.5.3; extra == 'all'
|
|
59
|
+
Requires-Dist: pygraphviz<2.1.0,>=1.10; extra == 'all'
|
|
60
|
+
Requires-Dist: pysqa==1.1.0; extra == 'all'
|
|
61
61
|
Provides-Extra: cache
|
|
62
|
-
Requires-Dist: h5py
|
|
62
|
+
Requires-Dist: h5py<3.17.0,>=3.6.0; extra == 'cache'
|
|
63
63
|
Provides-Extra: cluster
|
|
64
|
-
Requires-Dist: h5py
|
|
65
|
-
Requires-Dist: pysqa==1.
|
|
64
|
+
Requires-Dist: h5py<3.17.0,>=3.6.0; extra == 'cluster'
|
|
65
|
+
Requires-Dist: pysqa==1.1.0; extra == 'cluster'
|
|
66
66
|
Provides-Extra: graph
|
|
67
|
-
Requires-Dist: networkx
|
|
68
|
-
Requires-Dist: pygraphviz
|
|
67
|
+
Requires-Dist: networkx<3.7.0,>=2.8.8; extra == 'graph'
|
|
68
|
+
Requires-Dist: pygraphviz<2.1.0,>=1.10; extra == 'graph'
|
|
69
69
|
Provides-Extra: graphnotebook
|
|
70
|
-
Requires-Dist: ipython
|
|
71
|
-
Requires-Dist: networkx
|
|
72
|
-
Requires-Dist: pygraphviz
|
|
70
|
+
Requires-Dist: ipython<9.10.0,>=7.33.0; extra == 'graphnotebook'
|
|
71
|
+
Requires-Dist: networkx<3.7.0,>=2.8.8; extra == 'graphnotebook'
|
|
72
|
+
Requires-Dist: pygraphviz<2.1.0,>=1.10; extra == 'graphnotebook'
|
|
73
73
|
Provides-Extra: mpi
|
|
74
|
-
Requires-Dist: mpi4py
|
|
74
|
+
Requires-Dist: mpi4py<4.2.0,>=3.1.4; extra == 'mpi'
|
|
75
75
|
Provides-Extra: validation
|
|
76
|
-
Requires-Dist: pydantic
|
|
76
|
+
Requires-Dist: pydantic<2.15.0,>=2.5.3; extra == 'validation'
|
|
77
77
|
Description-Content-Type: text/markdown
|
|
78
78
|
|
|
79
79
|
# executorlib
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[build-system]
|
|
2
2
|
requires = [
|
|
3
|
-
"hatchling>=1.27.0
|
|
4
|
-
"hatch-vcs
|
|
5
|
-
"cloudpickle>=2.0.0
|
|
6
|
-
"pyzmq>=25.0.0
|
|
3
|
+
"hatchling>=1.27.0,<1.33.0",
|
|
4
|
+
"hatch-vcs>=0.4.0,<0.6.0",
|
|
5
|
+
"cloudpickle>=2.0.0,<3.2.0",
|
|
6
|
+
"pyzmq>=25.0.0,<27.3.0",
|
|
7
7
|
]
|
|
8
8
|
build-backend = "hatchling.build"
|
|
9
9
|
|
|
@@ -30,8 +30,8 @@ classifiers = [
|
|
|
30
30
|
"Programming Language :: Python :: 3.14",
|
|
31
31
|
]
|
|
32
32
|
dependencies = [
|
|
33
|
-
"cloudpickle>=2.0.0
|
|
34
|
-
"pyzmq>=25.0.0
|
|
33
|
+
"cloudpickle>=2.0.0,<3.2.0",
|
|
34
|
+
"pyzmq>=25.0.0,<27.3.0",
|
|
35
35
|
]
|
|
36
36
|
dynamic = ["version"]
|
|
37
37
|
|
|
@@ -41,32 +41,32 @@ Documentation = "https://executorlib.readthedocs.io"
|
|
|
41
41
|
Repository = "https://github.com/pyiron/executorlib"
|
|
42
42
|
|
|
43
43
|
[project.optional-dependencies]
|
|
44
|
-
cache = ["h5py>=3.6.0
|
|
44
|
+
cache = ["h5py>=3.6.0,<3.17.0"]
|
|
45
45
|
graph = [
|
|
46
|
-
"pygraphviz>=1.10
|
|
47
|
-
"networkx>=2.8.8
|
|
46
|
+
"pygraphviz>=1.10,<2.1.0",
|
|
47
|
+
"networkx>=2.8.8,<3.7.0",
|
|
48
48
|
]
|
|
49
49
|
graphnotebook = [
|
|
50
|
-
"pygraphviz>=1.10
|
|
51
|
-
"networkx>=2.8.8
|
|
52
|
-
"ipython>=7.33.0
|
|
50
|
+
"pygraphviz>=1.10,<2.1.0",
|
|
51
|
+
"networkx>=2.8.8,<3.7.0",
|
|
52
|
+
"ipython>=7.33.0,<9.10.0",
|
|
53
53
|
]
|
|
54
|
-
mpi = ["mpi4py>=3.1.4
|
|
54
|
+
mpi = ["mpi4py>=3.1.4,<4.2.0"]
|
|
55
55
|
cluster = [
|
|
56
|
-
"pysqa==1.
|
|
57
|
-
"h5py>=3.6.0
|
|
56
|
+
"pysqa==1.1.0",
|
|
57
|
+
"h5py>=3.6.0,<3.17.0",
|
|
58
58
|
]
|
|
59
59
|
validation = [
|
|
60
|
-
"pydantic>=2.5.3
|
|
60
|
+
"pydantic>=2.5.3,<2.15.0",
|
|
61
61
|
]
|
|
62
62
|
all = [
|
|
63
|
-
"mpi4py>=3.1.4
|
|
64
|
-
"pysqa==1.
|
|
65
|
-
"h5py>=3.6.0
|
|
66
|
-
"pydantic>=2.5.3
|
|
67
|
-
"pygraphviz>=1.10
|
|
68
|
-
"networkx>=2.8.8
|
|
69
|
-
"ipython>=7.33.0
|
|
63
|
+
"mpi4py>=3.1.4,<4.2.0",
|
|
64
|
+
"pysqa==1.1.0",
|
|
65
|
+
"h5py>=3.6.0,<3.17.0",
|
|
66
|
+
"pydantic>=2.5.3,<2.15.0",
|
|
67
|
+
"pygraphviz>=1.10,<2.1.0",
|
|
68
|
+
"networkx>=2.8.8,<3.7.0",
|
|
69
|
+
"ipython>=7.33.0,<9.10.0",
|
|
70
70
|
]
|
|
71
71
|
|
|
72
72
|
[tool.ruff]
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.
|
|
22
|
-
__version_tuple__ = version_tuple = (1,
|
|
21
|
+
__version__ = version = '1.11.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 11, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -4,6 +4,7 @@ from executorlib.executor.base import BaseExecutor
|
|
|
4
4
|
from executorlib.standalone.inputcheck import (
|
|
5
5
|
check_command_line_argument_lst,
|
|
6
6
|
check_init_function,
|
|
7
|
+
check_init_function_with_cache,
|
|
7
8
|
check_log_obj_size,
|
|
8
9
|
check_oversubscribe,
|
|
9
10
|
check_plot_dependency_graph,
|
|
@@ -580,6 +581,9 @@ def create_flux_executor(
|
|
|
580
581
|
executor_kwargs["hostname_localhost"] = hostname_localhost
|
|
581
582
|
executor_kwargs["log_obj_size"] = log_obj_size
|
|
582
583
|
check_init_function(block_allocation=block_allocation, init_function=init_function)
|
|
584
|
+
check_init_function_with_cache(
|
|
585
|
+
init_function=init_function, cache_directory=cache_directory
|
|
586
|
+
)
|
|
583
587
|
check_pmi(backend="flux_allocation", pmi=pmi_mode)
|
|
584
588
|
check_oversubscribe(
|
|
585
589
|
oversubscribe=executor_kwargs.get("openmpi_oversubscribe", False)
|
|
@@ -5,6 +5,7 @@ from executorlib.standalone.inputcheck import (
|
|
|
5
5
|
check_command_line_argument_lst,
|
|
6
6
|
check_gpus_per_worker,
|
|
7
7
|
check_init_function,
|
|
8
|
+
check_init_function_with_cache,
|
|
8
9
|
check_plot_dependency_graph,
|
|
9
10
|
check_refresh_rate,
|
|
10
11
|
check_restart_limit,
|
|
@@ -511,6 +512,9 @@ def create_single_node_executor(
|
|
|
511
512
|
executor_kwargs["log_obj_size"] = log_obj_size
|
|
512
513
|
|
|
513
514
|
check_init_function(block_allocation=block_allocation, init_function=init_function)
|
|
515
|
+
check_init_function_with_cache(
|
|
516
|
+
init_function=init_function, cache_directory=cache_directory
|
|
517
|
+
)
|
|
514
518
|
check_gpus_per_worker(gpus_per_worker=executor_kwargs.get("gpus_per_core", 0))
|
|
515
519
|
check_command_line_argument_lst(
|
|
516
520
|
command_line_argument_lst=executor_kwargs.get("slurm_cmd_args", [])
|
|
@@ -3,6 +3,7 @@ from typing import Callable, Optional, Union
|
|
|
3
3
|
from executorlib.executor.base import BaseExecutor
|
|
4
4
|
from executorlib.standalone.inputcheck import (
|
|
5
5
|
check_init_function,
|
|
6
|
+
check_init_function_with_cache,
|
|
6
7
|
check_log_obj_size,
|
|
7
8
|
check_plot_dependency_graph,
|
|
8
9
|
check_refresh_rate,
|
|
@@ -552,6 +553,9 @@ def create_slurm_executor(
|
|
|
552
553
|
executor_kwargs["log_obj_size"] = log_obj_size
|
|
553
554
|
executor_kwargs["pmi_mode"] = pmi_mode
|
|
554
555
|
check_init_function(block_allocation=block_allocation, init_function=init_function)
|
|
556
|
+
check_init_function_with_cache(
|
|
557
|
+
init_function=init_function, cache_directory=cache_directory
|
|
558
|
+
)
|
|
555
559
|
check_wait_on_shutdown(wait_on_shutdown=wait)
|
|
556
560
|
if block_allocation:
|
|
557
561
|
executor_kwargs["init_function"] = init_function
|
|
@@ -31,18 +31,22 @@ def dump(file_name: Optional[str], data_dict: dict) -> None:
|
|
|
31
31
|
"""
|
|
32
32
|
if file_name is not None:
|
|
33
33
|
file_name_abs = os.path.abspath(file_name)
|
|
34
|
+
# Serialize all values before opening the file, so a serialization error does
|
|
35
|
+
# not leave an incomplete HDF5 file behind.
|
|
36
|
+
binary_dict = {
|
|
37
|
+
group_dict[data_key]: cloudpickle.dumps(data_value)
|
|
38
|
+
for data_key, data_value in data_dict.items()
|
|
39
|
+
if data_key in group_dict
|
|
40
|
+
}
|
|
34
41
|
os.makedirs(os.path.dirname(file_name_abs), exist_ok=True)
|
|
35
42
|
with h5py.File(file_name_abs, "a") as fname:
|
|
36
|
-
for
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
),
|
|
44
|
-
compression="gzip",
|
|
45
|
-
)
|
|
43
|
+
for group_name, binary in binary_dict.items():
|
|
44
|
+
with contextlib.suppress(ValueError):
|
|
45
|
+
fname.create_dataset(
|
|
46
|
+
name="/" + group_name,
|
|
47
|
+
data=np.frombuffer(binary, dtype=np.uint8),
|
|
48
|
+
compression="gzip",
|
|
49
|
+
)
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
def load(file_name: str) -> dict:
|
|
@@ -138,6 +138,23 @@ def check_init_function(
|
|
|
138
138
|
raise ValueError("")
|
|
139
139
|
|
|
140
140
|
|
|
141
|
+
def check_init_function_with_cache(
|
|
142
|
+
init_function: Optional[Callable], cache_directory: Optional[str]
|
|
143
|
+
) -> None:
|
|
144
|
+
"""
|
|
145
|
+
Check if init_function and cache_directory are both set, and raise a warning if they are. The cache key is
|
|
146
|
+
computed from the submitted function and its arguments only, so it does not reflect the state set by the
|
|
147
|
+
init_function, which can lead to stale results being returned from the cache.
|
|
148
|
+
"""
|
|
149
|
+
if init_function is not None and cache_directory is not None:
|
|
150
|
+
warn(
|
|
151
|
+
"The init_function and the cache_directory are incompatible: the cache key does not include the "
|
|
152
|
+
"state set by the init_function, so functions which depend on this state may return stale results "
|
|
153
|
+
"from the cache. See https://github.com/pyiron/executorlib/issues/1084",
|
|
154
|
+
stacklevel=2,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
141
158
|
def check_max_workers_and_cores(
|
|
142
159
|
max_workers: Optional[int], max_cores: Optional[int]
|
|
143
160
|
) -> None:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import queue
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def put_front(que: queue.Queue, item: Any):
|
|
6
|
+
"""
|
|
7
|
+
Insert an item at the front of the queue, ahead of any items already waiting, and wake a consumer blocked in
|
|
8
|
+
get(). Equivalent to queue.Queue.put() except the item is placed at the front rather than the back; mutating
|
|
9
|
+
que.queue directly instead would silently skip the notification, leaving a waiting consumer asleep forever.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
que (queue.Queue): Queue with task objects which should be executed
|
|
13
|
+
item (Any): item to place at the front of the queue
|
|
14
|
+
"""
|
|
15
|
+
with que.not_full:
|
|
16
|
+
que.queue.insert(0, item)
|
|
17
|
+
que.unfinished_tasks += 1
|
|
18
|
+
que.not_empty.notify()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def cancel_items_in_queue(que: queue.Queue):
|
|
22
|
+
"""
|
|
23
|
+
Cancel items which are still waiting in the queue. If the executor is busy tasks remain in the queue, so the future
|
|
24
|
+
objects have to be cancelled when the executor shuts down.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
que (queue.Queue): Queue with task objects which should be executed
|
|
28
|
+
"""
|
|
29
|
+
while True:
|
|
30
|
+
try:
|
|
31
|
+
item = que.get_nowait()
|
|
32
|
+
if isinstance(item, dict) and "future" in item:
|
|
33
|
+
item["future"].cancel()
|
|
34
|
+
que.task_done()
|
|
35
|
+
except queue.Empty:
|
|
36
|
+
break
|
|
@@ -21,9 +21,10 @@ class FutureSelector(Future):
|
|
|
21
21
|
future (Future): The underlying future whose result is a collection.
|
|
22
22
|
selector (int | str): Index or key used to extract an element from the result.
|
|
23
23
|
"""
|
|
24
|
+
# Future.__init__() is not called, as its attributes would be forwarded to the
|
|
25
|
+
# wrapped future by __setattr__() and reset the state of an already completed future.
|
|
24
26
|
self._future = future
|
|
25
27
|
self._selector = selector
|
|
26
|
-
super().__init__()
|
|
27
28
|
|
|
28
29
|
def __getattr__(self, attr: str) -> Any:
|
|
29
30
|
"""Delegate attribute access to the wrapped future."""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import os
|
|
3
3
|
import queue
|
|
4
|
-
from concurrent.futures import Future
|
|
4
|
+
from concurrent.futures import CancelledError, Future
|
|
5
5
|
from time import sleep
|
|
6
6
|
from typing import Any, Callable, Optional
|
|
7
7
|
|
|
@@ -118,83 +118,93 @@ def execute_tasks_h5(
|
|
|
118
118
|
future_queue.join()
|
|
119
119
|
break
|
|
120
120
|
elif task_dict is not None:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
_get_task_input(
|
|
128
|
-
task_resource_dict=task_dict["resource_dict"].copy(),
|
|
129
|
-
executor_kwargs=executor_kwargs,
|
|
121
|
+
try:
|
|
122
|
+
task_args, task_kwargs, future_wait_key_lst = _convert_args_and_kwargs(
|
|
123
|
+
task_dict=task_dict,
|
|
124
|
+
memory_dict=memory_dict,
|
|
125
|
+
file_name_dict=file_name_dict,
|
|
126
|
+
duplicate_dict=duplicate_dict,
|
|
130
127
|
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
128
|
+
task_resource_dict, cache_key, cache_directory, error_log_file = (
|
|
129
|
+
_get_task_input(
|
|
130
|
+
task_resource_dict=task_dict["resource_dict"].copy(),
|
|
131
|
+
executor_kwargs=executor_kwargs,
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
task_key, data_dict, serialize_exception = serialize_funct(
|
|
135
|
+
fn=task_dict["fn"],
|
|
136
|
+
fn_args=task_args,
|
|
137
|
+
fn_kwargs=task_kwargs,
|
|
138
|
+
resource_dict=task_resource_dict,
|
|
139
|
+
cache_key=cache_key,
|
|
140
|
+
)
|
|
141
|
+
data_dict["error_log_file"] = error_log_file
|
|
142
|
+
if serialize_exception is not None:
|
|
143
|
+
task_dict["future"].set_exception(serialize_exception)
|
|
144
|
+
elif task_key not in memory_dict:
|
|
145
|
+
if os.path.join(
|
|
146
|
+
cache_directory, task_key + "_o.h5"
|
|
147
|
+
) not in get_cache_files(cache_directory=cache_directory):
|
|
148
|
+
file_name = os.path.join(cache_directory, task_key + "_i.h5")
|
|
149
|
+
if not disable_dependencies:
|
|
150
|
+
task_dependent_lst = [
|
|
151
|
+
process_dict[k]
|
|
152
|
+
for k in future_wait_key_lst
|
|
153
|
+
if k in process_dict
|
|
154
|
+
]
|
|
155
|
+
else:
|
|
156
|
+
if len(future_wait_key_lst) > 0:
|
|
157
|
+
task_dict["future"].set_exception(
|
|
158
|
+
ValueError(
|
|
159
|
+
"Future objects are not supported as input if disable_dependencies=True."
|
|
160
|
+
)
|
|
158
161
|
)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
continue
|
|
163
|
+
task_dependent_lst = []
|
|
164
|
+
try:
|
|
165
|
+
process_dict[task_key] = execute_function(
|
|
166
|
+
command=get_cache_execute_command(
|
|
167
|
+
file_name=file_name,
|
|
168
|
+
cores=task_resource_dict["cores"],
|
|
169
|
+
backend=backend,
|
|
170
|
+
exclusive=task_resource_dict.get(
|
|
171
|
+
"exclusive", False
|
|
172
|
+
),
|
|
173
|
+
openmpi_oversubscribe=task_resource_dict.get(
|
|
174
|
+
"openmpi_oversubscribe", False
|
|
175
|
+
),
|
|
176
|
+
pmi_mode=pmi_mode,
|
|
177
|
+
),
|
|
164
178
|
file_name=file_name,
|
|
165
|
-
|
|
179
|
+
data_dict=data_dict,
|
|
180
|
+
task_dependent_lst=task_dependent_lst,
|
|
181
|
+
resource_dict=task_resource_dict,
|
|
182
|
+
config_directory=pysqa_config_directory,
|
|
166
183
|
backend=backend,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
memory_dict[task_key] = task_dict["future"]
|
|
192
|
-
cache_dir_dict[task_key] = cache_directory
|
|
193
|
-
elif memory_dict[task_key] != task_dict["future"]:
|
|
194
|
-
if task_key not in duplicate_dict:
|
|
195
|
-
duplicate_dict[task_key] = []
|
|
196
|
-
duplicate_dict[task_key].append(task_dict["future"])
|
|
197
|
-
future_queue.task_done()
|
|
184
|
+
cache_directory=cache_directory,
|
|
185
|
+
)
|
|
186
|
+
except Exception as e:
|
|
187
|
+
task_dict["future"].set_exception(e)
|
|
188
|
+
memory_dict[task_key] = task_dict["future"]
|
|
189
|
+
continue
|
|
190
|
+
file_name = os.path.join(cache_directory, task_key + "_o.h5")
|
|
191
|
+
file_name_dict[task_key] = file_name
|
|
192
|
+
queue_id = get_queue_id(file_name=file_name)
|
|
193
|
+
if queue_id is not None:
|
|
194
|
+
process_dict[task_key] = queue_id
|
|
195
|
+
memory_dict[task_key] = task_dict["future"]
|
|
196
|
+
cache_dir_dict[task_key] = cache_directory
|
|
197
|
+
elif memory_dict[task_key] != task_dict["future"]:
|
|
198
|
+
if task_key not in duplicate_dict:
|
|
199
|
+
duplicate_dict[task_key] = []
|
|
200
|
+
duplicate_dict[task_key].append(task_dict["future"])
|
|
201
|
+
except Exception as e:
|
|
202
|
+
# Fail only the current task, never the scheduler thread, e.g. when
|
|
203
|
+
# one of the input futures of this task failed before submission.
|
|
204
|
+
if not task_dict["future"].done():
|
|
205
|
+
task_dict["future"].set_exception(e)
|
|
206
|
+
finally:
|
|
207
|
+
future_queue.task_done()
|
|
198
208
|
else:
|
|
199
209
|
memory_dict = _refresh_memory_dict(
|
|
200
210
|
memory_dict=memory_dict,
|
|
@@ -308,7 +318,10 @@ def _update_future(
|
|
|
308
318
|
|
|
309
319
|
|
|
310
320
|
def _convert_args_and_kwargs(
|
|
311
|
-
task_dict: dict,
|
|
321
|
+
task_dict: dict,
|
|
322
|
+
memory_dict: dict,
|
|
323
|
+
file_name_dict: dict,
|
|
324
|
+
duplicate_dict: Optional[dict] = None,
|
|
312
325
|
) -> tuple[list, dict, list]:
|
|
313
326
|
"""
|
|
314
327
|
Convert the arguments and keyword arguments in a task dictionary to the appropriate types.
|
|
@@ -317,59 +330,84 @@ def _convert_args_and_kwargs(
|
|
|
317
330
|
task_dict (dict): The task dictionary containing the arguments and keyword arguments.
|
|
318
331
|
memory_dict (dict): The dictionary mapping future objects to their associated task keys.
|
|
319
332
|
file_name_dict (dict): The dictionary mapping task keys to their corresponding file names.
|
|
333
|
+
duplicate_dict (dict, optional): The dictionary mapping task keys to their associated duplicate future objects.
|
|
320
334
|
|
|
321
335
|
Returns:
|
|
322
336
|
Tuple[list, dict, list]: A tuple containing the converted arguments, converted keyword arguments, and a list of future wait keys.
|
|
323
337
|
|
|
338
|
+
Raises:
|
|
339
|
+
Exception: The exception of an input future which already failed, so the dependent task fails as well.
|
|
340
|
+
|
|
324
341
|
"""
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
future_wait_key_lst = []
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
task_args.append(arg)
|
|
349
|
-
for key, arg in task_dict["kwargs"].items():
|
|
350
|
-
selector = None
|
|
351
|
-
if isinstance(arg, Future):
|
|
352
|
-
if hasattr(arg, "_future") and hasattr(arg, "_selector"):
|
|
353
|
-
selector = arg._selector
|
|
354
|
-
future = arg._future
|
|
355
|
-
else:
|
|
356
|
-
future = arg
|
|
357
|
-
match_found = False
|
|
358
|
-
for k, v in memory_dict.items():
|
|
359
|
-
if future == v:
|
|
360
|
-
task_kwargs[key] = FutureItem(
|
|
361
|
-
file_name=file_name_dict[k], selector=selector
|
|
362
|
-
)
|
|
363
|
-
future_wait_key_lst.append(k)
|
|
364
|
-
match_found = True
|
|
365
|
-
break
|
|
366
|
-
if not match_found:
|
|
367
|
-
task_kwargs[key] = future.result()
|
|
368
|
-
else:
|
|
369
|
-
task_kwargs[key] = arg
|
|
342
|
+
if duplicate_dict is None:
|
|
343
|
+
duplicate_dict = {}
|
|
344
|
+
future_wait_key_lst: list = []
|
|
345
|
+
task_args = [
|
|
346
|
+
_convert_future(
|
|
347
|
+
arg=arg,
|
|
348
|
+
memory_dict=memory_dict,
|
|
349
|
+
file_name_dict=file_name_dict,
|
|
350
|
+
duplicate_dict=duplicate_dict,
|
|
351
|
+
future_wait_key_lst=future_wait_key_lst,
|
|
352
|
+
)
|
|
353
|
+
for arg in task_dict["args"]
|
|
354
|
+
]
|
|
355
|
+
task_kwargs = {
|
|
356
|
+
key: _convert_future(
|
|
357
|
+
arg=arg,
|
|
358
|
+
memory_dict=memory_dict,
|
|
359
|
+
file_name_dict=file_name_dict,
|
|
360
|
+
duplicate_dict=duplicate_dict,
|
|
361
|
+
future_wait_key_lst=future_wait_key_lst,
|
|
362
|
+
)
|
|
363
|
+
for key, arg in task_dict["kwargs"].items()
|
|
364
|
+
}
|
|
370
365
|
return task_args, task_kwargs, future_wait_key_lst
|
|
371
366
|
|
|
372
367
|
|
|
368
|
+
def _convert_future(
|
|
369
|
+
arg: Any,
|
|
370
|
+
memory_dict: dict,
|
|
371
|
+
file_name_dict: dict,
|
|
372
|
+
duplicate_dict: dict,
|
|
373
|
+
future_wait_key_lst: list,
|
|
374
|
+
) -> Any:
|
|
375
|
+
"""
|
|
376
|
+
Convert a single argument, replacing future objects by FutureItem objects or their results.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
arg (Any): The argument to convert.
|
|
380
|
+
memory_dict (dict): The dictionary mapping task keys to their associated future objects.
|
|
381
|
+
file_name_dict (dict): The dictionary mapping task keys to their corresponding file names.
|
|
382
|
+
duplicate_dict (dict): The dictionary mapping task keys to their associated duplicate future objects.
|
|
383
|
+
future_wait_key_lst (list): List of task keys the current task depends on, extended in place.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
Any: The converted argument.
|
|
387
|
+
|
|
388
|
+
Raises:
|
|
389
|
+
Exception: The exception of the input future, if it already failed.
|
|
390
|
+
|
|
391
|
+
"""
|
|
392
|
+
if not isinstance(arg, Future):
|
|
393
|
+
return arg
|
|
394
|
+
if hasattr(arg, "_future") and hasattr(arg, "_selector"):
|
|
395
|
+
selector = arg._selector
|
|
396
|
+
future = arg._future
|
|
397
|
+
else:
|
|
398
|
+
selector = None
|
|
399
|
+
future = arg
|
|
400
|
+
if future.done():
|
|
401
|
+
exception = CancelledError() if future.cancelled() else future.exception()
|
|
402
|
+
if exception is not None:
|
|
403
|
+
raise exception
|
|
404
|
+
for k, v in memory_dict.items():
|
|
405
|
+
if k in file_name_dict and (future == v or future in duplicate_dict.get(k, [])):
|
|
406
|
+
future_wait_key_lst.append(k)
|
|
407
|
+
return FutureItem(file_name=file_name_dict[k], selector=selector)
|
|
408
|
+
return arg.result()
|
|
409
|
+
|
|
410
|
+
|
|
373
411
|
def _refresh_memory_dict(
|
|
374
412
|
memory_dict: dict,
|
|
375
413
|
cache_dir_dict: dict,
|
|
@@ -15,7 +15,7 @@ from executorlib.standalone.interactive.communication import (
|
|
|
15
15
|
interface_bootup,
|
|
16
16
|
)
|
|
17
17
|
from executorlib.standalone.interactive.spawner import BaseSpawner, MpiExecSpawner
|
|
18
|
-
from executorlib.standalone.queue import cancel_items_in_queue
|
|
18
|
+
from executorlib.standalone.queue import cancel_items_in_queue, put_front
|
|
19
19
|
from executorlib.task_scheduler.base import TaskSchedulerBase, validate_resource_dict
|
|
20
20
|
from executorlib.task_scheduler.interactive.shared import (
|
|
21
21
|
execute_task_dict,
|
|
@@ -122,7 +122,7 @@ class BlockAllocationTaskScheduler(TaskSchedulerBase):
|
|
|
122
122
|
):
|
|
123
123
|
if self._max_workers > max_workers:
|
|
124
124
|
for _ in range(self._max_workers - max_workers):
|
|
125
|
-
self._future_queue
|
|
125
|
+
put_front(self._future_queue, {"shutdown": True, "wait": True})
|
|
126
126
|
while len(self._process) > max_workers:
|
|
127
127
|
self._process = [
|
|
128
128
|
process for process in self._process if process.is_alive()
|
|
@@ -6,7 +6,10 @@ from typing import Callable, Optional
|
|
|
6
6
|
from pysqa import QueueAdapter
|
|
7
7
|
|
|
8
8
|
from executorlib.standalone.command_pysqa import pysqa_execute_command, pysqa_terminate
|
|
9
|
-
from executorlib.standalone.inputcheck import
|
|
9
|
+
from executorlib.standalone.inputcheck import (
|
|
10
|
+
check_init_function_with_cache,
|
|
11
|
+
validate_number_of_cores,
|
|
12
|
+
)
|
|
10
13
|
from executorlib.standalone.interactive.spawner import (
|
|
11
14
|
BaseSpawner,
|
|
12
15
|
set_current_directory_in_environment,
|
|
@@ -233,6 +236,9 @@ def create_pysqa_block_allocation_scheduler(
|
|
|
233
236
|
backend: Optional[str] = None,
|
|
234
237
|
validator: Callable = validate_resource_dict,
|
|
235
238
|
):
|
|
239
|
+
check_init_function_with_cache(
|
|
240
|
+
init_function=init_function, cache_directory=cache_directory
|
|
241
|
+
)
|
|
236
242
|
if executor_kwargs is None:
|
|
237
243
|
executor_kwargs = {}
|
|
238
244
|
cores_per_worker = executor_kwargs.get("cores", 1)
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import queue
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def cancel_items_in_queue(que: queue.Queue):
|
|
5
|
-
"""
|
|
6
|
-
Cancel items which are still waiting in the queue. If the executor is busy tasks remain in the queue, so the future
|
|
7
|
-
objects have to be cancelled when the executor shuts down.
|
|
8
|
-
|
|
9
|
-
Args:
|
|
10
|
-
que (queue.Queue): Queue with task objects which should be executed
|
|
11
|
-
"""
|
|
12
|
-
while True:
|
|
13
|
-
try:
|
|
14
|
-
item = que.get_nowait()
|
|
15
|
-
if isinstance(item, dict) and "future" in item:
|
|
16
|
-
item["future"].cancel()
|
|
17
|
-
que.task_done()
|
|
18
|
-
except queue.Empty:
|
|
19
|
-
break
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/interactive/__init__.py
RENAMED
|
File without changes
|
{executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/interactive/arguments.py
RENAMED
|
File without changes
|
|
File without changes
|
{executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/standalone/interactive/communication.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/file/spawner_pysqa.py
RENAMED
|
File without changes
|
{executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/file/spawner_subprocess.py
RENAMED
|
File without changes
|
{executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/file/task_scheduler.py
RENAMED
|
File without changes
|
{executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/__init__.py
RENAMED
|
File without changes
|
{executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/dependency.py
RENAMED
|
File without changes
|
|
File without changes
|
{executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/onetoone.py
RENAMED
|
File without changes
|
{executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/shared.py
RENAMED
|
File without changes
|
{executorlib-1.10.3 → executorlib-1.11.0}/src/executorlib/task_scheduler/interactive/spawner_flux.py
RENAMED
|
File without changes
|
|
File without changes
|