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.
Files changed (74) hide show
  1. {executorlib-0.2.0/executorlib.egg-info → executorlib-0.3.0}/PKG-INFO +1 -1
  2. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/_version.py +3 -3
  3. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/base/executor.py +6 -6
  4. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/cache/executor.py +2 -2
  5. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/cache/shared.py +4 -2
  6. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interactive/executor.py +2 -2
  7. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interactive/shared.py +37 -18
  8. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/__init__.py +0 -2
  9. {executorlib-0.2.0 → executorlib-0.3.0/executorlib.egg-info}/PKG-INFO +1 -1
  10. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib.egg-info/SOURCES.txt +0 -2
  11. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_cache_executor_serial.py +6 -5
  12. executorlib-0.3.0/tests/test_dependencies_executor.py +330 -0
  13. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_local_executor.py +6 -1
  14. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_shell_executor.py +11 -0
  15. executorlib-0.2.0/executorlib/standalone/thread.py +0 -42
  16. executorlib-0.2.0/tests/test_dependencies_executor.py +0 -169
  17. executorlib-0.2.0/tests/test_shared_thread.py +0 -15
  18. {executorlib-0.2.0 → executorlib-0.3.0}/LICENSE +0 -0
  19. {executorlib-0.2.0 → executorlib-0.3.0}/MANIFEST.in +0 -0
  20. {executorlib-0.2.0 → executorlib-0.3.0}/README.md +0 -0
  21. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/__init__.py +0 -0
  22. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/backend/__init__.py +0 -0
  23. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/backend/cache_parallel.py +0 -0
  24. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/backend/cache_serial.py +0 -0
  25. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/backend/interactive_parallel.py +0 -0
  26. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/backend/interactive_serial.py +0 -0
  27. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/base/__init__.py +0 -0
  28. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/cache/__init__.py +0 -0
  29. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/cache/backend.py +0 -0
  30. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/cache/queue_spawner.py +0 -0
  31. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/cache/subprocess_spawner.py +0 -0
  32. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interactive/__init__.py +0 -0
  33. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interactive/flux.py +0 -0
  34. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interactive/slurm.py +0 -0
  35. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interfaces/__init__.py +0 -0
  36. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interfaces/flux.py +0 -0
  37. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interfaces/single.py +0 -0
  38. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/interfaces/slurm.py +0 -0
  39. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/command.py +0 -0
  40. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/hdf.py +0 -0
  41. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/inputcheck.py +0 -0
  42. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/interactive/__init__.py +0 -0
  43. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/interactive/backend.py +0 -0
  44. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/interactive/communication.py +0 -0
  45. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/interactive/spawner.py +0 -0
  46. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/plot.py +0 -0
  47. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/queue.py +0 -0
  48. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib/standalone/serialize.py +0 -0
  49. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib.egg-info/dependency_links.txt +0 -0
  50. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib.egg-info/requires.txt +0 -0
  51. {executorlib-0.2.0 → executorlib-0.3.0}/executorlib.egg-info/top_level.txt +0 -0
  52. {executorlib-0.2.0 → executorlib-0.3.0}/pyproject.toml +0 -0
  53. {executorlib-0.2.0 → executorlib-0.3.0}/setup.cfg +0 -0
  54. {executorlib-0.2.0 → executorlib-0.3.0}/setup.py +0 -0
  55. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_backend_serial.py +0 -0
  56. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_cache_executor_interactive.py +0 -0
  57. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_cache_executor_mpi.py +0 -0
  58. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_cache_executor_pysqa_flux.py +0 -0
  59. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_cache_hdf.py +0 -0
  60. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_cache_shared.py +0 -0
  61. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_executor_backend_flux.py +0 -0
  62. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_executor_backend_mpi.py +0 -0
  63. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_executor_backend_mpi_noblock.py +0 -0
  64. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_flux_executor.py +0 -0
  65. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_integration_pyiron_workflow.py +0 -0
  66. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_local_executor_future.py +0 -0
  67. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_plot_dependency.py +0 -0
  68. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_plot_dependency_flux.py +0 -0
  69. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_pysqa_subprocess.py +0 -0
  70. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_shared_backend.py +0 -0
  71. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_shared_communication.py +0 -0
  72. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_shared_executorbase.py +0 -0
  73. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_shared_input_check.py +0 -0
  74. {executorlib-0.2.0 → executorlib-0.3.0}/tests/test_shell_interactive.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: executorlib
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Up-scale python functions for high performance computing (HPC) with executorlib.
5
5
  Author-email: Jan Janssen <janssen@lanl.gov>
6
6
  License: BSD 3-Clause License
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-02-11T12:47:28+0100",
11
+ "date": "2025-02-14T16:51:45+0100",
12
12
  "dirty": true,
13
13
  "error": null,
14
- "full-revisionid": "0ffd31288952f78be3b0c810eac1890759634d35",
15
- "version": "0.2.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[RaisingThread, list[RaisingThread]]] = None
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].get_kwargs().copy()
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.get_kwargs().copy()
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, RaisingThread):
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: RaisingThread):
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
- RaisingThread(
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
- raise ValueError(
119
- "Future objects are not supported as input if disable_dependencies=True."
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
- RaisingThread(
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 concurrent.futures import Future
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[RaisingThread]): # type: ignore
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
- RaisingThread(
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
- RaisingThread(
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
- if len(future_lst) == 0 or ready_flag:
365
- # No future objects are used in the input or all future objects are already done
366
- task_dict["args"], task_dict["kwargs"] = _update_futures_in_input(
367
- args=task_dict["args"], kwargs=task_dict["kwargs"]
368
- )
369
- executor_queue.put(task_dict)
370
- else: # Otherwise add the function to the wait list
371
- task_dict["future_lst"] = future_lst
372
- wait_lst.append(task_dict)
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
- if all(future.done() for future in task_wait_dict["future_lst"]):
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 = RaisingThread(
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
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: executorlib
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Up-scale python functions for high performance computing (HPC) with executorlib.
5
5
  Author-email: Jan Janssen <janssen@lanl.gov>
6
6
  License: BSD 3-Clause License
@@ -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 = RaisingThread(
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 = RaisingThread(
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 = RaisingThread(
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