Skip to content

Commit 7fdbe1c

Browse files
committed
Add duplicate to file based executors
1 parent 501f340 commit 7fdbe1c

2 files changed

Lines changed: 35 additions & 2 deletions

File tree

src/executorlib/task_scheduler/file/shared.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def execute_tasks_h5(
9191
process_dict: dict = {}
9292
cache_dir_dict: dict = {}
9393
file_name_dict: dict = {}
94+
duplicate_dict: dict = {}
9495
while True:
9596
task_dict = None
9697
with contextlib.suppress(queue.Empty):
@@ -101,6 +102,7 @@ def execute_tasks_h5(
101102
cancel_futures=task_dict.get("cancel_futures", False),
102103
memory_dict=memory_dict,
103104
process_dict=process_dict,
105+
duplicate_dict=duplicate_dict,
104106
cache_dir_dict=cache_dir_dict,
105107
terminate_function=terminate_function,
106108
pysqa_config_directory=pysqa_config_directory,
@@ -175,12 +177,17 @@ def execute_tasks_h5(
175177
process_dict[task_key] = queue_id
176178
memory_dict[task_key] = task_dict["future"]
177179
cache_dir_dict[task_key] = cache_directory
180+
elif memory_dict[task_key] != task_dict["future"]:
181+
if task_key not in duplicate_dict:
182+
duplicate_dict[task_key] = []
183+
duplicate_dict[task_key].append(task_dict["future"])
178184
future_queue.task_done()
179185
else:
180186
memory_dict = _refresh_memory_dict(
181187
memory_dict=memory_dict,
182188
cache_dir_dict=cache_dir_dict,
183189
process_dict=process_dict,
190+
duplicate_dict=duplicate_dict,
184191
terminate_function=terminate_function,
185192
pysqa_config_directory=pysqa_config_directory,
186193
backend=backend,
@@ -189,7 +196,7 @@ def execute_tasks_h5(
189196

190197

191198
def _check_task_output(
192-
task_key: str, future_obj: Future, cache_directory: str
199+
task_key: str, future_obj: Future, cache_directory: str, duplicate_dict: dict
193200
) -> Future:
194201
"""
195202
Check the output of a task and set the result of the future object if available.
@@ -198,7 +205,7 @@ def _check_task_output(
198205
task_key (str): The key of the task.
199206
future_obj (Future): The future object associated with the task.
200207
cache_directory (str): The directory where the HDF5 files are stored.
201-
208+
duplicate_dict (dict): The dictionary mapping task keys to their associated duplicate future objects.
202209
Returns:
203210
Future: The updated future object.
204211
@@ -211,6 +218,13 @@ def _check_task_output(
211218
future_obj.set_result(result)
212219
elif exec_flag:
213220
future_obj.set_exception(result)
221+
if task_key in duplicate_dict:
222+
for duplicate_future in duplicate_dict[task_key]:
223+
if exec_flag and no_error_flag:
224+
duplicate_future.set_result(result)
225+
elif exec_flag:
226+
duplicate_future.set_exception(result)
227+
del duplicate_dict[task_key]
214228
return future_obj
215229

216230

@@ -281,6 +295,7 @@ def _refresh_memory_dict(
281295
memory_dict: dict,
282296
cache_dir_dict: dict,
283297
process_dict: dict,
298+
duplicate_dict: dict,
284299
terminate_function: Optional[Callable] = None,
285300
pysqa_config_directory: Optional[str] = None,
286301
backend: Optional[str] = None,
@@ -293,6 +308,7 @@ def _refresh_memory_dict(
293308
memory_dict (dict): dictionary with task keys and future objects
294309
cache_dir_dict (dict): dictionary with task keys and cache directories
295310
process_dict (dict): dictionary with task keys and process reference.
311+
duplicate_dict (dict): dictionary with task keys and duplicate future objects.
296312
terminate_function (callable): The function to terminate the tasks.
297313
pysqa_config_directory (str): path to the pysqa config directory (only for pysqa based backend).
298314
backend (str): name of the backend used to spawn tasks.
@@ -315,6 +331,7 @@ def _refresh_memory_dict(
315331
task_key=key,
316332
future_obj=value,
317333
cache_directory=cache_dir_dict[key],
334+
duplicate_dict=duplicate_dict,
318335
)
319336
for key, value in memory_dict.items()
320337
if not value.done()
@@ -399,6 +416,7 @@ def _shutdown_executor(
399416
cancel_futures: bool,
400417
memory_dict: dict,
401418
process_dict: dict,
419+
duplicate_dict: dict,
402420
cache_dir_dict: dict,
403421
terminate_function: Optional[Callable] = None,
404422
pysqa_config_directory: Optional[str] = None,
@@ -421,6 +439,7 @@ def _shutdown_executor(
421439
cancel_futures (bool): Whether to cancel futures that have not yet started.
422440
memory_dict (dict): Mapping of task keys to their Future objects.
423441
process_dict (dict): Mapping of task keys to process handles or queue IDs.
442+
duplicate_dict (dict): Mapping of task keys to lists of duplicate Future objects.
424443
cache_dir_dict (dict): Mapping of task keys to the cache directory for each task.
425444
terminate_function (Callable, optional): Function used to terminate running processes.
426445
pysqa_config_directory (str, optional): Path to the pysqa config directory.
@@ -433,6 +452,7 @@ def _shutdown_executor(
433452
memory_dict=memory_dict,
434453
cache_dir_dict=cache_dir_dict,
435454
process_dict=process_dict,
455+
duplicate_dict=duplicate_dict,
436456
terminate_function=terminate_function,
437457
pysqa_config_directory=pysqa_config_directory,
438458
backend=backend,
@@ -447,6 +467,7 @@ def _shutdown_executor(
447467
memory_dict=memory_dict,
448468
cache_dir_dict=cache_dir_dict,
449469
process_dict=process_dict,
470+
duplicate_dict=duplicate_dict,
450471
terminate_function=terminate_function,
451472
pysqa_config_directory=pysqa_config_directory,
452473
backend=backend,
@@ -465,6 +486,7 @@ def _shutdown_executor(
465486
memory_dict=memory_dict,
466487
cache_dir_dict=cache_dir_dict,
467488
process_dict=process_dict,
489+
duplicate_dict=duplicate_dict,
468490
terminate_function=terminate_function,
469491
pysqa_config_directory=pysqa_config_directory,
470492
backend=backend,

tests/unit/executor/test_api.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,17 @@ def test_executor_dependency_plot(self):
156156
self.assertEqual(len(nodes), 4)
157157
self.assertEqual(len(edges), 4)
158158

159+
def test_duplicate_futures(self):
160+
with TestClusterExecutor(cache_directory="cache_dir") as exe:
161+
cloudpickle_register(ind=1)
162+
future_1 = exe.submit(add_with_sleep, 1, parameter_2=2)
163+
future_2 = exe.submit(add_with_sleep, 1, parameter_2=2)
164+
self.assertFalse(future_1.done())
165+
self.assertFalse(future_2.done())
166+
self.assertEqual(future_1.result(), 3)
167+
self.assertEqual(future_2.result(), 3)
168+
self.assertEqual(len(os.listdir("cache_dir")), 1)
169+
159170
def test_shutdown_wait_false_cancel_futures_false(self):
160171
exe = TestClusterExecutor(cache_directory="shutdown_1_dir")
161172
cloudpickle_register(ind=1)

0 commit comments

Comments
 (0)