executorlib 0.2.0__tar.gz → 0.3.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-0.2.0/executorlib.egg-info → executorlib-0.3.0}/PKG-INFO +1 -1
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/_version.py +3 -3
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/base/executor.py +6 -6
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/cache/executor.py +2 -2
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/cache/shared.py +4 -2
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interactive/executor.py +2 -2
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interactive/shared.py +37 -18
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/__init__.py +0 -2
- {executorlib-0.2.0 → executorlib-0.3.0/executorlib.egg-info}/PKG-INFO +1 -1
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib.egg-info/SOURCES.txt +0 -2
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_cache_executor_serial.py +6 -5
- executorlib-0.3.0/tests/test_dependencies_executor.py +330 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_local_executor.py +6 -1
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_shell_executor.py +11 -0
- executorlib-0.2.0/executorlib/standalone/thread.py +0 -42
- executorlib-0.2.0/tests/test_dependencies_executor.py +0 -169
- executorlib-0.2.0/tests/test_shared_thread.py +0 -15
- {executorlib-0.2.0 → executorlib-0.3.0}/LICENSE +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/MANIFEST.in +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/README.md +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/__init__.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/backend/__init__.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/backend/cache_parallel.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/backend/cache_serial.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/backend/interactive_parallel.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/backend/interactive_serial.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/base/__init__.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/cache/__init__.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/cache/backend.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/cache/queue_spawner.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/cache/subprocess_spawner.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interactive/__init__.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interactive/flux.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interactive/slurm.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interfaces/__init__.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interfaces/flux.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interfaces/single.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interfaces/slurm.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/command.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/hdf.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/inputcheck.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/interactive/__init__.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/interactive/backend.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/interactive/communication.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/interactive/spawner.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/plot.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/queue.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/serialize.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib.egg-info/dependency_links.txt +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib.egg-info/requires.txt +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/executorlib.egg-info/top_level.txt +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/pyproject.toml +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/setup.cfg +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/setup.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_backend_serial.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_cache_executor_interactive.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_cache_executor_mpi.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_cache_executor_pysqa_flux.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_cache_hdf.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_cache_shared.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_executor_backend_flux.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_executor_backend_mpi.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_executor_backend_mpi_noblock.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_flux_executor.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_integration_pyiron_workflow.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_local_executor_future.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_plot_dependency.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_plot_dependency_flux.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_pysqa_subprocess.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_shared_backend.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_shared_communication.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_shared_executorbase.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_shared_input_check.py +0 -0
- {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_shell_interactive.py +0 -0
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2025-02-
|
|
11
|
+
"date": "2025-02-14T16:51:45+0100",
|
|
12
12
|
"dirty": true,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.
|
|
14
|
+
"full-revisionid": "ff49de3af89e8739b3b3c5fa041055cf4bc9bc14",
|
|
15
|
+
"version": "0.3.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -6,12 +6,12 @@ from concurrent.futures import (
|
|
|
6
6
|
from concurrent.futures import (
|
|
7
7
|
Future,
|
|
8
8
|
)
|
|
9
|
+
from threading import Thread
|
|
9
10
|
from typing import Callable, Optional, Union
|
|
10
11
|
|
|
11
12
|
from executorlib.standalone.inputcheck import check_resource_dict
|
|
12
13
|
from executorlib.standalone.queue import cancel_items_in_queue
|
|
13
14
|
from executorlib.standalone.serialize import cloudpickle_register
|
|
14
|
-
from executorlib.standalone.thread import RaisingThread
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class ExecutorBase(FutureExecutor):
|
|
@@ -29,7 +29,7 @@ class ExecutorBase(FutureExecutor):
|
|
|
29
29
|
cloudpickle_register(ind=3)
|
|
30
30
|
self._max_cores = max_cores
|
|
31
31
|
self._future_queue: Optional[queue.Queue] = queue.Queue()
|
|
32
|
-
self._process: Optional[Union[
|
|
32
|
+
self._process: Optional[Union[Thread, list[Thread]]] = None
|
|
33
33
|
|
|
34
34
|
@property
|
|
35
35
|
def info(self) -> Optional[dict]:
|
|
@@ -40,13 +40,13 @@ class ExecutorBase(FutureExecutor):
|
|
|
40
40
|
Optional[dict]: Information about the executor.
|
|
41
41
|
"""
|
|
42
42
|
if self._process is not None and isinstance(self._process, list):
|
|
43
|
-
meta_data_dict = self._process[0].
|
|
43
|
+
meta_data_dict = self._process[0]._kwargs.copy() # type: ignore
|
|
44
44
|
if "future_queue" in meta_data_dict:
|
|
45
45
|
del meta_data_dict["future_queue"]
|
|
46
46
|
meta_data_dict["max_workers"] = len(self._process)
|
|
47
47
|
return meta_data_dict
|
|
48
48
|
elif self._process is not None:
|
|
49
|
-
meta_data_dict = self._process.
|
|
49
|
+
meta_data_dict = self._process._kwargs.copy() # type: ignore
|
|
50
50
|
if "future_queue" in meta_data_dict:
|
|
51
51
|
del meta_data_dict["future_queue"]
|
|
52
52
|
return meta_data_dict
|
|
@@ -138,13 +138,13 @@ class ExecutorBase(FutureExecutor):
|
|
|
138
138
|
cancel_items_in_queue(que=self._future_queue)
|
|
139
139
|
if self._process is not None and self._future_queue is not None:
|
|
140
140
|
self._future_queue.put({"shutdown": True, "wait": wait})
|
|
141
|
-
if wait and isinstance(self._process,
|
|
141
|
+
if wait and isinstance(self._process, Thread):
|
|
142
142
|
self._process.join()
|
|
143
143
|
self._future_queue.join()
|
|
144
144
|
self._process = None
|
|
145
145
|
self._future_queue = None
|
|
146
146
|
|
|
147
|
-
def _set_process(self, process:
|
|
147
|
+
def _set_process(self, process: Thread):
|
|
148
148
|
"""
|
|
149
149
|
Set the process for the executor.
|
|
150
150
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from threading import Thread
|
|
2
3
|
from typing import Callable, Optional
|
|
3
4
|
|
|
4
5
|
from executorlib.base.executor import ExecutorBase
|
|
@@ -15,7 +16,6 @@ from executorlib.standalone.inputcheck import (
|
|
|
15
16
|
check_max_workers_and_cores,
|
|
16
17
|
check_nested_flux_executor,
|
|
17
18
|
)
|
|
18
|
-
from executorlib.standalone.thread import RaisingThread
|
|
19
19
|
|
|
20
20
|
try:
|
|
21
21
|
from executorlib.cache.queue_spawner import execute_with_pysqa
|
|
@@ -64,7 +64,7 @@ class FileExecutor(ExecutorBase):
|
|
|
64
64
|
cache_directory_path = os.path.abspath(cache_directory)
|
|
65
65
|
os.makedirs(cache_directory_path, exist_ok=True)
|
|
66
66
|
self._set_process(
|
|
67
|
-
|
|
67
|
+
Thread(
|
|
68
68
|
target=execute_tasks_h5,
|
|
69
69
|
kwargs={
|
|
70
70
|
"future_queue": self._future_queue,
|
|
@@ -115,8 +115,10 @@ def execute_tasks_h5(
|
|
|
115
115
|
]
|
|
116
116
|
else:
|
|
117
117
|
if len(future_wait_key_lst) > 0:
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
task_dict["future"].set_exception(
|
|
119
|
+
ValueError(
|
|
120
|
+
"Future objects are not supported as input if disable_dependencies=True."
|
|
121
|
+
)
|
|
120
122
|
)
|
|
121
123
|
task_dependent_lst = []
|
|
122
124
|
process_dict[task_key] = execute_function(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from concurrent.futures import Future
|
|
2
|
+
from threading import Thread
|
|
2
3
|
from typing import Any, Callable, Optional
|
|
3
4
|
|
|
4
5
|
from executorlib.base.executor import ExecutorBase
|
|
@@ -8,7 +9,6 @@ from executorlib.standalone.plot import (
|
|
|
8
9
|
generate_nodes_and_edges,
|
|
9
10
|
generate_task_hash,
|
|
10
11
|
)
|
|
11
|
-
from executorlib.standalone.thread import RaisingThread
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class ExecutorWithDependencies(ExecutorBase):
|
|
@@ -41,7 +41,7 @@ class ExecutorWithDependencies(ExecutorBase):
|
|
|
41
41
|
) -> None:
|
|
42
42
|
super().__init__(max_cores=max_cores)
|
|
43
43
|
self._set_process(
|
|
44
|
-
|
|
44
|
+
Thread(
|
|
45
45
|
target=execute_tasks_with_dependencies,
|
|
46
46
|
kwargs={
|
|
47
47
|
# Executor Arguments
|
|
@@ -3,7 +3,9 @@ import os
|
|
|
3
3
|
import queue
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
|
-
from
|
|
6
|
+
from asyncio.exceptions import CancelledError
|
|
7
|
+
from concurrent.futures import Future, TimeoutError
|
|
8
|
+
from threading import Thread
|
|
7
9
|
from time import sleep
|
|
8
10
|
from typing import Any, Callable, Optional, Union
|
|
9
11
|
|
|
@@ -19,7 +21,6 @@ from executorlib.standalone.interactive.communication import (
|
|
|
19
21
|
)
|
|
20
22
|
from executorlib.standalone.interactive.spawner import BaseSpawner, MpiExecSpawner
|
|
21
23
|
from executorlib.standalone.serialize import serialize_funct_h5
|
|
22
|
-
from executorlib.standalone.thread import RaisingThread
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class ExecutorBroker(ExecutorBase):
|
|
@@ -88,7 +89,7 @@ class ExecutorBroker(ExecutorBase):
|
|
|
88
89
|
self._process = None
|
|
89
90
|
self._future_queue = None
|
|
90
91
|
|
|
91
|
-
def _set_process(self, process: list[
|
|
92
|
+
def _set_process(self, process: list[Thread]): # type: ignore
|
|
92
93
|
"""
|
|
93
94
|
Set the process for the executor.
|
|
94
95
|
|
|
@@ -148,7 +149,7 @@ class InteractiveExecutor(ExecutorBroker):
|
|
|
148
149
|
executor_kwargs["queue_join_on_shutdown"] = False
|
|
149
150
|
self._set_process(
|
|
150
151
|
process=[
|
|
151
|
-
|
|
152
|
+
Thread(
|
|
152
153
|
target=execute_parallel_tasks,
|
|
153
154
|
kwargs=executor_kwargs,
|
|
154
155
|
)
|
|
@@ -204,7 +205,7 @@ class InteractiveStepExecutor(ExecutorBase):
|
|
|
204
205
|
executor_kwargs["max_cores"] = max_cores
|
|
205
206
|
executor_kwargs["max_workers"] = max_workers
|
|
206
207
|
self._set_process(
|
|
207
|
-
|
|
208
|
+
Thread(
|
|
208
209
|
target=execute_separate_tasks,
|
|
209
210
|
kwargs=executor_kwargs,
|
|
210
211
|
)
|
|
@@ -361,15 +362,19 @@ def execute_tasks_with_dependencies(
|
|
|
361
362
|
task_dict is not None and "fn" in task_dict and "future" in task_dict
|
|
362
363
|
):
|
|
363
364
|
future_lst, ready_flag = _get_future_objects_from_input(task_dict=task_dict)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
365
|
+
exception_lst = _get_exception_lst(future_lst=future_lst)
|
|
366
|
+
if not _get_exception(future_obj=task_dict["future"]):
|
|
367
|
+
if len(exception_lst) > 0:
|
|
368
|
+
task_dict["future"].set_exception(exception_lst[0])
|
|
369
|
+
elif len(future_lst) == 0 or ready_flag:
|
|
370
|
+
# No future objects are used in the input or all future objects are already done
|
|
371
|
+
task_dict["args"], task_dict["kwargs"] = _update_futures_in_input(
|
|
372
|
+
args=task_dict["args"], kwargs=task_dict["kwargs"]
|
|
373
|
+
)
|
|
374
|
+
executor_queue.put(task_dict)
|
|
375
|
+
else: # Otherwise add the function to the wait list
|
|
376
|
+
task_dict["future_lst"] = future_lst
|
|
377
|
+
wait_lst.append(task_dict)
|
|
373
378
|
future_queue.task_done()
|
|
374
379
|
elif len(wait_lst) > 0:
|
|
375
380
|
number_waiting = len(wait_lst)
|
|
@@ -455,7 +460,10 @@ def _submit_waiting_task(wait_lst: list[dict], executor_queue: queue.Queue) -> l
|
|
|
455
460
|
"""
|
|
456
461
|
wait_tmp_lst = []
|
|
457
462
|
for task_wait_dict in wait_lst:
|
|
458
|
-
|
|
463
|
+
exception_lst = _get_exception_lst(future_lst=task_wait_dict["future_lst"])
|
|
464
|
+
if len(exception_lst) > 0:
|
|
465
|
+
task_wait_dict["future"].set_exception(exception_lst[0])
|
|
466
|
+
elif all(future.done() for future in task_wait_dict["future_lst"]):
|
|
459
467
|
del task_wait_dict["future_lst"]
|
|
460
468
|
task_wait_dict["args"], task_wait_dict["kwargs"] = _update_futures_in_input(
|
|
461
469
|
args=task_wait_dict["args"], kwargs=task_wait_dict["kwargs"]
|
|
@@ -582,7 +590,7 @@ def _submit_function_to_separate_process(
|
|
|
582
590
|
"init_function": None,
|
|
583
591
|
}
|
|
584
592
|
)
|
|
585
|
-
process =
|
|
593
|
+
process = Thread(
|
|
586
594
|
target=execute_parallel_tasks,
|
|
587
595
|
kwargs=task_kwargs,
|
|
588
596
|
)
|
|
@@ -603,14 +611,13 @@ def _execute_task(
|
|
|
603
611
|
future_queue (Queue): Queue for receiving new tasks.
|
|
604
612
|
"""
|
|
605
613
|
f = task_dict.pop("future")
|
|
606
|
-
if f.set_running_or_notify_cancel():
|
|
614
|
+
if not f.done() and f.set_running_or_notify_cancel():
|
|
607
615
|
try:
|
|
608
616
|
f.set_result(interface.send_and_receive_dict(input_dict=task_dict))
|
|
609
617
|
except Exception as thread_exception:
|
|
610
618
|
interface.shutdown(wait=True)
|
|
611
619
|
future_queue.task_done()
|
|
612
620
|
f.set_exception(exception=thread_exception)
|
|
613
|
-
raise thread_exception
|
|
614
621
|
else:
|
|
615
622
|
future_queue.task_done()
|
|
616
623
|
|
|
@@ -663,3 +670,15 @@ def _execute_task_with_cache(
|
|
|
663
670
|
future = task_dict["future"]
|
|
664
671
|
future.set_result(result)
|
|
665
672
|
future_queue.task_done()
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def _get_exception_lst(future_lst: list[Future]) -> list:
|
|
676
|
+
return [f.exception() for f in future_lst if _get_exception(future_obj=f)]
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def _get_exception(future_obj: Future) -> bool:
|
|
680
|
+
try:
|
|
681
|
+
excp = future_obj.exception(timeout=10**-10)
|
|
682
|
+
return excp is not None and not isinstance(excp, CancelledError)
|
|
683
|
+
except TimeoutError:
|
|
684
|
+
return False
|
|
@@ -7,7 +7,6 @@ from executorlib.standalone.interactive.communication import (
|
|
|
7
7
|
interface_shutdown,
|
|
8
8
|
)
|
|
9
9
|
from executorlib.standalone.interactive.spawner import MpiExecSpawner
|
|
10
|
-
from executorlib.standalone.thread import RaisingThread
|
|
11
10
|
|
|
12
11
|
__all__ = [
|
|
13
12
|
"SocketInterface",
|
|
@@ -16,6 +15,5 @@ __all__ = [
|
|
|
16
15
|
"interface_send",
|
|
17
16
|
"interface_shutdown",
|
|
18
17
|
"interface_receive",
|
|
19
|
-
"RaisingThread",
|
|
20
18
|
"MpiExecSpawner",
|
|
21
19
|
]
|
|
@@ -39,7 +39,6 @@ executorlib/standalone/inputcheck.py
|
|
|
39
39
|
executorlib/standalone/plot.py
|
|
40
40
|
executorlib/standalone/queue.py
|
|
41
41
|
executorlib/standalone/serialize.py
|
|
42
|
-
executorlib/standalone/thread.py
|
|
43
42
|
executorlib/standalone/interactive/__init__.py
|
|
44
43
|
executorlib/standalone/interactive/backend.py
|
|
45
44
|
executorlib/standalone/interactive/communication.py
|
|
@@ -66,6 +65,5 @@ tests/test_shared_backend.py
|
|
|
66
65
|
tests/test_shared_communication.py
|
|
67
66
|
tests/test_shared_executorbase.py
|
|
68
67
|
tests/test_shared_input_check.py
|
|
69
|
-
tests/test_shared_thread.py
|
|
70
68
|
tests/test_shell_executor.py
|
|
71
69
|
tests/test_shell_interactive.py
|
|
@@ -3,12 +3,12 @@ import os
|
|
|
3
3
|
from queue import Queue
|
|
4
4
|
import shutil
|
|
5
5
|
import unittest
|
|
6
|
+
from threading import Thread
|
|
6
7
|
|
|
7
8
|
from executorlib.cache.subprocess_spawner import (
|
|
8
9
|
execute_in_subprocess,
|
|
9
10
|
terminate_subprocess,
|
|
10
11
|
)
|
|
11
|
-
from executorlib.standalone.thread import RaisingThread
|
|
12
12
|
|
|
13
13
|
try:
|
|
14
14
|
from executorlib.cache.executor import FileExecutor, create_file_executor
|
|
@@ -57,7 +57,8 @@ class TestCacheExecutorSerial(unittest.TestCase):
|
|
|
57
57
|
with FileExecutor(
|
|
58
58
|
execute_function=execute_in_subprocess, disable_dependencies=True
|
|
59
59
|
) as exe:
|
|
60
|
-
exe.submit(my_funct, 1, b=exe.submit(my_funct, 1, b=2))
|
|
60
|
+
fs = exe.submit(my_funct, 1, b=exe.submit(my_funct, 1, b=2))
|
|
61
|
+
fs.result()
|
|
61
62
|
|
|
62
63
|
def test_executor_working_directory(self):
|
|
63
64
|
cwd = os.path.join(os.path.dirname(__file__), "executables")
|
|
@@ -81,7 +82,7 @@ class TestCacheExecutorSerial(unittest.TestCase):
|
|
|
81
82
|
)
|
|
82
83
|
cache_dir = os.path.abspath("cache")
|
|
83
84
|
os.makedirs(cache_dir, exist_ok=True)
|
|
84
|
-
process =
|
|
85
|
+
process = Thread(
|
|
85
86
|
target=execute_tasks_h5,
|
|
86
87
|
kwargs={
|
|
87
88
|
"future_queue": q,
|
|
@@ -122,7 +123,7 @@ class TestCacheExecutorSerial(unittest.TestCase):
|
|
|
122
123
|
)
|
|
123
124
|
cache_dir = os.path.abspath("cache")
|
|
124
125
|
os.makedirs(cache_dir, exist_ok=True)
|
|
125
|
-
process =
|
|
126
|
+
process = Thread(
|
|
126
127
|
target=execute_tasks_h5,
|
|
127
128
|
kwargs={
|
|
128
129
|
"future_queue": q,
|
|
@@ -163,7 +164,7 @@ class TestCacheExecutorSerial(unittest.TestCase):
|
|
|
163
164
|
)
|
|
164
165
|
cache_dir = os.path.abspath("cache")
|
|
165
166
|
os.makedirs(cache_dir, exist_ok=True)
|
|
166
|
-
process =
|
|
167
|
+
process = Thread(
|
|
167
168
|
target=execute_tasks_h5,
|
|
168
169
|
kwargs={
|
|
169
170
|
"future_queue": q,
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
from concurrent.futures import Future
|
|
2
|
+
import unittest
|
|
3
|
+
import sys
|
|
4
|
+
from time import sleep
|
|
5
|
+
from queue import Queue
|
|
6
|
+
from threading import Thread
|
|
7
|
+
|
|
8
|
+
from executorlib import SingleNodeExecutor
|
|
9
|
+
from executorlib.interfaces.single import create_single_node_executor
|
|
10
|
+
from executorlib.interactive.shared import execute_tasks_with_dependencies
|
|
11
|
+
from executorlib.standalone.serialize import cloudpickle_register
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import pygraphviz
|
|
16
|
+
|
|
17
|
+
skip_graphviz_test = False
|
|
18
|
+
except ImportError:
|
|
19
|
+
skip_graphviz_test = True
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def add_function(parameter_1, parameter_2):
|
|
23
|
+
sleep(0.2)
|
|
24
|
+
return parameter_1 + parameter_2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def generate_tasks(length):
|
|
28
|
+
sleep(0.2)
|
|
29
|
+
return range(length)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def calc_from_lst(lst, ind, parameter):
|
|
33
|
+
sleep(0.2)
|
|
34
|
+
return lst[ind] + parameter
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def merge(lst):
|
|
38
|
+
sleep(0.2)
|
|
39
|
+
return sum(lst)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def return_input_dict(input_dict):
|
|
43
|
+
return input_dict
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def raise_error(parameter):
|
|
47
|
+
raise RuntimeError
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestExecutorWithDependencies(unittest.TestCase):
|
|
51
|
+
def test_executor(self):
|
|
52
|
+
with SingleNodeExecutor(max_cores=1) as exe:
|
|
53
|
+
cloudpickle_register(ind=1)
|
|
54
|
+
future_1 = exe.submit(add_function, 1, parameter_2=2)
|
|
55
|
+
future_2 = exe.submit(add_function, 1, parameter_2=future_1)
|
|
56
|
+
self.assertEqual(future_2.result(), 4)
|
|
57
|
+
|
|
58
|
+
def test_dependency_steps(self):
|
|
59
|
+
cloudpickle_register(ind=1)
|
|
60
|
+
fs1 = Future()
|
|
61
|
+
fs2 = Future()
|
|
62
|
+
q = Queue()
|
|
63
|
+
q.put(
|
|
64
|
+
{
|
|
65
|
+
"fn": add_function,
|
|
66
|
+
"args": (),
|
|
67
|
+
"kwargs": {"parameter_1": 1, "parameter_2": 2},
|
|
68
|
+
"future": fs1,
|
|
69
|
+
"resource_dict": {"cores": 1},
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
q.put(
|
|
73
|
+
{
|
|
74
|
+
"fn": add_function,
|
|
75
|
+
"args": (),
|
|
76
|
+
"kwargs": {"parameter_1": 1, "parameter_2": fs1},
|
|
77
|
+
"future": fs2,
|
|
78
|
+
"resource_dict": {"cores": 1},
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
executor = create_single_node_executor(
|
|
82
|
+
max_workers=1,
|
|
83
|
+
max_cores=2,
|
|
84
|
+
resource_dict={
|
|
85
|
+
"cores": 1,
|
|
86
|
+
"threads_per_core": 1,
|
|
87
|
+
"gpus_per_core": 0,
|
|
88
|
+
"cwd": None,
|
|
89
|
+
"openmpi_oversubscribe": False,
|
|
90
|
+
"slurm_cmd_args": [],
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
process = Thread(
|
|
94
|
+
target=execute_tasks_with_dependencies,
|
|
95
|
+
kwargs={
|
|
96
|
+
"future_queue": q,
|
|
97
|
+
"executor_queue": executor._future_queue,
|
|
98
|
+
"executor": executor,
|
|
99
|
+
"refresh_rate": 0.01,
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
process.start()
|
|
103
|
+
self.assertFalse(fs1.done())
|
|
104
|
+
self.assertFalse(fs2.done())
|
|
105
|
+
self.assertEqual(fs2.result(), 4)
|
|
106
|
+
self.assertTrue(fs1.done())
|
|
107
|
+
self.assertTrue(fs2.done())
|
|
108
|
+
q.put({"shutdown": True, "wait": True})
|
|
109
|
+
|
|
110
|
+
def test_dependency_steps_error(self):
|
|
111
|
+
cloudpickle_register(ind=1)
|
|
112
|
+
fs1 = Future()
|
|
113
|
+
fs2 = Future()
|
|
114
|
+
q = Queue()
|
|
115
|
+
q.put(
|
|
116
|
+
{
|
|
117
|
+
"fn": raise_error,
|
|
118
|
+
"args": (),
|
|
119
|
+
"kwargs": {"parameter": 0},
|
|
120
|
+
"future": fs1,
|
|
121
|
+
"resource_dict": {"cores": 1},
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
q.put(
|
|
125
|
+
{
|
|
126
|
+
"fn": add_function,
|
|
127
|
+
"args": (),
|
|
128
|
+
"kwargs": {"parameter_1": 1, "parameter_2": fs1},
|
|
129
|
+
"future": fs2,
|
|
130
|
+
"resource_dict": {"cores": 1},
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
executor = create_single_node_executor(
|
|
134
|
+
max_workers=1,
|
|
135
|
+
max_cores=2,
|
|
136
|
+
resource_dict={
|
|
137
|
+
"cores": 1,
|
|
138
|
+
"threads_per_core": 1,
|
|
139
|
+
"gpus_per_core": 0,
|
|
140
|
+
"cwd": None,
|
|
141
|
+
"openmpi_oversubscribe": False,
|
|
142
|
+
"slurm_cmd_args": [],
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
process = Thread(
|
|
146
|
+
target=execute_tasks_with_dependencies,
|
|
147
|
+
kwargs={
|
|
148
|
+
"future_queue": q,
|
|
149
|
+
"executor_queue": executor._future_queue,
|
|
150
|
+
"executor": executor,
|
|
151
|
+
"refresh_rate": 0.01,
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
process.start()
|
|
155
|
+
self.assertFalse(fs1.done())
|
|
156
|
+
self.assertFalse(fs2.done())
|
|
157
|
+
self.assertTrue(fs1.exception() is not None)
|
|
158
|
+
self.assertTrue(fs2.exception() is not None)
|
|
159
|
+
with self.assertRaises(RuntimeError):
|
|
160
|
+
fs2.result()
|
|
161
|
+
q.put({"shutdown": True, "wait": True})
|
|
162
|
+
|
|
163
|
+
def test_dependency_steps_error_before(self):
|
|
164
|
+
cloudpickle_register(ind=1)
|
|
165
|
+
fs1 = Future()
|
|
166
|
+
fs1.set_exception(RuntimeError())
|
|
167
|
+
fs2 = Future()
|
|
168
|
+
q = Queue()
|
|
169
|
+
q.put(
|
|
170
|
+
{
|
|
171
|
+
"fn": add_function,
|
|
172
|
+
"args": (),
|
|
173
|
+
"kwargs": {"parameter_1": 1, "parameter_2": 2},
|
|
174
|
+
"future": fs1,
|
|
175
|
+
"resource_dict": {"cores": 1},
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
q.put(
|
|
179
|
+
{
|
|
180
|
+
"fn": add_function,
|
|
181
|
+
"args": (),
|
|
182
|
+
"kwargs": {"parameter_1": 1, "parameter_2": fs1},
|
|
183
|
+
"future": fs2,
|
|
184
|
+
"resource_dict": {"cores": 1},
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
executor = create_single_node_executor(
|
|
188
|
+
max_workers=1,
|
|
189
|
+
max_cores=2,
|
|
190
|
+
resource_dict={
|
|
191
|
+
"cores": 1,
|
|
192
|
+
"threads_per_core": 1,
|
|
193
|
+
"gpus_per_core": 0,
|
|
194
|
+
"cwd": None,
|
|
195
|
+
"openmpi_oversubscribe": False,
|
|
196
|
+
"slurm_cmd_args": [],
|
|
197
|
+
},
|
|
198
|
+
)
|
|
199
|
+
process = Thread(
|
|
200
|
+
target=execute_tasks_with_dependencies,
|
|
201
|
+
kwargs={
|
|
202
|
+
"future_queue": q,
|
|
203
|
+
"executor_queue": executor._future_queue,
|
|
204
|
+
"executor": executor,
|
|
205
|
+
"refresh_rate": 0.01,
|
|
206
|
+
},
|
|
207
|
+
)
|
|
208
|
+
process.start()
|
|
209
|
+
self.assertTrue(fs1.exception() is not None)
|
|
210
|
+
self.assertTrue(fs2.exception() is not None)
|
|
211
|
+
with self.assertRaises(RuntimeError):
|
|
212
|
+
fs2.result()
|
|
213
|
+
executor.shutdown(wait=True)
|
|
214
|
+
q.put({"shutdown": True, "wait": True})
|
|
215
|
+
q.join()
|
|
216
|
+
process.join()
|
|
217
|
+
|
|
218
|
+
def test_many_to_one(self):
|
|
219
|
+
length = 5
|
|
220
|
+
parameter = 1
|
|
221
|
+
with SingleNodeExecutor(max_cores=2) as exe:
|
|
222
|
+
cloudpickle_register(ind=1)
|
|
223
|
+
future_lst = exe.submit(
|
|
224
|
+
generate_tasks,
|
|
225
|
+
length=length,
|
|
226
|
+
resource_dict={"cores": 1},
|
|
227
|
+
)
|
|
228
|
+
lst = []
|
|
229
|
+
for i in range(length):
|
|
230
|
+
lst.append(
|
|
231
|
+
exe.submit(
|
|
232
|
+
calc_from_lst,
|
|
233
|
+
lst=future_lst,
|
|
234
|
+
ind=i,
|
|
235
|
+
parameter=parameter,
|
|
236
|
+
resource_dict={"cores": 1},
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
future_sum = exe.submit(
|
|
240
|
+
merge,
|
|
241
|
+
lst=lst,
|
|
242
|
+
resource_dict={"cores": 1},
|
|
243
|
+
)
|
|
244
|
+
self.assertEqual(future_sum.result(), 15)
|
|
245
|
+
|
|
246
|
+
def test_future_input_dict(self):
|
|
247
|
+
with SingleNodeExecutor() as exe:
|
|
248
|
+
fs = exe.submit(
|
|
249
|
+
return_input_dict,
|
|
250
|
+
input_dict={"a": exe.submit(sum, [2, 2])},
|
|
251
|
+
)
|
|
252
|
+
self.assertEqual(fs.result()["a"], 4)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class TestExecutorErrors(unittest.TestCase):
|
|
256
|
+
def test_block_allocation_false_one_worker(self):
|
|
257
|
+
with self.assertRaises(RuntimeError):
|
|
258
|
+
with SingleNodeExecutor(max_cores=1, block_allocation=False) as exe:
|
|
259
|
+
cloudpickle_register(ind=1)
|
|
260
|
+
fs = exe.submit(raise_error, parameter=0)
|
|
261
|
+
fs.result()
|
|
262
|
+
|
|
263
|
+
def test_block_allocation_true_one_worker(self):
|
|
264
|
+
with self.assertRaises(RuntimeError):
|
|
265
|
+
with SingleNodeExecutor(max_cores=1, block_allocation=True) as exe:
|
|
266
|
+
cloudpickle_register(ind=1)
|
|
267
|
+
fs = exe.submit(raise_error, parameter=0)
|
|
268
|
+
fs.result()
|
|
269
|
+
|
|
270
|
+
def test_block_allocation_false_two_workers(self):
|
|
271
|
+
with self.assertRaises(RuntimeError):
|
|
272
|
+
with SingleNodeExecutor(max_cores=2, block_allocation=False) as exe:
|
|
273
|
+
cloudpickle_register(ind=1)
|
|
274
|
+
fs = exe.submit(raise_error, parameter=0)
|
|
275
|
+
fs.result()
|
|
276
|
+
|
|
277
|
+
def test_block_allocation_true_two_workers(self):
|
|
278
|
+
with self.assertRaises(RuntimeError):
|
|
279
|
+
with SingleNodeExecutor(max_cores=2, block_allocation=True) as exe:
|
|
280
|
+
cloudpickle_register(ind=1)
|
|
281
|
+
fs = exe.submit(raise_error, parameter=0)
|
|
282
|
+
fs.result()
|
|
283
|
+
|
|
284
|
+
def test_block_allocation_false_one_worker_loop(self):
|
|
285
|
+
with self.assertRaises(RuntimeError):
|
|
286
|
+
with SingleNodeExecutor(max_cores=1, block_allocation=False) as exe:
|
|
287
|
+
cloudpickle_register(ind=1)
|
|
288
|
+
lst = []
|
|
289
|
+
for i in range(1, 4):
|
|
290
|
+
lst = exe.submit(
|
|
291
|
+
raise_error,
|
|
292
|
+
parameter=lst,
|
|
293
|
+
)
|
|
294
|
+
lst.result()
|
|
295
|
+
|
|
296
|
+
def test_block_allocation_true_one_worker_loop(self):
|
|
297
|
+
with self.assertRaises(RuntimeError):
|
|
298
|
+
with SingleNodeExecutor(max_cores=1, block_allocation=True) as exe:
|
|
299
|
+
cloudpickle_register(ind=1)
|
|
300
|
+
lst = []
|
|
301
|
+
for i in range(1, 4):
|
|
302
|
+
lst = exe.submit(
|
|
303
|
+
raise_error,
|
|
304
|
+
parameter=lst,
|
|
305
|
+
)
|
|
306
|
+
lst.result()
|
|
307
|
+
|
|
308
|
+
def test_block_allocation_false_two_workers_loop(self):
|
|
309
|
+
with self.assertRaises(RuntimeError):
|
|
310
|
+
with SingleNodeExecutor(max_cores=2, block_allocation=False) as exe:
|
|
311
|
+
cloudpickle_register(ind=1)
|
|
312
|
+
lst = []
|
|
313
|
+
for i in range(1, 4):
|
|
314
|
+
lst = exe.submit(
|
|
315
|
+
raise_error,
|
|
316
|
+
parameter=lst,
|
|
317
|
+
)
|
|
318
|
+
lst.result()
|
|
319
|
+
|
|
320
|
+
def test_block_allocation_true_two_workers_loop(self):
|
|
321
|
+
with self.assertRaises(RuntimeError):
|
|
322
|
+
with SingleNodeExecutor(max_cores=2, block_allocation=True) as exe:
|
|
323
|
+
cloudpickle_register(ind=1)
|
|
324
|
+
lst = []
|
|
325
|
+
for i in range(1, 4):
|
|
326
|
+
lst = exe.submit(
|
|
327
|
+
raise_error,
|
|
328
|
+
parameter=lst,
|
|
329
|
+
)
|
|
330
|
+
lst.result()
|
|
@@ -318,7 +318,8 @@ class TestFuturePool(unittest.TestCase):
|
|
|
318
318
|
executor_kwargs={"cores": 1},
|
|
319
319
|
spawner=MpiExecSpawner,
|
|
320
320
|
) as p:
|
|
321
|
-
p.submit(raise_error)
|
|
321
|
+
fs = p.submit(raise_error)
|
|
322
|
+
fs.result()
|
|
322
323
|
|
|
323
324
|
def test_executor_exception_future(self):
|
|
324
325
|
with self.assertRaises(RuntimeError):
|
|
@@ -424,6 +425,7 @@ class TestFuturePool(unittest.TestCase):
|
|
|
424
425
|
f = Future()
|
|
425
426
|
q = Queue()
|
|
426
427
|
q.put({"fn": calc_array, "args": (), "kwargs": {}, "future": f})
|
|
428
|
+
q.put({"shutdown": True, "wait": True})
|
|
427
429
|
cloudpickle_register(ind=1)
|
|
428
430
|
with self.assertRaises(TypeError):
|
|
429
431
|
execute_parallel_tasks(
|
|
@@ -432,12 +434,14 @@ class TestFuturePool(unittest.TestCase):
|
|
|
432
434
|
openmpi_oversubscribe=False,
|
|
433
435
|
spawner=MpiExecSpawner,
|
|
434
436
|
)
|
|
437
|
+
f.result()
|
|
435
438
|
q.join()
|
|
436
439
|
|
|
437
440
|
def test_execute_task_failed_wrong_argument(self):
|
|
438
441
|
f = Future()
|
|
439
442
|
q = Queue()
|
|
440
443
|
q.put({"fn": calc_array, "args": (), "kwargs": {"j": 4}, "future": f})
|
|
444
|
+
q.put({"shutdown": True, "wait": True})
|
|
441
445
|
cloudpickle_register(ind=1)
|
|
442
446
|
with self.assertRaises(TypeError):
|
|
443
447
|
execute_parallel_tasks(
|
|
@@ -446,6 +450,7 @@ class TestFuturePool(unittest.TestCase):
|
|
|
446
450
|
openmpi_oversubscribe=False,
|
|
447
451
|
spawner=MpiExecSpawner,
|
|
448
452
|
)
|
|
453
|
+
f.result()
|
|
449
454
|
q.join()
|
|
450
455
|
|
|
451
456
|
def test_execute_task(self):
|
|
@@ -53,6 +53,9 @@ class SubprocessExecutorTest(unittest.TestCase):
|
|
|
53
53
|
"future": f,
|
|
54
54
|
}
|
|
55
55
|
)
|
|
56
|
+
test_queue.put(
|
|
57
|
+
{"shutdown": True, "wait": True}
|
|
58
|
+
)
|
|
56
59
|
cloudpickle_register(ind=1)
|
|
57
60
|
with self.assertRaises(TypeError):
|
|
58
61
|
execute_parallel_tasks(
|
|
@@ -61,6 +64,7 @@ class SubprocessExecutorTest(unittest.TestCase):
|
|
|
61
64
|
openmpi_oversubscribe=False,
|
|
62
65
|
spawner=MpiExecSpawner,
|
|
63
66
|
)
|
|
67
|
+
f.result()
|
|
64
68
|
|
|
65
69
|
def test_broken_executable(self):
|
|
66
70
|
test_queue = queue.Queue()
|
|
@@ -73,6 +77,12 @@ class SubprocessExecutorTest(unittest.TestCase):
|
|
|
73
77
|
"future": f,
|
|
74
78
|
}
|
|
75
79
|
)
|
|
80
|
+
test_queue.put(
|
|
81
|
+
{
|
|
82
|
+
"shutdown": True,
|
|
83
|
+
"wait": True,
|
|
84
|
+
}
|
|
85
|
+
)
|
|
76
86
|
cloudpickle_register(ind=1)
|
|
77
87
|
with self.assertRaises(FileNotFoundError):
|
|
78
88
|
execute_parallel_tasks(
|
|
@@ -81,6 +91,7 @@ class SubprocessExecutorTest(unittest.TestCase):
|
|
|
81
91
|
openmpi_oversubscribe=False,
|
|
82
92
|
spawner=MpiExecSpawner,
|
|
83
93
|
)
|
|
94
|
+
f.result()
|
|
84
95
|
|
|
85
96
|
def test_shell_static_executor_args(self):
|
|
86
97
|
with SingleNodeExecutor(max_workers=1) as exe:
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
from threading import Thread
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class RaisingThread(Thread):
|
|
5
|
-
"""
|
|
6
|
-
A subclass of Thread that allows catching exceptions raised in the thread.
|
|
7
|
-
|
|
8
|
-
Based on https://stackoverflow.com/questions/2829329/catch-a-threads-exception-in-the-caller-thread
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
def __init__(
|
|
12
|
-
self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None
|
|
13
|
-
):
|
|
14
|
-
super().__init__(
|
|
15
|
-
group=group,
|
|
16
|
-
target=target,
|
|
17
|
-
name=name,
|
|
18
|
-
args=args,
|
|
19
|
-
kwargs=kwargs,
|
|
20
|
-
daemon=daemon,
|
|
21
|
-
)
|
|
22
|
-
self._exception = None
|
|
23
|
-
|
|
24
|
-
def get_kwargs(self):
|
|
25
|
-
return self._kwargs
|
|
26
|
-
|
|
27
|
-
def run(self) -> None:
|
|
28
|
-
"""
|
|
29
|
-
Run the thread's target function and catch any exceptions raised.
|
|
30
|
-
"""
|
|
31
|
-
try:
|
|
32
|
-
super().run()
|
|
33
|
-
except Exception as e:
|
|
34
|
-
self._exception = e
|
|
35
|
-
|
|
36
|
-
def join(self, timeout=None) -> None:
|
|
37
|
-
"""
|
|
38
|
-
Wait for the thread to complete and re-raise any exceptions caught during execution.
|
|
39
|
-
"""
|
|
40
|
-
super().join(timeout=timeout)
|
|
41
|
-
if self._exception:
|
|
42
|
-
raise self._exception
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
from concurrent.futures import Future
|
|
2
|
-
import unittest
|
|
3
|
-
from time import sleep
|
|
4
|
-
from queue import Queue
|
|
5
|
-
|
|
6
|
-
from executorlib import SingleNodeExecutor
|
|
7
|
-
from executorlib.interfaces.single import create_single_node_executor
|
|
8
|
-
from executorlib.interactive.shared import execute_tasks_with_dependencies
|
|
9
|
-
from executorlib.standalone.serialize import cloudpickle_register
|
|
10
|
-
from executorlib.standalone.thread import RaisingThread
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
try:
|
|
14
|
-
import pygraphviz
|
|
15
|
-
|
|
16
|
-
skip_graphviz_test = False
|
|
17
|
-
except ImportError:
|
|
18
|
-
skip_graphviz_test = True
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def add_function(parameter_1, parameter_2):
|
|
22
|
-
sleep(0.2)
|
|
23
|
-
return parameter_1 + parameter_2
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def generate_tasks(length):
|
|
27
|
-
sleep(0.2)
|
|
28
|
-
return range(length)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def calc_from_lst(lst, ind, parameter):
|
|
32
|
-
sleep(0.2)
|
|
33
|
-
return lst[ind] + parameter
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def merge(lst):
|
|
37
|
-
sleep(0.2)
|
|
38
|
-
return sum(lst)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def return_input_dict(input_dict):
|
|
42
|
-
return input_dict
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def raise_error():
|
|
46
|
-
raise RuntimeError
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class TestExecutorWithDependencies(unittest.TestCase):
|
|
50
|
-
def test_executor(self):
|
|
51
|
-
with SingleNodeExecutor(max_cores=1) as exe:
|
|
52
|
-
cloudpickle_register(ind=1)
|
|
53
|
-
future_1 = exe.submit(add_function, 1, parameter_2=2)
|
|
54
|
-
future_2 = exe.submit(add_function, 1, parameter_2=future_1)
|
|
55
|
-
self.assertEqual(future_2.result(), 4)
|
|
56
|
-
|
|
57
|
-
def test_dependency_steps(self):
|
|
58
|
-
cloudpickle_register(ind=1)
|
|
59
|
-
fs1 = Future()
|
|
60
|
-
fs2 = Future()
|
|
61
|
-
q = Queue()
|
|
62
|
-
q.put(
|
|
63
|
-
{
|
|
64
|
-
"fn": add_function,
|
|
65
|
-
"args": (),
|
|
66
|
-
"kwargs": {"parameter_1": 1, "parameter_2": 2},
|
|
67
|
-
"future": fs1,
|
|
68
|
-
"resource_dict": {"cores": 1},
|
|
69
|
-
}
|
|
70
|
-
)
|
|
71
|
-
q.put(
|
|
72
|
-
{
|
|
73
|
-
"fn": add_function,
|
|
74
|
-
"args": (),
|
|
75
|
-
"kwargs": {"parameter_1": 1, "parameter_2": fs1},
|
|
76
|
-
"future": fs2,
|
|
77
|
-
"resource_dict": {"cores": 1},
|
|
78
|
-
}
|
|
79
|
-
)
|
|
80
|
-
executor = create_single_node_executor(
|
|
81
|
-
max_workers=1,
|
|
82
|
-
max_cores=2,
|
|
83
|
-
resource_dict={
|
|
84
|
-
"cores": 1,
|
|
85
|
-
"threads_per_core": 1,
|
|
86
|
-
"gpus_per_core": 0,
|
|
87
|
-
"cwd": None,
|
|
88
|
-
"openmpi_oversubscribe": False,
|
|
89
|
-
"slurm_cmd_args": [],
|
|
90
|
-
},
|
|
91
|
-
)
|
|
92
|
-
process = RaisingThread(
|
|
93
|
-
target=execute_tasks_with_dependencies,
|
|
94
|
-
kwargs={
|
|
95
|
-
"future_queue": q,
|
|
96
|
-
"executor_queue": executor._future_queue,
|
|
97
|
-
"executor": executor,
|
|
98
|
-
"refresh_rate": 0.01,
|
|
99
|
-
},
|
|
100
|
-
)
|
|
101
|
-
process.start()
|
|
102
|
-
self.assertFalse(fs1.done())
|
|
103
|
-
self.assertFalse(fs2.done())
|
|
104
|
-
self.assertEqual(fs2.result(), 4)
|
|
105
|
-
self.assertTrue(fs1.done())
|
|
106
|
-
self.assertTrue(fs2.done())
|
|
107
|
-
q.put({"shutdown": True, "wait": True})
|
|
108
|
-
|
|
109
|
-
def test_many_to_one(self):
|
|
110
|
-
length = 5
|
|
111
|
-
parameter = 1
|
|
112
|
-
with SingleNodeExecutor(max_cores=2) as exe:
|
|
113
|
-
cloudpickle_register(ind=1)
|
|
114
|
-
future_lst = exe.submit(
|
|
115
|
-
generate_tasks,
|
|
116
|
-
length=length,
|
|
117
|
-
resource_dict={"cores": 1},
|
|
118
|
-
)
|
|
119
|
-
lst = []
|
|
120
|
-
for i in range(length):
|
|
121
|
-
lst.append(
|
|
122
|
-
exe.submit(
|
|
123
|
-
calc_from_lst,
|
|
124
|
-
lst=future_lst,
|
|
125
|
-
ind=i,
|
|
126
|
-
parameter=parameter,
|
|
127
|
-
resource_dict={"cores": 1},
|
|
128
|
-
)
|
|
129
|
-
)
|
|
130
|
-
future_sum = exe.submit(
|
|
131
|
-
merge,
|
|
132
|
-
lst=lst,
|
|
133
|
-
resource_dict={"cores": 1},
|
|
134
|
-
)
|
|
135
|
-
self.assertEqual(future_sum.result(), 15)
|
|
136
|
-
|
|
137
|
-
def test_future_input_dict(self):
|
|
138
|
-
with SingleNodeExecutor() as exe:
|
|
139
|
-
fs = exe.submit(
|
|
140
|
-
return_input_dict,
|
|
141
|
-
input_dict={"a": exe.submit(sum, [2, 2])},
|
|
142
|
-
)
|
|
143
|
-
self.assertEqual(fs.result()["a"], 4)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
class TestExecutorErrors(unittest.TestCase):
|
|
147
|
-
def test_block_allocation_false_one_worker(self):
|
|
148
|
-
with self.assertRaises(RuntimeError):
|
|
149
|
-
with SingleNodeExecutor(max_cores=1, block_allocation=False) as exe:
|
|
150
|
-
cloudpickle_register(ind=1)
|
|
151
|
-
_ = exe.submit(raise_error)
|
|
152
|
-
|
|
153
|
-
def test_block_allocation_true_one_worker(self):
|
|
154
|
-
with self.assertRaises(RuntimeError):
|
|
155
|
-
with SingleNodeExecutor(max_cores=1, block_allocation=True) as exe:
|
|
156
|
-
cloudpickle_register(ind=1)
|
|
157
|
-
_ = exe.submit(raise_error)
|
|
158
|
-
|
|
159
|
-
def test_block_allocation_false_two_workers(self):
|
|
160
|
-
with self.assertRaises(RuntimeError):
|
|
161
|
-
with SingleNodeExecutor(max_cores=2, block_allocation=False) as exe:
|
|
162
|
-
cloudpickle_register(ind=1)
|
|
163
|
-
_ = exe.submit(raise_error)
|
|
164
|
-
|
|
165
|
-
def test_block_allocation_true_two_workers(self):
|
|
166
|
-
with self.assertRaises(RuntimeError):
|
|
167
|
-
with SingleNodeExecutor(max_cores=2, block_allocation=True) as exe:
|
|
168
|
-
cloudpickle_register(ind=1)
|
|
169
|
-
_ = exe.submit(raise_error)
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
|
|
3
|
-
from executorlib.standalone.thread import RaisingThread
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def raise_error():
|
|
7
|
-
raise ValueError
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class TestRaisingThread(unittest.TestCase):
|
|
11
|
-
def test_raising_thread(self):
|
|
12
|
-
with self.assertRaises(ValueError):
|
|
13
|
-
process = RaisingThread(target=raise_error)
|
|
14
|
-
process.start()
|
|
15
|
-
process.join()
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|