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