executorlib 1.10.1__py3-none-any.whl → 1.10.2__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.
- executorlib/_version.py +2 -2
- executorlib/executor/single.py +2 -2
- executorlib/standalone/command_pysqa.py +148 -0
- executorlib/standalone/interactive/communication.py +7 -9
- executorlib/task_scheduler/file/shared.py +74 -4
- executorlib/task_scheduler/file/spawner_pysqa.py +3 -3
- executorlib/task_scheduler/file/spawner_subprocess.py +2 -2
- executorlib/task_scheduler/file/task_scheduler.py +13 -8
- executorlib/task_scheduler/interactive/blockallocation.py +5 -6
- executorlib/task_scheduler/interactive/shared.py +16 -17
- executorlib/task_scheduler/interactive/spawner_pysqa.py +2 -2
- {executorlib-1.10.1.dist-info → executorlib-1.10.2.dist-info}/METADATA +3 -3
- {executorlib-1.10.1.dist-info → executorlib-1.10.2.dist-info}/RECORD +15 -15
- {executorlib-1.10.1.dist-info → executorlib-1.10.2.dist-info}/WHEEL +1 -1
- executorlib/standalone/scheduler.py +0 -65
- {executorlib-1.10.1.dist-info → executorlib-1.10.2.dist-info}/licenses/LICENSE +0 -0
executorlib/_version.py
CHANGED
|
@@ -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.10.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 10,
|
|
21
|
+
__version__ = version = '1.10.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 10, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
executorlib/executor/single.py
CHANGED
|
@@ -393,7 +393,7 @@ class TestClusterExecutor(BaseExecutor):
|
|
|
393
393
|
)
|
|
394
394
|
if not plot_dependency_graph:
|
|
395
395
|
from executorlib.task_scheduler.file.spawner_subprocess import (
|
|
396
|
-
|
|
396
|
+
subprocess_execute,
|
|
397
397
|
)
|
|
398
398
|
from executorlib.task_scheduler.file.task_scheduler import (
|
|
399
399
|
create_file_executor,
|
|
@@ -415,7 +415,7 @@ class TestClusterExecutor(BaseExecutor):
|
|
|
415
415
|
block_allocation=block_allocation,
|
|
416
416
|
init_function=init_function,
|
|
417
417
|
disable_dependencies=disable_dependencies,
|
|
418
|
-
execute_function=
|
|
418
|
+
execute_function=subprocess_execute,
|
|
419
419
|
wait=wait,
|
|
420
420
|
refresh_rate=refresh_rate,
|
|
421
421
|
validator=validate_resource_dict_with_optional_keys,
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
from time import monotonic
|
|
5
|
+
from typing import Optional, Union
|
|
6
|
+
|
|
7
|
+
from pysqa import QueueAdapter
|
|
8
|
+
|
|
9
|
+
# Minimum time between two queries of the queuing system for the status of a task whose output
|
|
10
|
+
# file has not appeared yet. Detecting a dead job (timeout, OOM, node failure, scancel, ...) relies
|
|
11
|
+
# on this status query, but it must not be issued on every poll of the (much faster) refresh_rate
|
|
12
|
+
# loop, as that would flood the queuing system commands (e.g. squeue/sacct) with requests.
|
|
13
|
+
_JOB_STATUS_CHECK_INTERVAL = 30.0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def pysqa_job_output_validation(
|
|
17
|
+
task_key: str,
|
|
18
|
+
file_name: str,
|
|
19
|
+
queue_id: int,
|
|
20
|
+
status_check_dict: Optional[dict],
|
|
21
|
+
pysqa_config_directory: Optional[str] = None,
|
|
22
|
+
backend: Optional[str] = None,
|
|
23
|
+
job_status_check_interval: float = _JOB_STATUS_CHECK_INTERVAL,
|
|
24
|
+
) -> bool:
|
|
25
|
+
"""
|
|
26
|
+
Check whether the queuing system job backing a task has died without ever writing its output
|
|
27
|
+
file. Only applies to queuing system backends (pysqa) and is throttled to at most once every
|
|
28
|
+
``job_status_check_interval`` seconds per task, to avoid flooding the queuing system with
|
|
29
|
+
status queries on every poll of the (much faster) refresh_rate loop.
|
|
30
|
+
|
|
31
|
+
A dead job is recognized in two ways, since queuing systems differ in whether they drop
|
|
32
|
+
terminated jobs from their listing: slurm's squeue removes a job as soon as it is gone
|
|
33
|
+
(status None), while flux's "flux jobs -a" keeps listing inactive jobs and instead reports
|
|
34
|
+
pysqa's terminal-failure status "error" (the same status pysqa_terminate already treats as
|
|
35
|
+
not alive).
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
task_key (str): The key of the task.
|
|
39
|
+
file_name (str): Path of the expected output HDF5 file.
|
|
40
|
+
queue_id (int, optional): The queuing system ID of the task.
|
|
41
|
+
pysqa_config_directory (str, optional): path to the pysqa config directory.
|
|
42
|
+
backend (str, optional): name of the backend used to spawn tasks ["slurm", "flux"].
|
|
43
|
+
status_check_dict (dict): Dictionary tracking when each task's job status was last queried.
|
|
44
|
+
job_status_check_interval (float): Minimum time interval between job status checks for the same task.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
bool: True if the job is no longer known to the queuing system, or is reported as having
|
|
48
|
+
errored out, and still has no output.
|
|
49
|
+
"""
|
|
50
|
+
now = monotonic()
|
|
51
|
+
last_checked = (
|
|
52
|
+
status_check_dict.get(task_key, 0.0) if status_check_dict is not None else 0.0
|
|
53
|
+
)
|
|
54
|
+
if now - last_checked < job_status_check_interval:
|
|
55
|
+
return False
|
|
56
|
+
if status_check_dict is not None:
|
|
57
|
+
status_check_dict[task_key] = now
|
|
58
|
+
status = pysqa_get_status_of_job(
|
|
59
|
+
queue_id=queue_id,
|
|
60
|
+
config_directory=pysqa_config_directory,
|
|
61
|
+
backend=backend,
|
|
62
|
+
)
|
|
63
|
+
return (status is None or status == "error") and not os.path.exists(file_name)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def pysqa_terminate(
|
|
67
|
+
queue_id: int,
|
|
68
|
+
config_directory: Optional[str] = None,
|
|
69
|
+
backend: Optional[str] = None,
|
|
70
|
+
):
|
|
71
|
+
"""
|
|
72
|
+
Delete job from queuing system
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
queue_id (int): Queuing system ID of the job to delete.
|
|
76
|
+
config_directory (str, optional): path to the config directory.
|
|
77
|
+
backend (str, optional): name of the backend used to spawn tasks ["slurm", "flux"].
|
|
78
|
+
"""
|
|
79
|
+
qa = QueueAdapter(
|
|
80
|
+
directory=config_directory,
|
|
81
|
+
queue_type=backend,
|
|
82
|
+
execute_command=pysqa_execute_command,
|
|
83
|
+
)
|
|
84
|
+
status = qa.get_status_of_job(process_id=queue_id)
|
|
85
|
+
if status is not None and status not in ["finished", "error"]:
|
|
86
|
+
with contextlib.suppress(subprocess.CalledProcessError):
|
|
87
|
+
qa.delete_job(process_id=queue_id)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def pysqa_get_status_of_job(
|
|
91
|
+
queue_id: int,
|
|
92
|
+
config_directory: Optional[str] = None,
|
|
93
|
+
backend: Optional[str] = None,
|
|
94
|
+
) -> Optional[str]:
|
|
95
|
+
"""
|
|
96
|
+
Query the status of a job from the queuing system.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
queue_id (int): Queuing system ID of the job to check.
|
|
100
|
+
config_directory (str, optional): path to the config directory.
|
|
101
|
+
backend (str, optional): name of the backend used to spawn tasks ["slurm", "flux"].
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
str: status of the job as reported by the queuing system, None if the queuing system no
|
|
105
|
+
longer knows about the job (e.g. it timed out, was killed or already finished).
|
|
106
|
+
"""
|
|
107
|
+
qa = QueueAdapter(
|
|
108
|
+
directory=config_directory,
|
|
109
|
+
queue_type=backend,
|
|
110
|
+
execute_command=pysqa_execute_command,
|
|
111
|
+
)
|
|
112
|
+
return qa.get_status_of_job(process_id=queue_id)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def pysqa_execute_command(
|
|
116
|
+
commands: str,
|
|
117
|
+
working_directory: Optional[str] = None,
|
|
118
|
+
split_output: bool = True,
|
|
119
|
+
shell: bool = False,
|
|
120
|
+
error_filename: str = "pysqa.err",
|
|
121
|
+
) -> Union[str, list[str]]:
|
|
122
|
+
"""
|
|
123
|
+
A wrapper around the subprocess.check_output function. Modified from pysqa to raise an exception if the subprocess
|
|
124
|
+
fails to submit the job to the queue.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
commands (str): The command(s) to be executed on the command line
|
|
128
|
+
working_directory (str, optional): The directory where the command is executed. Defaults to None.
|
|
129
|
+
split_output (bool, optional): Boolean flag to split newlines in the output. Defaults to True.
|
|
130
|
+
shell (bool, optional): Additional switch to convert commands to a single string. Defaults to False.
|
|
131
|
+
error_filename (str, optional): In case the execution fails, the output is written to this file. Defaults to "pysqa.err".
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Union[str, List[str]]: Output of the shell command either as a string or as a list of strings
|
|
135
|
+
"""
|
|
136
|
+
if shell and isinstance(commands, list):
|
|
137
|
+
commands = " ".join(commands)
|
|
138
|
+
out = subprocess.check_output(
|
|
139
|
+
commands,
|
|
140
|
+
cwd=working_directory,
|
|
141
|
+
stderr=subprocess.STDOUT,
|
|
142
|
+
universal_newlines=True,
|
|
143
|
+
shell=not isinstance(commands, list),
|
|
144
|
+
)
|
|
145
|
+
if out is not None and split_output:
|
|
146
|
+
return out.split("\n")
|
|
147
|
+
else:
|
|
148
|
+
return out
|
|
@@ -78,19 +78,17 @@ class SocketInterface:
|
|
|
78
78
|
while len(response_lst) == 0:
|
|
79
79
|
response_lst = self._poller.poll(self._time_out_ms)
|
|
80
80
|
if not self._spawner.poll():
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
|
|
81
|
+
return {
|
|
82
|
+
"error": ExecutorlibSocketError(
|
|
83
|
+
"SocketInterface crashed during execution."
|
|
84
|
+
),
|
|
85
|
+
}
|
|
84
86
|
data = self._socket.recv(zmq.NOBLOCK)
|
|
85
87
|
if self._logger is not None:
|
|
86
88
|
self._logger.warning(
|
|
87
89
|
"Received dictionary of size: " + str(sys.getsizeof(data))
|
|
88
90
|
)
|
|
89
|
-
|
|
90
|
-
if "result" in output:
|
|
91
|
-
return output["result"]
|
|
92
|
-
else:
|
|
93
|
-
raise output["error"]
|
|
91
|
+
return cloudpickle.loads(data)
|
|
94
92
|
|
|
95
93
|
def send_and_receive_dict(self, input_dict: dict) -> dict:
|
|
96
94
|
"""
|
|
@@ -154,7 +152,7 @@ class SocketInterface:
|
|
|
154
152
|
if self._spawner.poll():
|
|
155
153
|
result = self.send_and_receive_dict(
|
|
156
154
|
input_dict={"shutdown": True, "wait": wait}
|
|
157
|
-
)
|
|
155
|
+
)["result"]
|
|
158
156
|
self._spawner.shutdown(wait=wait)
|
|
159
157
|
self._reset_socket()
|
|
160
158
|
return result
|
|
@@ -8,7 +8,7 @@ from typing import Any, Callable, Optional
|
|
|
8
8
|
from executorlib.standalone.command import get_cache_execute_command
|
|
9
9
|
from executorlib.standalone.hdf import get_cache_files, get_output, get_queue_id
|
|
10
10
|
from executorlib.standalone.serialize import serialize_funct
|
|
11
|
-
from executorlib.task_scheduler.file.spawner_subprocess import
|
|
11
|
+
from executorlib.task_scheduler.file.spawner_subprocess import subprocess_terminate
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class FutureItem:
|
|
@@ -59,6 +59,7 @@ def execute_tasks_h5(
|
|
|
59
59
|
execute_function: Callable,
|
|
60
60
|
executor_kwargs: dict,
|
|
61
61
|
terminate_function: Optional[Callable] = None,
|
|
62
|
+
validate_function: Optional[Callable] = None,
|
|
62
63
|
pysqa_config_directory: Optional[str] = None,
|
|
63
64
|
backend: Optional[str] = None,
|
|
64
65
|
disable_dependencies: bool = False,
|
|
@@ -76,6 +77,7 @@ def execute_tasks_h5(
|
|
|
76
77
|
- cwd (str/None): current working directory where the parallel python task is executed
|
|
77
78
|
execute_function (Callable): The function to execute the tasks.
|
|
78
79
|
terminate_function (Callable): The function to terminate the tasks.
|
|
80
|
+
validate_function (Callable): The function to validate the tasks.
|
|
79
81
|
pysqa_config_directory (str, optional): path to the pysqa config directory (only for pysqa based backend).
|
|
80
82
|
backend (str, optional): name of the backend used to spawn tasks.
|
|
81
83
|
disable_dependencies (boolean): Disable resolving future objects during the submission.
|
|
@@ -92,6 +94,7 @@ def execute_tasks_h5(
|
|
|
92
94
|
cache_dir_dict: dict = {}
|
|
93
95
|
file_name_dict: dict = {}
|
|
94
96
|
duplicate_dict: dict = {}
|
|
97
|
+
status_check_dict: dict = {}
|
|
95
98
|
while True:
|
|
96
99
|
task_dict = None
|
|
97
100
|
with contextlib.suppress(queue.Empty):
|
|
@@ -104,7 +107,9 @@ def execute_tasks_h5(
|
|
|
104
107
|
process_dict=process_dict,
|
|
105
108
|
duplicate_dict=duplicate_dict,
|
|
106
109
|
cache_dir_dict=cache_dir_dict,
|
|
110
|
+
status_check_dict=status_check_dict,
|
|
107
111
|
terminate_function=terminate_function,
|
|
112
|
+
validate_function=validate_function,
|
|
108
113
|
pysqa_config_directory=pysqa_config_directory,
|
|
109
114
|
backend=backend,
|
|
110
115
|
refresh_rate=refresh_rate,
|
|
@@ -188,7 +193,9 @@ def execute_tasks_h5(
|
|
|
188
193
|
cache_dir_dict=cache_dir_dict,
|
|
189
194
|
process_dict=process_dict,
|
|
190
195
|
duplicate_dict=duplicate_dict,
|
|
196
|
+
status_check_dict=status_check_dict,
|
|
191
197
|
terminate_function=terminate_function,
|
|
198
|
+
validate_function=validate_function,
|
|
192
199
|
pysqa_config_directory=pysqa_config_directory,
|
|
193
200
|
backend=backend,
|
|
194
201
|
refresh_rate=refresh_rate,
|
|
@@ -199,15 +206,30 @@ def _check_task_output(
|
|
|
199
206
|
task_key: str,
|
|
200
207
|
future_obj: Future,
|
|
201
208
|
cache_directory: str,
|
|
209
|
+
queue_id: Optional[int] = None,
|
|
210
|
+
pysqa_config_directory: Optional[str] = None,
|
|
211
|
+
backend: Optional[str] = None,
|
|
212
|
+
validate_function: Optional[Callable] = None,
|
|
213
|
+
status_check_dict: Optional[dict] = None,
|
|
202
214
|
duplicate_dict: Optional[dict] = None,
|
|
203
215
|
) -> Future:
|
|
204
216
|
"""
|
|
205
217
|
Check the output of a task and set the result of the future object if available.
|
|
206
218
|
|
|
219
|
+
If the output file is missing and the task is running on a queuing system backend, this also
|
|
220
|
+
detects jobs which died without producing output (e.g. walltime TIMEOUT, OOM, NODE_FAIL or an
|
|
221
|
+
external scancel) by periodically querying the job status via pysqa and fails the future
|
|
222
|
+
instead of leaving it pending forever.
|
|
223
|
+
|
|
207
224
|
Args:
|
|
208
225
|
task_key (str): The key of the task.
|
|
209
226
|
future_obj (Future): The future object associated with the task.
|
|
210
227
|
cache_directory (str): The directory where the HDF5 files are stored.
|
|
228
|
+
queue_id (int, optional): The queuing system ID of the task, if submitted via pysqa.
|
|
229
|
+
pysqa_config_directory (str, optional): path to the pysqa config directory.
|
|
230
|
+
backend (str, optional): name of the backend used to spawn tasks ["slurm", "flux"].
|
|
231
|
+
status_check_dict (dict): Dictionary tracking when each task's job status was last queried,
|
|
232
|
+
used to throttle calls to the queuing system.
|
|
211
233
|
duplicate_dict (dict): The dictionary mapping task keys to their associated duplicate future objects.
|
|
212
234
|
Returns:
|
|
213
235
|
Future: The updated future object.
|
|
@@ -215,8 +237,32 @@ def _check_task_output(
|
|
|
215
237
|
"""
|
|
216
238
|
file_name = os.path.join(cache_directory, task_key + "_o.h5")
|
|
217
239
|
if not os.path.exists(file_name):
|
|
218
|
-
|
|
219
|
-
|
|
240
|
+
if (
|
|
241
|
+
backend is None
|
|
242
|
+
or queue_id is None
|
|
243
|
+
or validate_function is None
|
|
244
|
+
or not validate_function(
|
|
245
|
+
task_key=task_key,
|
|
246
|
+
file_name=file_name,
|
|
247
|
+
queue_id=queue_id,
|
|
248
|
+
pysqa_config_directory=pysqa_config_directory,
|
|
249
|
+
backend=backend,
|
|
250
|
+
status_check_dict=status_check_dict,
|
|
251
|
+
)
|
|
252
|
+
):
|
|
253
|
+
return future_obj
|
|
254
|
+
exec_flag, no_error_flag, result = (
|
|
255
|
+
True,
|
|
256
|
+
False,
|
|
257
|
+
RuntimeError(
|
|
258
|
+
f"executorlib: queue job {queue_id} for task {task_key} terminated without "
|
|
259
|
+
"producing output (timeout, out-of-memory, node failure or cancellation)."
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
else:
|
|
263
|
+
exec_flag, no_error_flag, result = get_output(file_name=file_name)
|
|
264
|
+
if status_check_dict is not None:
|
|
265
|
+
status_check_dict.pop(task_key, None)
|
|
220
266
|
_update_future(
|
|
221
267
|
future_obj=future_obj,
|
|
222
268
|
exec_flag=exec_flag,
|
|
@@ -321,7 +367,9 @@ def _refresh_memory_dict(
|
|
|
321
367
|
cache_dir_dict: dict,
|
|
322
368
|
process_dict: dict,
|
|
323
369
|
duplicate_dict: Optional[dict] = None,
|
|
370
|
+
status_check_dict: Optional[dict] = None,
|
|
324
371
|
terminate_function: Optional[Callable] = None,
|
|
372
|
+
validate_function: Optional[Callable] = None,
|
|
325
373
|
pysqa_config_directory: Optional[str] = None,
|
|
326
374
|
backend: Optional[str] = None,
|
|
327
375
|
refresh_rate: float = 0.01,
|
|
@@ -334,7 +382,10 @@ def _refresh_memory_dict(
|
|
|
334
382
|
cache_dir_dict (dict): dictionary with task keys and cache directories
|
|
335
383
|
process_dict (dict): dictionary with task keys and process reference.
|
|
336
384
|
duplicate_dict (dict): dictionary with task keys and duplicate future objects.
|
|
385
|
+
status_check_dict (dict): dictionary with task keys and the last time their queuing system
|
|
386
|
+
job status was queried, used to throttle detection of jobs that died without output.
|
|
337
387
|
terminate_function (callable): The function to terminate the tasks.
|
|
388
|
+
validate_function (callable): The function to validate the tasks.
|
|
338
389
|
pysqa_config_directory (str): path to the pysqa config directory (only for pysqa based backend).
|
|
339
390
|
backend (str): name of the backend used to spawn tasks.
|
|
340
391
|
refresh_rate (float): The rate at which to refresh the result. Defaults to 0.01.
|
|
@@ -351,11 +402,19 @@ def _refresh_memory_dict(
|
|
|
351
402
|
pysqa_config_directory=pysqa_config_directory,
|
|
352
403
|
backend=backend,
|
|
353
404
|
)
|
|
405
|
+
if status_check_dict is not None:
|
|
406
|
+
for key in cancelled_lst:
|
|
407
|
+
status_check_dict.pop(key, None)
|
|
354
408
|
memory_updated_dict = {
|
|
355
409
|
key: _check_task_output(
|
|
356
410
|
task_key=key,
|
|
357
411
|
future_obj=value,
|
|
358
412
|
cache_directory=cache_dir_dict[key],
|
|
413
|
+
queue_id=process_dict.get(key),
|
|
414
|
+
pysqa_config_directory=pysqa_config_directory,
|
|
415
|
+
backend=backend,
|
|
416
|
+
validate_function=validate_function,
|
|
417
|
+
status_check_dict=status_check_dict,
|
|
359
418
|
duplicate_dict=duplicate_dict,
|
|
360
419
|
)
|
|
361
420
|
for key, value in memory_dict.items()
|
|
@@ -381,7 +440,7 @@ def _cancel_processes(
|
|
|
381
440
|
pysqa_config_directory (str): path to the pysqa config directory (only for pysqa based backend).
|
|
382
441
|
backend (str): name of the backend used to spawn tasks.
|
|
383
442
|
"""
|
|
384
|
-
if terminate_function is not None and terminate_function ==
|
|
443
|
+
if terminate_function is not None and terminate_function == subprocess_terminate:
|
|
385
444
|
for task in process_dict.values():
|
|
386
445
|
terminate_function(task=task)
|
|
387
446
|
elif terminate_function is not None and backend is not None:
|
|
@@ -443,7 +502,9 @@ def _shutdown_executor(
|
|
|
443
502
|
process_dict: dict,
|
|
444
503
|
cache_dir_dict: dict,
|
|
445
504
|
duplicate_dict: Optional[dict] = None,
|
|
505
|
+
status_check_dict: Optional[dict] = None,
|
|
446
506
|
terminate_function: Optional[Callable] = None,
|
|
507
|
+
validate_function: Optional[Callable] = None,
|
|
447
508
|
pysqa_config_directory: Optional[str] = None,
|
|
448
509
|
backend: Optional[str] = None,
|
|
449
510
|
refresh_rate: float = 0.01,
|
|
@@ -466,6 +527,9 @@ def _shutdown_executor(
|
|
|
466
527
|
process_dict (dict): Mapping of task keys to process handles or queue IDs.
|
|
467
528
|
duplicate_dict (dict): Mapping of task keys to lists of duplicate Future objects.
|
|
468
529
|
cache_dir_dict (dict): Mapping of task keys to the cache directory for each task.
|
|
530
|
+
status_check_dict (dict): Mapping of task keys to the last time their queuing system job
|
|
531
|
+
status was queried, used to throttle detection of jobs that died without output.
|
|
532
|
+
validate_function (Callable, optional): Function used to validate the tasks.
|
|
469
533
|
terminate_function (Callable, optional): Function used to terminate running processes.
|
|
470
534
|
pysqa_config_directory (str, optional): Path to the pysqa config directory.
|
|
471
535
|
backend (str, optional): Name of the backend ("slurm", "flux", or None for subprocess).
|
|
@@ -478,7 +542,9 @@ def _shutdown_executor(
|
|
|
478
542
|
cache_dir_dict=cache_dir_dict,
|
|
479
543
|
process_dict=process_dict,
|
|
480
544
|
duplicate_dict=duplicate_dict,
|
|
545
|
+
status_check_dict=status_check_dict,
|
|
481
546
|
terminate_function=terminate_function,
|
|
547
|
+
validate_function=validate_function,
|
|
482
548
|
pysqa_config_directory=pysqa_config_directory,
|
|
483
549
|
backend=backend,
|
|
484
550
|
refresh_rate=refresh_rate,
|
|
@@ -493,7 +559,9 @@ def _shutdown_executor(
|
|
|
493
559
|
cache_dir_dict=cache_dir_dict,
|
|
494
560
|
process_dict=process_dict,
|
|
495
561
|
duplicate_dict=duplicate_dict,
|
|
562
|
+
status_check_dict=status_check_dict,
|
|
496
563
|
terminate_function=terminate_function,
|
|
564
|
+
validate_function=validate_function,
|
|
497
565
|
pysqa_config_directory=pysqa_config_directory,
|
|
498
566
|
backend=backend,
|
|
499
567
|
refresh_rate=refresh_rate,
|
|
@@ -512,7 +580,9 @@ def _shutdown_executor(
|
|
|
512
580
|
cache_dir_dict=cache_dir_dict,
|
|
513
581
|
process_dict=process_dict,
|
|
514
582
|
duplicate_dict=duplicate_dict,
|
|
583
|
+
status_check_dict=status_check_dict,
|
|
515
584
|
terminate_function=terminate_function,
|
|
585
|
+
validate_function=validate_function,
|
|
516
586
|
pysqa_config_directory=pysqa_config_directory,
|
|
517
587
|
backend=backend,
|
|
518
588
|
refresh_rate=refresh_rate,
|
|
@@ -4,12 +4,12 @@ from typing import Optional
|
|
|
4
4
|
|
|
5
5
|
from pysqa import QueueAdapter
|
|
6
6
|
|
|
7
|
+
from executorlib.standalone.command_pysqa import pysqa_execute_command, pysqa_terminate
|
|
7
8
|
from executorlib.standalone.hdf import dump, get_queue_id
|
|
8
9
|
from executorlib.standalone.inputcheck import check_file_exists
|
|
9
10
|
from executorlib.standalone.interactive.spawner import (
|
|
10
11
|
set_current_directory_in_environment,
|
|
11
12
|
)
|
|
12
|
-
from executorlib.standalone.scheduler import pysqa_execute_command, terminate_with_pysqa
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def execute_with_pysqa(
|
|
@@ -122,7 +122,7 @@ def terminate_tasks_in_cache(
|
|
|
122
122
|
for f in hdf5_file_lst:
|
|
123
123
|
queue_id = get_queue_id(f)
|
|
124
124
|
if queue_id is not None:
|
|
125
|
-
|
|
125
|
+
pysqa_terminate(
|
|
126
126
|
queue_id=queue_id,
|
|
127
127
|
config_directory=pysqa_config_directory,
|
|
128
128
|
backend=backend,
|
|
@@ -148,7 +148,7 @@ def terminate_task_in_cache(
|
|
|
148
148
|
file_name = os.path.join(cache_directory, cache_key + "_i.h5")
|
|
149
149
|
queue_id = get_queue_id(file_name=file_name)
|
|
150
150
|
if queue_id is not None:
|
|
151
|
-
|
|
151
|
+
pysqa_terminate(
|
|
152
152
|
queue_id=queue_id,
|
|
153
153
|
config_directory=pysqa_config_directory,
|
|
154
154
|
backend=backend,
|
|
@@ -10,7 +10,7 @@ from executorlib.standalone.interactive.spawner import (
|
|
|
10
10
|
)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def
|
|
13
|
+
def subprocess_execute(
|
|
14
14
|
command: list,
|
|
15
15
|
file_name: str,
|
|
16
16
|
data_dict: dict,
|
|
@@ -65,7 +65,7 @@ def execute_in_subprocess(
|
|
|
65
65
|
return subprocess.Popen(command, universal_newlines=True, cwd=cwd)
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
def
|
|
68
|
+
def subprocess_terminate(task):
|
|
69
69
|
"""
|
|
70
70
|
Terminate a subprocess and wait for it to complete.
|
|
71
71
|
|
|
@@ -12,17 +12,21 @@ from executorlib.standalone.inputcheck import (
|
|
|
12
12
|
from executorlib.task_scheduler.base import TaskSchedulerBase, validate_resource_dict
|
|
13
13
|
from executorlib.task_scheduler.file.shared import execute_tasks_h5
|
|
14
14
|
from executorlib.task_scheduler.file.spawner_subprocess import (
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
subprocess_execute,
|
|
16
|
+
subprocess_terminate,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
try:
|
|
20
|
-
from executorlib.standalone.
|
|
20
|
+
from executorlib.standalone.command_pysqa import (
|
|
21
|
+
pysqa_job_output_validation,
|
|
22
|
+
pysqa_terminate,
|
|
23
|
+
)
|
|
21
24
|
from executorlib.task_scheduler.file.spawner_pysqa import execute_with_pysqa
|
|
22
25
|
except ImportError:
|
|
23
26
|
# If pysqa is not available fall back to executing tasks in a subprocess
|
|
24
|
-
execute_with_pysqa =
|
|
25
|
-
|
|
27
|
+
execute_with_pysqa = subprocess_execute # type: ignore
|
|
28
|
+
pysqa_terminate = None # type: ignore
|
|
29
|
+
pysqa_job_output_validation = None # type: ignore
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
class FileTaskScheduler(TaskSchedulerBase):
|
|
@@ -74,6 +78,7 @@ class FileTaskScheduler(TaskSchedulerBase):
|
|
|
74
78
|
"future_queue": self._future_queue,
|
|
75
79
|
"execute_function": execute_function,
|
|
76
80
|
"terminate_function": terminate_function,
|
|
81
|
+
"validate_function": pysqa_job_output_validation,
|
|
77
82
|
"pysqa_config_directory": pysqa_config_directory,
|
|
78
83
|
"backend": backend,
|
|
79
84
|
"disable_dependencies": disable_dependencies,
|
|
@@ -126,10 +131,10 @@ def create_file_executor(
|
|
|
126
131
|
check_executor(executor=flux_executor)
|
|
127
132
|
check_nested_flux_executor(nested_flux_executor=flux_executor_nesting)
|
|
128
133
|
check_flux_log_files(flux_log_files=flux_log_files)
|
|
129
|
-
if execute_function !=
|
|
130
|
-
terminate_function =
|
|
134
|
+
if execute_function != subprocess_execute:
|
|
135
|
+
terminate_function = pysqa_terminate # type: ignore
|
|
131
136
|
else:
|
|
132
|
-
terminate_function =
|
|
137
|
+
terminate_function = subprocess_terminate # type: ignore
|
|
133
138
|
return FileTaskScheduler(
|
|
134
139
|
executor_kwargs=executor_kwargs,
|
|
135
140
|
pysqa_config_directory=pysqa_config_directory,
|
|
@@ -379,10 +379,9 @@ def _set_init_function(
|
|
|
379
379
|
) -> Optional[Exception]:
|
|
380
380
|
interface_initialization_exception = None
|
|
381
381
|
if init_function is not None and interface.status:
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
interface_initialization_exception = init_exception
|
|
382
|
+
output = interface.send_and_receive_dict(
|
|
383
|
+
input_dict={"init": True, "fn": init_function, "args": (), "kwargs": {}}
|
|
384
|
+
)
|
|
385
|
+
if "error" in output:
|
|
386
|
+
interface_initialization_exception = output["error"]
|
|
388
387
|
return interface_initialization_exception
|
|
@@ -101,13 +101,13 @@ def _execute_task_without_cache(
|
|
|
101
101
|
Returns:
|
|
102
102
|
bool: True if the task was submitted successfully, False otherwise.
|
|
103
103
|
"""
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
output = interface.send_and_receive_dict(input_dict=task_dict)
|
|
105
|
+
if "result" in output:
|
|
106
|
+
future_obj.set_result(output["result"])
|
|
107
|
+
elif isinstance(output["error"], ExecutorlibSocketError):
|
|
108
|
+
return False
|
|
109
|
+
else:
|
|
110
|
+
future_obj.set_exception(exception=output["error"])
|
|
111
111
|
return True
|
|
112
112
|
|
|
113
113
|
|
|
@@ -141,18 +141,17 @@ def _execute_task_with_cache(
|
|
|
141
141
|
)
|
|
142
142
|
file_name = os.path.abspath(os.path.join(cache_directory, task_key + "_o.h5"))
|
|
143
143
|
if file_name not in get_cache_files(cache_directory=cache_directory):
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
data_dict["output"] = result
|
|
144
|
+
time_start = time.time()
|
|
145
|
+
output = interface.send_and_receive_dict(input_dict=task_dict)
|
|
146
|
+
if "result" in output:
|
|
147
|
+
data_dict["output"] = output["result"]
|
|
148
148
|
data_dict["runtime"] = time.time() - time_start
|
|
149
149
|
dump(file_name=file_name, data_dict=data_dict)
|
|
150
|
-
future_obj.set_result(result)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
future_obj.set_exception(exception=thread_exception)
|
|
150
|
+
future_obj.set_result(output["result"])
|
|
151
|
+
elif isinstance(output["error"], ExecutorlibSocketError):
|
|
152
|
+
return False
|
|
153
|
+
else:
|
|
154
|
+
future_obj.set_exception(exception=output["error"])
|
|
156
155
|
else:
|
|
157
156
|
_, _, result = get_output(file_name=file_name)
|
|
158
157
|
future_obj.set_result(result)
|
|
@@ -5,12 +5,12 @@ from typing import Callable, Optional
|
|
|
5
5
|
|
|
6
6
|
from pysqa import QueueAdapter
|
|
7
7
|
|
|
8
|
+
from executorlib.standalone.command_pysqa import pysqa_execute_command, pysqa_terminate
|
|
8
9
|
from executorlib.standalone.inputcheck import validate_number_of_cores
|
|
9
10
|
from executorlib.standalone.interactive.spawner import (
|
|
10
11
|
BaseSpawner,
|
|
11
12
|
set_current_directory_in_environment,
|
|
12
13
|
)
|
|
13
|
-
from executorlib.standalone.scheduler import pysqa_execute_command, terminate_with_pysqa
|
|
14
14
|
from executorlib.task_scheduler.base import validate_resource_dict
|
|
15
15
|
from executorlib.task_scheduler.interactive.blockallocation import (
|
|
16
16
|
BlockAllocationTaskScheduler,
|
|
@@ -162,7 +162,7 @@ class PysqaSpawner(BaseSpawner):
|
|
|
162
162
|
wait (bool, optional): Whether to wait for the interface to shutdown. Defaults to True.
|
|
163
163
|
"""
|
|
164
164
|
if self._process is not None:
|
|
165
|
-
|
|
165
|
+
pysqa_terminate(
|
|
166
166
|
queue_id=self._process,
|
|
167
167
|
config_directory=self._config_directory,
|
|
168
168
|
backend=self._backend,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: executorlib
|
|
3
|
-
Version: 1.10.
|
|
3
|
+
Version: 1.10.2
|
|
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
|
|
@@ -57,12 +57,12 @@ Requires-Dist: mpi4py<=4.1.2,>=3.1.4; extra == 'all'
|
|
|
57
57
|
Requires-Dist: networkx<=3.6.1,>=2.8.8; extra == 'all'
|
|
58
58
|
Requires-Dist: pydantic<=2.13.4,>=2.5.3; extra == 'all'
|
|
59
59
|
Requires-Dist: pygraphviz<=1.14,>=1.10; extra == 'all'
|
|
60
|
-
Requires-Dist: pysqa==0.4.
|
|
60
|
+
Requires-Dist: pysqa==0.4.4; extra == 'all'
|
|
61
61
|
Provides-Extra: cache
|
|
62
62
|
Requires-Dist: h5py<=3.16.0,>=3.6.0; extra == 'cache'
|
|
63
63
|
Provides-Extra: cluster
|
|
64
64
|
Requires-Dist: h5py<=3.16.0,>=3.6.0; extra == 'cluster'
|
|
65
|
-
Requires-Dist: pysqa==0.4.
|
|
65
|
+
Requires-Dist: pysqa==0.4.4; extra == 'cluster'
|
|
66
66
|
Provides-Extra: graph
|
|
67
67
|
Requires-Dist: networkx<=3.6.1,>=2.8.8; extra == 'graph'
|
|
68
68
|
Requires-Dist: pygraphviz<=1.14,>=1.10; extra == 'graph'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
executorlib/__init__.py,sha256=IY_DkT706LPIv2_iIe3whrQNGoDzaDFYBvXyUepoZpw,4780
|
|
2
|
-
executorlib/_version.py,sha256=
|
|
2
|
+
executorlib/_version.py,sha256=aubWhgSag2TvKOWdccSUKUZIISdFObmOxMDtFhqzXC0,522
|
|
3
3
|
executorlib/api.py,sha256=F6ibDJnVcPGxxPe-UScFJHprWHiVsETGhgAvS1T-9ZI,1365
|
|
4
4
|
executorlib/backend/__init__.py,sha256=2b_iwds4VUrJe92nKtQXkoGxkJiXeEDSlbvCRN_-de8,456
|
|
5
5
|
executorlib/backend/cache_parallel.py,sha256=ldb2iYzrhcXJZ0LcH75FpTLO_6F51mleU9JGAilF00k,2020
|
|
@@ -9,42 +9,42 @@ executorlib/backend/interactive_serial.py,sha256=ZV_VGcnCAk7N-VcoJKYEIG0OGUUwR4K
|
|
|
9
9
|
executorlib/executor/__init__.py,sha256=oR2ABLAMYV_TNs1eG-3-MAKHFS7OKB-1KOzCqKNbtWA,310
|
|
10
10
|
executorlib/executor/base.py,sha256=UKJViNoshthAgF2I4FoK0nZT0FL7WgkrzWFYhRiU1H4,6957
|
|
11
11
|
executorlib/executor/flux.py,sha256=g8K1GMfn9KMM4udj3LdmDMahavPBBCOusYVG36W1hAY,40779
|
|
12
|
-
executorlib/executor/single.py,sha256=
|
|
12
|
+
executorlib/executor/single.py,sha256=jX4BZCFdNksA3XhTpkxyLJfXGNh0j-5qq8jSgRsjwuk,36015
|
|
13
13
|
executorlib/executor/slurm.py,sha256=Prvts9IgDHgCCMYoWpNdeoKMZ4h1ICQ05fkomh25Yl4,38337
|
|
14
14
|
executorlib/standalone/__init__.py,sha256=n_Q4NUFu35Fn0qlWIhmoC_FyUkmFGlWn1cNc_7EscF0,290
|
|
15
15
|
executorlib/standalone/batched.py,sha256=o3TtsFJir8nP1O_NZFfW0Q78CoZAFgHMyroWlg520nE,1446
|
|
16
16
|
executorlib/standalone/command.py,sha256=4Xndog84zJdku1ke1DwdUamkWvzRrMz-VDCi5cRXi0M,6548
|
|
17
|
+
executorlib/standalone/command_pysqa.py,sha256=eNTyHxtjUsn3950NOWHfNrFKgtxgD9GAVxCdN6Ewxcc,5948
|
|
17
18
|
executorlib/standalone/error.py,sha256=aaMQoRsoLzLCbYT-E4LZxGM9pm_AhjMesRjVt0d_LDk,714
|
|
18
19
|
executorlib/standalone/hdf.py,sha256=dRePOCYXbPG_i__ASUcD4qs5AAU2fPbP5o_5ytwaOjw,7805
|
|
19
20
|
executorlib/standalone/inputcheck.py,sha256=kSWUzqdMOhILYXW4Isumn1EGH3SegxPUMWRkUWfEleU,10041
|
|
20
21
|
executorlib/standalone/queue.py,sha256=KFEBFQXGvcaf5qcuCjT5kaZg7ucWOTGOtrD96tIBlq0,589
|
|
21
|
-
executorlib/standalone/scheduler.py,sha256=LhCuIdaw9BjrP3GerbHUIqwsvxERdtH5JQoeyAqASIU,2325
|
|
22
22
|
executorlib/standalone/select.py,sha256=FUSXYbhoc_uP_vPji8L4PcK4b2Gwcai7rXZ5dKRVjZM,3061
|
|
23
23
|
executorlib/standalone/serialize.py,sha256=7DxcuCYZVqLf8wa7EXsN-_8fSpUJqyQ-5G6NT863SgM,4716
|
|
24
24
|
executorlib/standalone/validate.py,sha256=hHG32xomLhbqnqd6Vn4AO05bQ7B3WZ1rngTkMsODwwk,4430
|
|
25
25
|
executorlib/standalone/interactive/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
26
|
executorlib/standalone/interactive/arguments.py,sha256=iaVXf7Wq9MUOu3WeYSi2t-f8Si_pyAPyykcnq8bf8cY,3239
|
|
27
27
|
executorlib/standalone/interactive/backend.py,sha256=gGgcvER8w75bM_p1uplSsr6yCtBBlOS_6wR1iQNsntc,3006
|
|
28
|
-
executorlib/standalone/interactive/communication.py,sha256=
|
|
28
|
+
executorlib/standalone/interactive/communication.py,sha256=ctiVWkc7g1mfsYHJSJzg29nAvaEkJopXpajBmOpJOIc,10400
|
|
29
29
|
executorlib/standalone/interactive/spawner.py,sha256=4TEPL4iI_rF25kOaW-eqY74jK93LFZ05cYOHUjIuo4M,6384
|
|
30
30
|
executorlib/task_scheduler/__init__.py,sha256=-sw60rfnEvwFiOAONFHpE3izPA9CPtp5cQ7vRetw2Mw,249
|
|
31
31
|
executorlib/task_scheduler/base.py,sha256=A9nQb8FYKbZGjQP1G5ccT75ODbEqqiM0OVybHAxjqzE,9718
|
|
32
32
|
executorlib/task_scheduler/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
executorlib/task_scheduler/file/backend.py,sha256=tojXcYytAsfABugsOMfkx9oAh01do_DcSneVldMfeK0,2876
|
|
34
|
-
executorlib/task_scheduler/file/shared.py,sha256=
|
|
35
|
-
executorlib/task_scheduler/file/spawner_pysqa.py,sha256=
|
|
36
|
-
executorlib/task_scheduler/file/spawner_subprocess.py,sha256=
|
|
37
|
-
executorlib/task_scheduler/file/task_scheduler.py,sha256=
|
|
34
|
+
executorlib/task_scheduler/file/shared.py,sha256=uJECqjLr_Mq9AQdVOAkEdN-gS2dryfwufmtrcgMs02M,24719
|
|
35
|
+
executorlib/task_scheduler/file/spawner_pysqa.py,sha256=z6kEsyVnCFmpLmVlHL2LxumAgDU0t0zvo59snMv0sMw,6021
|
|
36
|
+
executorlib/task_scheduler/file/spawner_subprocess.py,sha256=yMc4nHRUghgdR0ehihE2CYsaTn1RspRI3ddhIhkTgDw,3086
|
|
37
|
+
executorlib/task_scheduler/file/task_scheduler.py,sha256=gfEpNVJ9u87_yaDYZ86kHt_mAEXfOqBwvxX4IlpKt6o,6170
|
|
38
38
|
executorlib/task_scheduler/interactive/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
-
executorlib/task_scheduler/interactive/blockallocation.py,sha256=
|
|
39
|
+
executorlib/task_scheduler/interactive/blockallocation.py,sha256=hsKTDScVxzLJ4sHhAWIoMjRxZxX2zzpO4r_z-7ksV_s,17705
|
|
40
40
|
executorlib/task_scheduler/interactive/dependency.py,sha256=zQMfI6AjeUv3A1qyPzFah2BKYGWgGLRyEIdZ0XTjq1M,15222
|
|
41
41
|
executorlib/task_scheduler/interactive/dependency_plot.py,sha256=iZBZ9UMDeYOMFjD2-kYOxOdKeodj7TuyshI4HbH4XCU,11788
|
|
42
42
|
executorlib/task_scheduler/interactive/onetoone.py,sha256=cTXgTXR9kKzzlx6EfGumMsEm7DUa7BD9JSeBpdPiupI,13312
|
|
43
|
-
executorlib/task_scheduler/interactive/shared.py,sha256
|
|
43
|
+
executorlib/task_scheduler/interactive/shared.py,sha256=tGlhIsqOfkO7QwNmr3I71M8DFT3NNXHwyUoZ_BrjfWQ,6263
|
|
44
44
|
executorlib/task_scheduler/interactive/spawner_flux.py,sha256=M7CPWvzY7MDkplJ4awymh7WbMYwKsDPTKxA2Op9wfNo,7075
|
|
45
|
-
executorlib/task_scheduler/interactive/spawner_pysqa.py,sha256=
|
|
45
|
+
executorlib/task_scheduler/interactive/spawner_pysqa.py,sha256=jQB8PQAH9cBo_ofpxHTzydg0ghwSOP51CtVL96YH_fc,10346
|
|
46
46
|
executorlib/task_scheduler/interactive/spawner_slurm.py,sha256=K5mj63PE8FYbkw9WWm4uEjoA5q0HM42z1d2wjAmO5YA,3713
|
|
47
|
-
executorlib-1.10.
|
|
48
|
-
executorlib-1.10.
|
|
49
|
-
executorlib-1.10.
|
|
50
|
-
executorlib-1.10.
|
|
47
|
+
executorlib-1.10.2.dist-info/METADATA,sha256=xt32x11FToSi84-Knrpr2Rx4OIUUBfWth9c7xSPf0Es,20966
|
|
48
|
+
executorlib-1.10.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
49
|
+
executorlib-1.10.2.dist-info/licenses/LICENSE,sha256=NkrhccIxPXCbbvp_upyVojt7DNTwZIco3z9wsYpSLqU,1511
|
|
50
|
+
executorlib-1.10.2.dist-info/RECORD,,
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import contextlib
|
|
2
|
-
import subprocess
|
|
3
|
-
from typing import Optional, Union
|
|
4
|
-
|
|
5
|
-
from pysqa import QueueAdapter
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def terminate_with_pysqa(
|
|
9
|
-
queue_id: int,
|
|
10
|
-
config_directory: Optional[str] = None,
|
|
11
|
-
backend: Optional[str] = None,
|
|
12
|
-
):
|
|
13
|
-
"""
|
|
14
|
-
Delete job from queuing system
|
|
15
|
-
|
|
16
|
-
Args:
|
|
17
|
-
queue_id (int): Queuing system ID of the job to delete.
|
|
18
|
-
config_directory (str, optional): path to the config directory.
|
|
19
|
-
backend (str, optional): name of the backend used to spawn tasks ["slurm", "flux"].
|
|
20
|
-
"""
|
|
21
|
-
qa = QueueAdapter(
|
|
22
|
-
directory=config_directory,
|
|
23
|
-
queue_type=backend,
|
|
24
|
-
execute_command=pysqa_execute_command,
|
|
25
|
-
)
|
|
26
|
-
status = qa.get_status_of_job(process_id=queue_id)
|
|
27
|
-
if status is not None and status not in ["finished", "error"]:
|
|
28
|
-
with contextlib.suppress(subprocess.CalledProcessError):
|
|
29
|
-
qa.delete_job(process_id=queue_id)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def pysqa_execute_command(
|
|
33
|
-
commands: str,
|
|
34
|
-
working_directory: Optional[str] = None,
|
|
35
|
-
split_output: bool = True,
|
|
36
|
-
shell: bool = False,
|
|
37
|
-
error_filename: str = "pysqa.err",
|
|
38
|
-
) -> Union[str, list[str]]:
|
|
39
|
-
"""
|
|
40
|
-
A wrapper around the subprocess.check_output function. Modified from pysqa to raise an exception if the subprocess
|
|
41
|
-
fails to submit the job to the queue.
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
commands (str): The command(s) to be executed on the command line
|
|
45
|
-
working_directory (str, optional): The directory where the command is executed. Defaults to None.
|
|
46
|
-
split_output (bool, optional): Boolean flag to split newlines in the output. Defaults to True.
|
|
47
|
-
shell (bool, optional): Additional switch to convert commands to a single string. Defaults to False.
|
|
48
|
-
error_filename (str, optional): In case the execution fails, the output is written to this file. Defaults to "pysqa.err".
|
|
49
|
-
|
|
50
|
-
Returns:
|
|
51
|
-
Union[str, List[str]]: Output of the shell command either as a string or as a list of strings
|
|
52
|
-
"""
|
|
53
|
-
if shell and isinstance(commands, list):
|
|
54
|
-
commands = " ".join(commands)
|
|
55
|
-
out = subprocess.check_output(
|
|
56
|
-
commands,
|
|
57
|
-
cwd=working_directory,
|
|
58
|
-
stderr=subprocess.STDOUT,
|
|
59
|
-
universal_newlines=True,
|
|
60
|
-
shell=not isinstance(commands, list),
|
|
61
|
-
)
|
|
62
|
-
if out is not None and split_output:
|
|
63
|
-
return out.split("\n")
|
|
64
|
-
else:
|
|
65
|
-
return out
|
|
File without changes
|