parsl 2025.1.20__py3-none-any.whl → 2025.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- parsl/dataflow/dflow.py +6 -81
- parsl/dataflow/errors.py +60 -18
- parsl/dataflow/memoization.py +76 -2
- parsl/executors/high_throughput/executor.py +14 -11
- parsl/executors/high_throughput/interchange.py +74 -96
- parsl/tests/test_checkpointing/test_python_checkpoint_1.py +0 -3
- parsl/tests/test_python_apps/test_fail.py +6 -0
- parsl/tests/test_python_apps/test_memoize_1.py +1 -3
- parsl/version.py +1 -1
- {parsl-2025.1.20.data → parsl-2025.2.3.data}/scripts/interchange.py +74 -96
- {parsl-2025.1.20.dist-info → parsl-2025.2.3.dist-info}/METADATA +2 -2
- {parsl-2025.1.20.dist-info → parsl-2025.2.3.dist-info}/RECORD +19 -20
- parsl/tests/test_checkpointing/test_python_checkpoint_3.py +0 -42
- {parsl-2025.1.20.data → parsl-2025.2.3.data}/scripts/exec_parsl_function.py +0 -0
- {parsl-2025.1.20.data → parsl-2025.2.3.data}/scripts/parsl_coprocess.py +0 -0
- {parsl-2025.1.20.data → parsl-2025.2.3.data}/scripts/process_worker_pool.py +0 -0
- {parsl-2025.1.20.dist-info → parsl-2025.2.3.dist-info}/LICENSE +0 -0
- {parsl-2025.1.20.dist-info → parsl-2025.2.3.dist-info}/WHEEL +0 -0
- {parsl-2025.1.20.dist-info → parsl-2025.2.3.dist-info}/entry_points.txt +0 -0
- {parsl-2025.1.20.dist-info → parsl-2025.2.3.dist-info}/top_level.txt +0 -0
parsl/dataflow/dflow.py
CHANGED
@@ -28,7 +28,7 @@ from parsl.config import Config
|
|
28
28
|
from parsl.data_provider.data_manager import DataManager
|
29
29
|
from parsl.data_provider.files import File
|
30
30
|
from parsl.dataflow.dependency_resolvers import SHALLOW_DEPENDENCY_RESOLVER
|
31
|
-
from parsl.dataflow.errors import
|
31
|
+
from parsl.dataflow.errors import DependencyError, JoinError
|
32
32
|
from parsl.dataflow.futures import AppFuture
|
33
33
|
from parsl.dataflow.memoization import Memoizer
|
34
34
|
from parsl.dataflow.rundirs import make_rundir
|
@@ -161,13 +161,13 @@ class DataFlowKernel:
|
|
161
161
|
workflow_info))
|
162
162
|
|
163
163
|
if config.checkpoint_files is not None:
|
164
|
-
|
164
|
+
checkpoint_files = config.checkpoint_files
|
165
165
|
elif config.checkpoint_files is None and config.checkpoint_mode is not None:
|
166
|
-
|
166
|
+
checkpoint_files = get_all_checkpoints(self.run_dir)
|
167
167
|
else:
|
168
|
-
|
168
|
+
checkpoint_files = []
|
169
169
|
|
170
|
-
self.memoizer = Memoizer(self, memoize=config.app_cache,
|
170
|
+
self.memoizer = Memoizer(self, memoize=config.app_cache, checkpoint_files=checkpoint_files)
|
171
171
|
self.checkpointed_tasks = 0
|
172
172
|
self._checkpoint_timer = None
|
173
173
|
self.checkpoint_mode = config.checkpoint_mode
|
@@ -1263,7 +1263,7 @@ class DataFlowKernel:
|
|
1263
1263
|
Returns:
|
1264
1264
|
Checkpoint dir if checkpoints were written successfully.
|
1265
1265
|
By default the checkpoints are written to the RUNDIR of the current
|
1266
|
-
run under RUNDIR/checkpoints/
|
1266
|
+
run under RUNDIR/checkpoints/tasks.pkl
|
1267
1267
|
"""
|
1268
1268
|
with self.checkpoint_lock:
|
1269
1269
|
if tasks:
|
@@ -1273,18 +1273,11 @@ class DataFlowKernel:
|
|
1273
1273
|
self.checkpointable_tasks = []
|
1274
1274
|
|
1275
1275
|
checkpoint_dir = '{0}/checkpoint'.format(self.run_dir)
|
1276
|
-
checkpoint_dfk = checkpoint_dir + '/dfk.pkl'
|
1277
1276
|
checkpoint_tasks = checkpoint_dir + '/tasks.pkl'
|
1278
1277
|
|
1279
1278
|
if not os.path.exists(checkpoint_dir):
|
1280
1279
|
os.makedirs(checkpoint_dir, exist_ok=True)
|
1281
1280
|
|
1282
|
-
with open(checkpoint_dfk, 'wb') as f:
|
1283
|
-
state = {'rundir': self.run_dir,
|
1284
|
-
'task_count': self.task_count
|
1285
|
-
}
|
1286
|
-
pickle.dump(state, f)
|
1287
|
-
|
1288
1281
|
count = 0
|
1289
1282
|
|
1290
1283
|
with open(checkpoint_tasks, 'ab') as f:
|
@@ -1317,74 +1310,6 @@ class DataFlowKernel:
|
|
1317
1310
|
|
1318
1311
|
return checkpoint_dir
|
1319
1312
|
|
1320
|
-
def _load_checkpoints(self, checkpointDirs: Sequence[str]) -> Dict[str, Future[Any]]:
|
1321
|
-
"""Load a checkpoint file into a lookup table.
|
1322
|
-
|
1323
|
-
The data being loaded from the pickle file mostly contains input
|
1324
|
-
attributes of the task: func, args, kwargs, env...
|
1325
|
-
To simplify the check of whether the exact task has been completed
|
1326
|
-
in the checkpoint, we hash these input params and use it as the key
|
1327
|
-
for the memoized lookup table.
|
1328
|
-
|
1329
|
-
Args:
|
1330
|
-
- checkpointDirs (list) : List of filepaths to checkpoints
|
1331
|
-
Eg. ['runinfo/001', 'runinfo/002']
|
1332
|
-
|
1333
|
-
Returns:
|
1334
|
-
- memoized_lookup_table (dict)
|
1335
|
-
"""
|
1336
|
-
memo_lookup_table = {}
|
1337
|
-
|
1338
|
-
for checkpoint_dir in checkpointDirs:
|
1339
|
-
logger.info("Loading checkpoints from {}".format(checkpoint_dir))
|
1340
|
-
checkpoint_file = os.path.join(checkpoint_dir, 'tasks.pkl')
|
1341
|
-
try:
|
1342
|
-
with open(checkpoint_file, 'rb') as f:
|
1343
|
-
while True:
|
1344
|
-
try:
|
1345
|
-
data = pickle.load(f)
|
1346
|
-
# Copy and hash only the input attributes
|
1347
|
-
memo_fu: Future = Future()
|
1348
|
-
assert data['exception'] is None
|
1349
|
-
memo_fu.set_result(data['result'])
|
1350
|
-
memo_lookup_table[data['hash']] = memo_fu
|
1351
|
-
|
1352
|
-
except EOFError:
|
1353
|
-
# Done with the checkpoint file
|
1354
|
-
break
|
1355
|
-
except FileNotFoundError:
|
1356
|
-
reason = "Checkpoint file was not found: {}".format(
|
1357
|
-
checkpoint_file)
|
1358
|
-
logger.error(reason)
|
1359
|
-
raise BadCheckpoint(reason)
|
1360
|
-
except Exception:
|
1361
|
-
reason = "Failed to load checkpoint: {}".format(
|
1362
|
-
checkpoint_file)
|
1363
|
-
logger.error(reason)
|
1364
|
-
raise BadCheckpoint(reason)
|
1365
|
-
|
1366
|
-
logger.info("Completed loading checkpoint: {0} with {1} tasks".format(checkpoint_file,
|
1367
|
-
len(memo_lookup_table.keys())))
|
1368
|
-
return memo_lookup_table
|
1369
|
-
|
1370
|
-
@typeguard.typechecked
|
1371
|
-
def load_checkpoints(self, checkpointDirs: Optional[Sequence[str]]) -> Dict[str, Future]:
|
1372
|
-
"""Load checkpoints from the checkpoint files into a dictionary.
|
1373
|
-
|
1374
|
-
The results are used to pre-populate the memoizer's lookup_table
|
1375
|
-
|
1376
|
-
Kwargs:
|
1377
|
-
- checkpointDirs (list) : List of run folder to use as checkpoints
|
1378
|
-
Eg. ['runinfo/001', 'runinfo/002']
|
1379
|
-
|
1380
|
-
Returns:
|
1381
|
-
- dict containing, hashed -> future mappings
|
1382
|
-
"""
|
1383
|
-
if checkpointDirs:
|
1384
|
-
return self._load_checkpoints(checkpointDirs)
|
1385
|
-
else:
|
1386
|
-
return {}
|
1387
|
-
|
1388
1313
|
@staticmethod
|
1389
1314
|
def _log_std_streams(task_record: TaskRecord) -> None:
|
1390
1315
|
tid = task_record['id']
|
parsl/dataflow/errors.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import List, Sequence, Tuple
|
2
2
|
|
3
3
|
from parsl.errors import ParslError
|
4
4
|
|
@@ -29,35 +29,77 @@ class BadCheckpoint(DataFlowException):
|
|
29
29
|
return self.reason
|
30
30
|
|
31
31
|
|
32
|
-
class
|
33
|
-
"""Error raised if an app
|
34
|
-
in a
|
32
|
+
class PropagatedException(DataFlowException):
|
33
|
+
"""Error raised if an app fails because there was an error
|
34
|
+
in a related task. This is intended to be subclassed for
|
35
|
+
dependency and join_app errors.
|
35
36
|
|
36
37
|
Args:
|
37
|
-
- dependent_exceptions_tids: List of exceptions and
|
38
|
-
dependencies which failed. The
|
39
|
-
the repr of a non-
|
38
|
+
- dependent_exceptions_tids: List of exceptions and brief descriptions
|
39
|
+
for dependencies which failed. The description might be a task ID or
|
40
|
+
the repr of a non-AppFuture.
|
40
41
|
- task_id: Task ID of the task that failed because of the dependency error
|
41
42
|
"""
|
42
43
|
|
43
|
-
def __init__(self,
|
44
|
+
def __init__(self,
|
45
|
+
dependent_exceptions_tids: Sequence[Tuple[BaseException, str]],
|
46
|
+
task_id: int,
|
47
|
+
*,
|
48
|
+
failure_description: str) -> None:
|
44
49
|
self.dependent_exceptions_tids = dependent_exceptions_tids
|
45
50
|
self.task_id = task_id
|
51
|
+
self._failure_description = failure_description
|
52
|
+
|
53
|
+
(cause, cause_sequence) = self._find_any_root_cause()
|
54
|
+
self.__cause__ = cause
|
55
|
+
self._cause_sequence = cause_sequence
|
46
56
|
|
47
57
|
def __str__(self) -> str:
|
48
|
-
|
49
|
-
return f"
|
58
|
+
sequence_text = " <- ".join(self._cause_sequence)
|
59
|
+
return f"{self._failure_description} for task {self.task_id}. " \
|
60
|
+
f"The representative cause is via {sequence_text}"
|
61
|
+
|
62
|
+
def _find_any_root_cause(self) -> Tuple[BaseException, List[str]]:
|
63
|
+
"""Looks recursively through self.dependent_exceptions_tids to find
|
64
|
+
an exception that caused this propagated error, that is not itself
|
65
|
+
a propagated error.
|
66
|
+
"""
|
67
|
+
e: BaseException = self
|
68
|
+
dep_ids = []
|
69
|
+
while isinstance(e, PropagatedException) and len(e.dependent_exceptions_tids) >= 1:
|
70
|
+
id_txt = e.dependent_exceptions_tids[0][1]
|
71
|
+
assert isinstance(id_txt, str)
|
72
|
+
# if there are several causes for this exception, label that
|
73
|
+
# there are more so that we know that the representative fail
|
74
|
+
# sequence is not the full story.
|
75
|
+
if len(e.dependent_exceptions_tids) > 1:
|
76
|
+
id_txt += " (+ others)"
|
77
|
+
dep_ids.append(id_txt)
|
78
|
+
e = e.dependent_exceptions_tids[0][0]
|
79
|
+
return e, dep_ids
|
80
|
+
|
81
|
+
|
82
|
+
class DependencyError(PropagatedException):
|
83
|
+
"""Error raised if an app cannot run because there was an error
|
84
|
+
in a dependency. There can be several exceptions (one from each
|
85
|
+
dependency) and DependencyError collects them all together.
|
50
86
|
|
87
|
+
Args:
|
88
|
+
- dependent_exceptions_tids: List of exceptions and brief descriptions
|
89
|
+
for dependencies which failed. The description might be a task ID or
|
90
|
+
the repr of a non-AppFuture.
|
91
|
+
- task_id: Task ID of the task that failed because of the dependency error
|
92
|
+
"""
|
93
|
+
def __init__(self, dependent_exceptions_tids: Sequence[Tuple[BaseException, str]], task_id: int) -> None:
|
94
|
+
super().__init__(dependent_exceptions_tids, task_id,
|
95
|
+
failure_description="Dependency failure")
|
51
96
|
|
52
|
-
|
97
|
+
|
98
|
+
class JoinError(PropagatedException):
|
53
99
|
"""Error raised if apps joining into a join_app raise exceptions.
|
54
100
|
There can be several exceptions (one from each joining app),
|
55
101
|
and JoinError collects them all together.
|
56
102
|
"""
|
57
|
-
def __init__(self, dependent_exceptions_tids: Sequence[Tuple[BaseException,
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
def __str__(self) -> str:
|
62
|
-
dep_tids = [tid for (exception, tid) in self.dependent_exceptions_tids]
|
63
|
-
return "Join failure for task {} with failed join dependencies from tasks {}".format(self.task_id, dep_tids)
|
103
|
+
def __init__(self, dependent_exceptions_tids: Sequence[Tuple[BaseException, str]], task_id: int) -> None:
|
104
|
+
super().__init__(dependent_exceptions_tids, task_id,
|
105
|
+
failure_description="Join failure")
|
parsl/dataflow/memoization.py
CHANGED
@@ -2,10 +2,14 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import hashlib
|
4
4
|
import logging
|
5
|
+
import os
|
5
6
|
import pickle
|
6
7
|
from functools import lru_cache, singledispatch
|
7
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
8
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence
|
8
9
|
|
10
|
+
import typeguard
|
11
|
+
|
12
|
+
from parsl.dataflow.errors import BadCheckpoint
|
9
13
|
from parsl.dataflow.taskrecord import TaskRecord
|
10
14
|
|
11
15
|
if TYPE_CHECKING:
|
@@ -146,7 +150,7 @@ class Memoizer:
|
|
146
150
|
|
147
151
|
"""
|
148
152
|
|
149
|
-
def __init__(self, dfk: DataFlowKernel, memoize: bool = True,
|
153
|
+
def __init__(self, dfk: DataFlowKernel, *, memoize: bool = True, checkpoint_files: Sequence[str]):
|
150
154
|
"""Initialize the memoizer.
|
151
155
|
|
152
156
|
Args:
|
@@ -159,6 +163,8 @@ class Memoizer:
|
|
159
163
|
self.dfk = dfk
|
160
164
|
self.memoize = memoize
|
161
165
|
|
166
|
+
checkpoint = self.load_checkpoints(checkpoint_files)
|
167
|
+
|
162
168
|
if self.memoize:
|
163
169
|
logger.info("App caching initialized")
|
164
170
|
self.memo_lookup_table = checkpoint
|
@@ -274,3 +280,71 @@ class Memoizer:
|
|
274
280
|
else:
|
275
281
|
logger.debug(f"Storing app cache entry {task['hashsum']} with result from task {task_id}")
|
276
282
|
self.memo_lookup_table[task['hashsum']] = r
|
283
|
+
|
284
|
+
def _load_checkpoints(self, checkpointDirs: Sequence[str]) -> Dict[str, Future[Any]]:
|
285
|
+
"""Load a checkpoint file into a lookup table.
|
286
|
+
|
287
|
+
The data being loaded from the pickle file mostly contains input
|
288
|
+
attributes of the task: func, args, kwargs, env...
|
289
|
+
To simplify the check of whether the exact task has been completed
|
290
|
+
in the checkpoint, we hash these input params and use it as the key
|
291
|
+
for the memoized lookup table.
|
292
|
+
|
293
|
+
Args:
|
294
|
+
- checkpointDirs (list) : List of filepaths to checkpoints
|
295
|
+
Eg. ['runinfo/001', 'runinfo/002']
|
296
|
+
|
297
|
+
Returns:
|
298
|
+
- memoized_lookup_table (dict)
|
299
|
+
"""
|
300
|
+
memo_lookup_table = {}
|
301
|
+
|
302
|
+
for checkpoint_dir in checkpointDirs:
|
303
|
+
logger.info("Loading checkpoints from {}".format(checkpoint_dir))
|
304
|
+
checkpoint_file = os.path.join(checkpoint_dir, 'tasks.pkl')
|
305
|
+
try:
|
306
|
+
with open(checkpoint_file, 'rb') as f:
|
307
|
+
while True:
|
308
|
+
try:
|
309
|
+
data = pickle.load(f)
|
310
|
+
# Copy and hash only the input attributes
|
311
|
+
memo_fu: Future = Future()
|
312
|
+
assert data['exception'] is None
|
313
|
+
memo_fu.set_result(data['result'])
|
314
|
+
memo_lookup_table[data['hash']] = memo_fu
|
315
|
+
|
316
|
+
except EOFError:
|
317
|
+
# Done with the checkpoint file
|
318
|
+
break
|
319
|
+
except FileNotFoundError:
|
320
|
+
reason = "Checkpoint file was not found: {}".format(
|
321
|
+
checkpoint_file)
|
322
|
+
logger.error(reason)
|
323
|
+
raise BadCheckpoint(reason)
|
324
|
+
except Exception:
|
325
|
+
reason = "Failed to load checkpoint: {}".format(
|
326
|
+
checkpoint_file)
|
327
|
+
logger.error(reason)
|
328
|
+
raise BadCheckpoint(reason)
|
329
|
+
|
330
|
+
logger.info("Completed loading checkpoint: {0} with {1} tasks".format(checkpoint_file,
|
331
|
+
len(memo_lookup_table.keys())))
|
332
|
+
return memo_lookup_table
|
333
|
+
|
334
|
+
@typeguard.typechecked
|
335
|
+
def load_checkpoints(self, checkpointDirs: Optional[Sequence[str]]) -> Dict[str, Future]:
|
336
|
+
"""Load checkpoints from the checkpoint files into a dictionary.
|
337
|
+
|
338
|
+
The results are used to pre-populate the memoizer's lookup_table
|
339
|
+
|
340
|
+
Kwargs:
|
341
|
+
- checkpointDirs (list) : List of run folder to use as checkpoints
|
342
|
+
Eg. ['runinfo/001', 'runinfo/002']
|
343
|
+
|
344
|
+
Returns:
|
345
|
+
- dict containing, hashed -> future mappings
|
346
|
+
"""
|
347
|
+
if checkpointDirs:
|
348
|
+
return self._load_checkpoints(checkpointDirs)
|
349
|
+
else:
|
350
|
+
return {}
|
@@ -431,8 +431,6 @@ class HighThroughputExecutor(BlockProviderExecutor, RepresentationMixin, UsageIn
|
|
431
431
|
self._start_result_queue_thread()
|
432
432
|
self._start_local_interchange_process()
|
433
433
|
|
434
|
-
logger.debug("Created result queue thread: %s", self._result_queue_thread)
|
435
|
-
|
436
434
|
self.initialize_scaling()
|
437
435
|
|
438
436
|
@wrap_with_logs
|
@@ -529,6 +527,8 @@ class HighThroughputExecutor(BlockProviderExecutor, RepresentationMixin, UsageIn
|
|
529
527
|
get the worker task and result ports that the interchange has bound to.
|
530
528
|
"""
|
531
529
|
|
530
|
+
assert self.interchange_proc is None, f"Already exists! {self.interchange_proc!r}"
|
531
|
+
|
532
532
|
interchange_config = {"client_address": self.loopback_address,
|
533
533
|
"client_ports": (self.outgoing_q.port,
|
534
534
|
self.incoming_q.port,
|
@@ -563,7 +563,12 @@ class HighThroughputExecutor(BlockProviderExecutor, RepresentationMixin, UsageIn
|
|
563
563
|
except CommandClientTimeoutError:
|
564
564
|
logger.error("Interchange has not completed initialization. Aborting")
|
565
565
|
raise Exception("Interchange failed to start")
|
566
|
-
logger.debug(
|
566
|
+
logger.debug(
|
567
|
+
"Interchange process started (%r). Worker ports: %d, %d",
|
568
|
+
self.interchange_proc,
|
569
|
+
self.worker_task_port,
|
570
|
+
self.worker_result_port
|
571
|
+
)
|
567
572
|
|
568
573
|
def _start_result_queue_thread(self):
|
569
574
|
"""Method to start the result queue thread as a daemon.
|
@@ -571,15 +576,13 @@ class HighThroughputExecutor(BlockProviderExecutor, RepresentationMixin, UsageIn
|
|
571
576
|
Checks if a thread already exists, then starts it.
|
572
577
|
Could be used later as a restart if the result queue thread dies.
|
573
578
|
"""
|
574
|
-
|
575
|
-
logger.debug("Starting result queue thread")
|
576
|
-
self._result_queue_thread = threading.Thread(target=self._result_queue_worker, name="HTEX-Result-Queue-Thread")
|
577
|
-
self._result_queue_thread.daemon = True
|
578
|
-
self._result_queue_thread.start()
|
579
|
-
logger.debug("Started result queue thread")
|
579
|
+
assert self._result_queue_thread is None, f"Already exists! {self._result_queue_thread!r}"
|
580
580
|
|
581
|
-
|
582
|
-
|
581
|
+
logger.debug("Starting result queue thread")
|
582
|
+
self._result_queue_thread = threading.Thread(target=self._result_queue_worker, name="HTEX-Result-Queue-Thread")
|
583
|
+
self._result_queue_thread.daemon = True
|
584
|
+
self._result_queue_thread.start()
|
585
|
+
logger.debug("Started result queue thread: %r", self._result_queue_thread)
|
583
586
|
|
584
587
|
def hold_worker(self, worker_id: str) -> None:
|
585
588
|
"""Puts a worker on hold, preventing scheduling of additional tasks to it.
|
@@ -9,7 +9,7 @@ import queue
|
|
9
9
|
import sys
|
10
10
|
import threading
|
11
11
|
import time
|
12
|
-
from typing import Any, Dict, List,
|
12
|
+
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, cast
|
13
13
|
|
14
14
|
import zmq
|
15
15
|
|
@@ -132,6 +132,11 @@ class Interchange:
|
|
132
132
|
self.hub_zmq_port = hub_zmq_port
|
133
133
|
|
134
134
|
self.pending_task_queue: queue.Queue[Any] = queue.Queue(maxsize=10 ** 6)
|
135
|
+
|
136
|
+
# count of tasks that have been received from the submit side
|
137
|
+
self.task_counter = 0
|
138
|
+
|
139
|
+
# count of tasks that have been sent out to worker pools
|
135
140
|
self.count = 0
|
136
141
|
|
137
142
|
self.worker_ports = worker_ports
|
@@ -201,28 +206,6 @@ class Interchange:
|
|
201
206
|
|
202
207
|
return tasks
|
203
208
|
|
204
|
-
@wrap_with_logs(target="interchange")
|
205
|
-
def task_puller(self) -> NoReturn:
|
206
|
-
"""Pull tasks from the incoming tasks zmq pipe onto the internal
|
207
|
-
pending task queue
|
208
|
-
"""
|
209
|
-
logger.info("Starting")
|
210
|
-
task_counter = 0
|
211
|
-
|
212
|
-
while True:
|
213
|
-
logger.debug("launching recv_pyobj")
|
214
|
-
try:
|
215
|
-
msg = self.task_incoming.recv_pyobj()
|
216
|
-
except zmq.Again:
|
217
|
-
# We just timed out while attempting to receive
|
218
|
-
logger.debug("zmq.Again with {} tasks in internal queue".format(self.pending_task_queue.qsize()))
|
219
|
-
continue
|
220
|
-
|
221
|
-
logger.debug("putting message onto pending_task_queue")
|
222
|
-
self.pending_task_queue.put(msg)
|
223
|
-
task_counter += 1
|
224
|
-
logger.debug(f"Fetched {task_counter} tasks so far")
|
225
|
-
|
226
209
|
def _send_monitoring_info(self, monitoring_radio: Optional[MonitoringRadioSender], manager: ManagerRecord) -> None:
|
227
210
|
if monitoring_radio:
|
228
211
|
logger.info("Sending message {} to MonitoringHub".format(manager))
|
@@ -234,79 +217,68 @@ class Interchange:
|
|
234
217
|
|
235
218
|
monitoring_radio.send((MessageType.NODE_INFO, d))
|
236
219
|
|
237
|
-
|
238
|
-
def _command_server(self) -> NoReturn:
|
220
|
+
def process_command(self, monitoring_radio: Optional[MonitoringRadioSender]) -> None:
|
239
221
|
""" Command server to run async command to the interchange
|
240
222
|
"""
|
241
|
-
logger.debug("
|
242
|
-
|
243
|
-
if self.hub_address is not None and self.hub_zmq_port is not None:
|
244
|
-
logger.debug("Creating monitoring radio to %s:%s", self.hub_address, self.hub_zmq_port)
|
245
|
-
monitoring_radio = ZMQRadioSender(self.hub_address, self.hub_zmq_port)
|
246
|
-
else:
|
247
|
-
monitoring_radio = None
|
223
|
+
logger.debug("entering command_server section")
|
248
224
|
|
249
225
|
reply: Any # the type of reply depends on the command_req received (aka this needs dependent types...)
|
250
226
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
else:
|
272
|
-
idle_duration = 0.0
|
273
|
-
resp = {'manager': manager_id.decode('utf-8'),
|
274
|
-
'block_id': m['block_id'],
|
275
|
-
'worker_count': m['worker_count'],
|
276
|
-
'tasks': len(m['tasks']),
|
277
|
-
'idle_duration': idle_duration,
|
278
|
-
'active': m['active'],
|
279
|
-
'parsl_version': m['parsl_version'],
|
280
|
-
'python_version': m['python_version'],
|
281
|
-
'draining': m['draining']}
|
282
|
-
reply.append(resp)
|
283
|
-
|
284
|
-
elif command_req.startswith("HOLD_WORKER"):
|
285
|
-
cmd, s_manager = command_req.split(';')
|
286
|
-
manager_id = s_manager.encode('utf-8')
|
287
|
-
logger.info("Received HOLD_WORKER for {!r}".format(manager_id))
|
288
|
-
if manager_id in self._ready_managers:
|
289
|
-
m = self._ready_managers[manager_id]
|
290
|
-
m['active'] = False
|
291
|
-
self._send_monitoring_info(monitoring_radio, m)
|
227
|
+
if self.command_channel in self.socks and self.socks[self.command_channel] == zmq.POLLIN:
|
228
|
+
|
229
|
+
command_req = self.command_channel.recv_pyobj()
|
230
|
+
logger.debug("Received command request: {}".format(command_req))
|
231
|
+
if command_req == "CONNECTED_BLOCKS":
|
232
|
+
reply = self.connected_block_history
|
233
|
+
|
234
|
+
elif command_req == "WORKERS":
|
235
|
+
num_workers = 0
|
236
|
+
for manager in self._ready_managers.values():
|
237
|
+
num_workers += manager['worker_count']
|
238
|
+
reply = num_workers
|
239
|
+
|
240
|
+
elif command_req == "MANAGERS":
|
241
|
+
reply = []
|
242
|
+
for manager_id in self._ready_managers:
|
243
|
+
m = self._ready_managers[manager_id]
|
244
|
+
idle_since = m['idle_since']
|
245
|
+
if idle_since is not None:
|
246
|
+
idle_duration = time.time() - idle_since
|
292
247
|
else:
|
293
|
-
|
294
|
-
|
295
|
-
|
248
|
+
idle_duration = 0.0
|
249
|
+
resp = {'manager': manager_id.decode('utf-8'),
|
250
|
+
'block_id': m['block_id'],
|
251
|
+
'worker_count': m['worker_count'],
|
252
|
+
'tasks': len(m['tasks']),
|
253
|
+
'idle_duration': idle_duration,
|
254
|
+
'active': m['active'],
|
255
|
+
'parsl_version': m['parsl_version'],
|
256
|
+
'python_version': m['python_version'],
|
257
|
+
'draining': m['draining']}
|
258
|
+
reply.append(resp)
|
259
|
+
|
260
|
+
elif command_req.startswith("HOLD_WORKER"):
|
261
|
+
cmd, s_manager = command_req.split(';')
|
262
|
+
manager_id = s_manager.encode('utf-8')
|
263
|
+
logger.info("Received HOLD_WORKER for {!r}".format(manager_id))
|
264
|
+
if manager_id in self._ready_managers:
|
265
|
+
m = self._ready_managers[manager_id]
|
266
|
+
m['active'] = False
|
267
|
+
self._send_monitoring_info(monitoring_radio, m)
|
268
|
+
else:
|
269
|
+
logger.warning("Worker to hold was not in ready managers list")
|
296
270
|
|
297
|
-
|
298
|
-
reply = (self.worker_task_port, self.worker_result_port)
|
271
|
+
reply = None
|
299
272
|
|
300
|
-
|
301
|
-
|
302
|
-
reply = None
|
273
|
+
elif command_req == "WORKER_PORTS":
|
274
|
+
reply = (self.worker_task_port, self.worker_result_port)
|
303
275
|
|
304
|
-
|
305
|
-
|
276
|
+
else:
|
277
|
+
logger.error(f"Received unknown command: {command_req}")
|
278
|
+
reply = None
|
306
279
|
|
307
|
-
|
308
|
-
|
309
|
-
continue
|
280
|
+
logger.debug("Reply: {}".format(reply))
|
281
|
+
self.command_channel.send_pyobj(reply)
|
310
282
|
|
311
283
|
@wrap_with_logs
|
312
284
|
def start(self) -> None:
|
@@ -326,21 +298,13 @@ class Interchange:
|
|
326
298
|
|
327
299
|
start = time.time()
|
328
300
|
|
329
|
-
self._task_puller_thread = threading.Thread(target=self.task_puller,
|
330
|
-
name="Interchange-Task-Puller",
|
331
|
-
daemon=True)
|
332
|
-
self._task_puller_thread.start()
|
333
|
-
|
334
|
-
self._command_thread = threading.Thread(target=self._command_server,
|
335
|
-
name="Interchange-Command",
|
336
|
-
daemon=True)
|
337
|
-
self._command_thread.start()
|
338
|
-
|
339
301
|
kill_event = threading.Event()
|
340
302
|
|
341
303
|
poller = zmq.Poller()
|
342
304
|
poller.register(self.task_outgoing, zmq.POLLIN)
|
343
305
|
poller.register(self.results_incoming, zmq.POLLIN)
|
306
|
+
poller.register(self.task_incoming, zmq.POLLIN)
|
307
|
+
poller.register(self.command_channel, zmq.POLLIN)
|
344
308
|
|
345
309
|
# These are managers which we should examine in an iteration
|
346
310
|
# for scheduling a job (or maybe any other attention?).
|
@@ -351,6 +315,8 @@ class Interchange:
|
|
351
315
|
while not kill_event.is_set():
|
352
316
|
self.socks = dict(poller.poll(timeout=poll_period))
|
353
317
|
|
318
|
+
self.process_command(monitoring_radio)
|
319
|
+
self.process_task_incoming()
|
354
320
|
self.process_task_outgoing_incoming(interesting_managers, monitoring_radio, kill_event)
|
355
321
|
self.process_results_incoming(interesting_managers, monitoring_radio)
|
356
322
|
self.expire_bad_managers(interesting_managers, monitoring_radio)
|
@@ -362,6 +328,18 @@ class Interchange:
|
|
362
328
|
logger.info(f"Processed {self.count} tasks in {delta} seconds")
|
363
329
|
logger.warning("Exiting")
|
364
330
|
|
331
|
+
def process_task_incoming(self) -> None:
|
332
|
+
"""Process incoming task message(s).
|
333
|
+
"""
|
334
|
+
|
335
|
+
if self.task_incoming in self.socks and self.socks[self.task_incoming] == zmq.POLLIN:
|
336
|
+
logger.debug("start task_incoming section")
|
337
|
+
msg = self.task_incoming.recv_pyobj()
|
338
|
+
logger.debug("putting message onto pending_task_queue")
|
339
|
+
self.pending_task_queue.put(msg)
|
340
|
+
self.task_counter += 1
|
341
|
+
logger.debug(f"Fetched {self.task_counter} tasks so far")
|
342
|
+
|
365
343
|
def process_task_outgoing_incoming(
|
366
344
|
self,
|
367
345
|
interesting_managers: Set[bytes],
|
@@ -27,8 +27,5 @@ def test_initial_checkpoint_write():
|
|
27
27
|
|
28
28
|
cpt_dir = parsl.dfk().checkpoint()
|
29
29
|
|
30
|
-
cptpath = cpt_dir + '/dfk.pkl'
|
31
|
-
assert os.path.exists(cptpath), f"DFK checkpoint missing: {cptpath}"
|
32
|
-
|
33
30
|
cptpath = cpt_dir + '/tasks.pkl'
|
34
31
|
assert os.path.exists(cptpath), f"Tasks checkpoint missing: {cptpath}"
|
@@ -39,6 +39,9 @@ def test_fail_sequence_first():
|
|
39
39
|
assert isinstance(t_final.exception().dependent_exceptions_tids[0][0], DependencyError)
|
40
40
|
assert t_final.exception().dependent_exceptions_tids[0][1].startswith("task ")
|
41
41
|
|
42
|
+
assert hasattr(t_final.exception(), '__cause__')
|
43
|
+
assert t_final.exception().__cause__ == t1.exception()
|
44
|
+
|
42
45
|
|
43
46
|
def test_fail_sequence_middle():
|
44
47
|
t1 = random_fail(fail_prob=0)
|
@@ -50,3 +53,6 @@ def test_fail_sequence_middle():
|
|
50
53
|
|
51
54
|
assert len(t_final.exception().dependent_exceptions_tids) == 1
|
52
55
|
assert isinstance(t_final.exception().dependent_exceptions_tids[0][0], ManufacturedTestFailure)
|
56
|
+
|
57
|
+
assert hasattr(t_final.exception(), '__cause__')
|
58
|
+
assert t_final.exception().__cause__ == t2.exception()
|
@@ -2,7 +2,6 @@ import argparse
|
|
2
2
|
|
3
3
|
import parsl
|
4
4
|
from parsl.app.app import python_app
|
5
|
-
from parsl.tests.configs.local_threads import config
|
6
5
|
|
7
6
|
|
8
7
|
@python_app(cache=True)
|
@@ -12,8 +11,7 @@ def random_uuid(x, cache=True):
|
|
12
11
|
|
13
12
|
|
14
13
|
def test_python_memoization(n=2):
|
15
|
-
"""Testing python memoization
|
16
|
-
"""
|
14
|
+
"""Testing python memoization."""
|
17
15
|
x = random_uuid(0)
|
18
16
|
print(x.result())
|
19
17
|
|
parsl/version.py
CHANGED
@@ -9,7 +9,7 @@ import queue
|
|
9
9
|
import sys
|
10
10
|
import threading
|
11
11
|
import time
|
12
|
-
from typing import Any, Dict, List,
|
12
|
+
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, cast
|
13
13
|
|
14
14
|
import zmq
|
15
15
|
|
@@ -132,6 +132,11 @@ class Interchange:
|
|
132
132
|
self.hub_zmq_port = hub_zmq_port
|
133
133
|
|
134
134
|
self.pending_task_queue: queue.Queue[Any] = queue.Queue(maxsize=10 ** 6)
|
135
|
+
|
136
|
+
# count of tasks that have been received from the submit side
|
137
|
+
self.task_counter = 0
|
138
|
+
|
139
|
+
# count of tasks that have been sent out to worker pools
|
135
140
|
self.count = 0
|
136
141
|
|
137
142
|
self.worker_ports = worker_ports
|
@@ -201,28 +206,6 @@ class Interchange:
|
|
201
206
|
|
202
207
|
return tasks
|
203
208
|
|
204
|
-
@wrap_with_logs(target="interchange")
|
205
|
-
def task_puller(self) -> NoReturn:
|
206
|
-
"""Pull tasks from the incoming tasks zmq pipe onto the internal
|
207
|
-
pending task queue
|
208
|
-
"""
|
209
|
-
logger.info("Starting")
|
210
|
-
task_counter = 0
|
211
|
-
|
212
|
-
while True:
|
213
|
-
logger.debug("launching recv_pyobj")
|
214
|
-
try:
|
215
|
-
msg = self.task_incoming.recv_pyobj()
|
216
|
-
except zmq.Again:
|
217
|
-
# We just timed out while attempting to receive
|
218
|
-
logger.debug("zmq.Again with {} tasks in internal queue".format(self.pending_task_queue.qsize()))
|
219
|
-
continue
|
220
|
-
|
221
|
-
logger.debug("putting message onto pending_task_queue")
|
222
|
-
self.pending_task_queue.put(msg)
|
223
|
-
task_counter += 1
|
224
|
-
logger.debug(f"Fetched {task_counter} tasks so far")
|
225
|
-
|
226
209
|
def _send_monitoring_info(self, monitoring_radio: Optional[MonitoringRadioSender], manager: ManagerRecord) -> None:
|
227
210
|
if monitoring_radio:
|
228
211
|
logger.info("Sending message {} to MonitoringHub".format(manager))
|
@@ -234,79 +217,68 @@ class Interchange:
|
|
234
217
|
|
235
218
|
monitoring_radio.send((MessageType.NODE_INFO, d))
|
236
219
|
|
237
|
-
|
238
|
-
def _command_server(self) -> NoReturn:
|
220
|
+
def process_command(self, monitoring_radio: Optional[MonitoringRadioSender]) -> None:
|
239
221
|
""" Command server to run async command to the interchange
|
240
222
|
"""
|
241
|
-
logger.debug("
|
242
|
-
|
243
|
-
if self.hub_address is not None and self.hub_zmq_port is not None:
|
244
|
-
logger.debug("Creating monitoring radio to %s:%s", self.hub_address, self.hub_zmq_port)
|
245
|
-
monitoring_radio = ZMQRadioSender(self.hub_address, self.hub_zmq_port)
|
246
|
-
else:
|
247
|
-
monitoring_radio = None
|
223
|
+
logger.debug("entering command_server section")
|
248
224
|
|
249
225
|
reply: Any # the type of reply depends on the command_req received (aka this needs dependent types...)
|
250
226
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
else:
|
272
|
-
idle_duration = 0.0
|
273
|
-
resp = {'manager': manager_id.decode('utf-8'),
|
274
|
-
'block_id': m['block_id'],
|
275
|
-
'worker_count': m['worker_count'],
|
276
|
-
'tasks': len(m['tasks']),
|
277
|
-
'idle_duration': idle_duration,
|
278
|
-
'active': m['active'],
|
279
|
-
'parsl_version': m['parsl_version'],
|
280
|
-
'python_version': m['python_version'],
|
281
|
-
'draining': m['draining']}
|
282
|
-
reply.append(resp)
|
283
|
-
|
284
|
-
elif command_req.startswith("HOLD_WORKER"):
|
285
|
-
cmd, s_manager = command_req.split(';')
|
286
|
-
manager_id = s_manager.encode('utf-8')
|
287
|
-
logger.info("Received HOLD_WORKER for {!r}".format(manager_id))
|
288
|
-
if manager_id in self._ready_managers:
|
289
|
-
m = self._ready_managers[manager_id]
|
290
|
-
m['active'] = False
|
291
|
-
self._send_monitoring_info(monitoring_radio, m)
|
227
|
+
if self.command_channel in self.socks and self.socks[self.command_channel] == zmq.POLLIN:
|
228
|
+
|
229
|
+
command_req = self.command_channel.recv_pyobj()
|
230
|
+
logger.debug("Received command request: {}".format(command_req))
|
231
|
+
if command_req == "CONNECTED_BLOCKS":
|
232
|
+
reply = self.connected_block_history
|
233
|
+
|
234
|
+
elif command_req == "WORKERS":
|
235
|
+
num_workers = 0
|
236
|
+
for manager in self._ready_managers.values():
|
237
|
+
num_workers += manager['worker_count']
|
238
|
+
reply = num_workers
|
239
|
+
|
240
|
+
elif command_req == "MANAGERS":
|
241
|
+
reply = []
|
242
|
+
for manager_id in self._ready_managers:
|
243
|
+
m = self._ready_managers[manager_id]
|
244
|
+
idle_since = m['idle_since']
|
245
|
+
if idle_since is not None:
|
246
|
+
idle_duration = time.time() - idle_since
|
292
247
|
else:
|
293
|
-
|
294
|
-
|
295
|
-
|
248
|
+
idle_duration = 0.0
|
249
|
+
resp = {'manager': manager_id.decode('utf-8'),
|
250
|
+
'block_id': m['block_id'],
|
251
|
+
'worker_count': m['worker_count'],
|
252
|
+
'tasks': len(m['tasks']),
|
253
|
+
'idle_duration': idle_duration,
|
254
|
+
'active': m['active'],
|
255
|
+
'parsl_version': m['parsl_version'],
|
256
|
+
'python_version': m['python_version'],
|
257
|
+
'draining': m['draining']}
|
258
|
+
reply.append(resp)
|
259
|
+
|
260
|
+
elif command_req.startswith("HOLD_WORKER"):
|
261
|
+
cmd, s_manager = command_req.split(';')
|
262
|
+
manager_id = s_manager.encode('utf-8')
|
263
|
+
logger.info("Received HOLD_WORKER for {!r}".format(manager_id))
|
264
|
+
if manager_id in self._ready_managers:
|
265
|
+
m = self._ready_managers[manager_id]
|
266
|
+
m['active'] = False
|
267
|
+
self._send_monitoring_info(monitoring_radio, m)
|
268
|
+
else:
|
269
|
+
logger.warning("Worker to hold was not in ready managers list")
|
296
270
|
|
297
|
-
|
298
|
-
reply = (self.worker_task_port, self.worker_result_port)
|
271
|
+
reply = None
|
299
272
|
|
300
|
-
|
301
|
-
|
302
|
-
reply = None
|
273
|
+
elif command_req == "WORKER_PORTS":
|
274
|
+
reply = (self.worker_task_port, self.worker_result_port)
|
303
275
|
|
304
|
-
|
305
|
-
|
276
|
+
else:
|
277
|
+
logger.error(f"Received unknown command: {command_req}")
|
278
|
+
reply = None
|
306
279
|
|
307
|
-
|
308
|
-
|
309
|
-
continue
|
280
|
+
logger.debug("Reply: {}".format(reply))
|
281
|
+
self.command_channel.send_pyobj(reply)
|
310
282
|
|
311
283
|
@wrap_with_logs
|
312
284
|
def start(self) -> None:
|
@@ -326,21 +298,13 @@ class Interchange:
|
|
326
298
|
|
327
299
|
start = time.time()
|
328
300
|
|
329
|
-
self._task_puller_thread = threading.Thread(target=self.task_puller,
|
330
|
-
name="Interchange-Task-Puller",
|
331
|
-
daemon=True)
|
332
|
-
self._task_puller_thread.start()
|
333
|
-
|
334
|
-
self._command_thread = threading.Thread(target=self._command_server,
|
335
|
-
name="Interchange-Command",
|
336
|
-
daemon=True)
|
337
|
-
self._command_thread.start()
|
338
|
-
|
339
301
|
kill_event = threading.Event()
|
340
302
|
|
341
303
|
poller = zmq.Poller()
|
342
304
|
poller.register(self.task_outgoing, zmq.POLLIN)
|
343
305
|
poller.register(self.results_incoming, zmq.POLLIN)
|
306
|
+
poller.register(self.task_incoming, zmq.POLLIN)
|
307
|
+
poller.register(self.command_channel, zmq.POLLIN)
|
344
308
|
|
345
309
|
# These are managers which we should examine in an iteration
|
346
310
|
# for scheduling a job (or maybe any other attention?).
|
@@ -351,6 +315,8 @@ class Interchange:
|
|
351
315
|
while not kill_event.is_set():
|
352
316
|
self.socks = dict(poller.poll(timeout=poll_period))
|
353
317
|
|
318
|
+
self.process_command(monitoring_radio)
|
319
|
+
self.process_task_incoming()
|
354
320
|
self.process_task_outgoing_incoming(interesting_managers, monitoring_radio, kill_event)
|
355
321
|
self.process_results_incoming(interesting_managers, monitoring_radio)
|
356
322
|
self.expire_bad_managers(interesting_managers, monitoring_radio)
|
@@ -362,6 +328,18 @@ class Interchange:
|
|
362
328
|
logger.info(f"Processed {self.count} tasks in {delta} seconds")
|
363
329
|
logger.warning("Exiting")
|
364
330
|
|
331
|
+
def process_task_incoming(self) -> None:
|
332
|
+
"""Process incoming task message(s).
|
333
|
+
"""
|
334
|
+
|
335
|
+
if self.task_incoming in self.socks and self.socks[self.task_incoming] == zmq.POLLIN:
|
336
|
+
logger.debug("start task_incoming section")
|
337
|
+
msg = self.task_incoming.recv_pyobj()
|
338
|
+
logger.debug("putting message onto pending_task_queue")
|
339
|
+
self.pending_task_queue.put(msg)
|
340
|
+
self.task_counter += 1
|
341
|
+
logger.debug(f"Fetched {self.task_counter} tasks so far")
|
342
|
+
|
365
343
|
def process_task_outgoing_incoming(
|
366
344
|
self,
|
367
345
|
interesting_managers: Set[bytes],
|
@@ -1,9 +1,9 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: parsl
|
3
|
-
Version: 2025.
|
3
|
+
Version: 2025.2.3
|
4
4
|
Summary: Simple data dependent workflows in Python
|
5
5
|
Home-page: https://github.com/Parsl/parsl
|
6
|
-
Download-URL: https://github.com/Parsl/parsl/archive/2025.
|
6
|
+
Download-URL: https://github.com/Parsl/parsl/archive/2025.02.03.tar.gz
|
7
7
|
Author: The Parsl Team
|
8
8
|
Author-email: parsl@googlegroups.com
|
9
9
|
License: Apache 2.0
|
@@ -8,7 +8,7 @@ parsl/multiprocessing.py,sha256=MyaEcEq-Qf860u7V98u-PZrPNdtzOZL_NW6EhIJnmfQ,1937
|
|
8
8
|
parsl/process_loggers.py,sha256=uQ7Gd0W72Jz7rrcYlOMfLsAEhkRltxXJL2MgdduJjEw,1136
|
9
9
|
parsl/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
parsl/utils.py,sha256=5FvHIMao3Ik0Rm2p2ieL1KQcQcYXc5K83Jrx5csi-B4,14301
|
11
|
-
parsl/version.py,sha256=
|
11
|
+
parsl/version.py,sha256=Zl1Frad6LRWH7vPgll3ZZU1ODYvo9dMwEa4jBjGYQkg,131
|
12
12
|
parsl/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
parsl/app/app.py,sha256=0gbM4AH2OtFOLsv07I5nglpElcwMSOi-FzdZZfrk7So,8532
|
14
14
|
parsl/app/bash.py,sha256=jm2AvePlCT9DZR7H_4ANDWxatp5dN_22FUlT_gWhZ-g,5528
|
@@ -53,10 +53,10 @@ parsl/data_provider/staging.py,sha256=ZDZuuFg38pjUStegKPcvPsfGp3iMeReMzfU6DSwtJj
|
|
53
53
|
parsl/data_provider/zip.py,sha256=S4kVuH9lxAegRURYbvIUR7EYYBOccyslaqyCrVWUBhw,4497
|
54
54
|
parsl/dataflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
55
|
parsl/dataflow/dependency_resolvers.py,sha256=Om8Dgh7a0ZwgXAc6TlhxLSzvxXHDlNNV1aBNiD3JTNY,3325
|
56
|
-
parsl/dataflow/dflow.py,sha256=
|
57
|
-
parsl/dataflow/errors.py,sha256=
|
56
|
+
parsl/dataflow/dflow.py,sha256=jNxrAd2xmxesS3fR6eZyDN9f6I0BIBQbhL63zv51lkk,61752
|
57
|
+
parsl/dataflow/errors.py,sha256=daVfr2BWs1zRsGD6JtosEMttWHvK1df1Npiu_MUvFKg,3998
|
58
58
|
parsl/dataflow/futures.py,sha256=08LuP-HFiHBIZmeKCjlsazw_WpQ5fwevrU2_WbidkYw,6080
|
59
|
-
parsl/dataflow/memoization.py,sha256=
|
59
|
+
parsl/dataflow/memoization.py,sha256=QUkTduZ_gdr8i08VWNWrqhfEvoMGsPDZegWUE2_7sGQ,12579
|
60
60
|
parsl/dataflow/rundirs.py,sha256=JZdzybVGubY35jL2YiKcDo65ZmRl1WyOApc8ajYxztc,1087
|
61
61
|
parsl/dataflow/states.py,sha256=hV6mfv-y4A6xrujeQglcomnfEs7y3Xm2g6JFwC6dvgQ,2612
|
62
62
|
parsl/dataflow/taskrecord.py,sha256=qIW7T6hn9dYTuNPdUura3HQwwUpUJACwPP5REm5COf4,3042
|
@@ -73,8 +73,8 @@ parsl/executors/flux/executor.py,sha256=8_xakLUu5zNJAHL0LbeTCFEWqWzRK1eE-3ep4GII
|
|
73
73
|
parsl/executors/flux/flux_instance_manager.py,sha256=5T3Rp7ZM-mlT0Pf0Gxgs5_YmnaPrSF9ec7zvRfLfYJw,2129
|
74
74
|
parsl/executors/high_throughput/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
75
75
|
parsl/executors/high_throughput/errors.py,sha256=k2XuvvFdUfNs2foHFnxmS-BToRMfdXpYEa4EF3ELKq4,1554
|
76
|
-
parsl/executors/high_throughput/executor.py,sha256=
|
77
|
-
parsl/executors/high_throughput/interchange.py,sha256=
|
76
|
+
parsl/executors/high_throughput/executor.py,sha256=4WKp0ZqAz036WcaXq-4DDwLyu8W6xGPunnvLAVSeaiQ,38493
|
77
|
+
parsl/executors/high_throughput/interchange.py,sha256=kVz7LTx6ThcYrQOSq-TsWHOlDwgyQk4w9Qyd3egQTuY,29143
|
78
78
|
parsl/executors/high_throughput/manager_record.py,sha256=yn3L8TUJFkgm2lX1x0SeS9mkvJowC0s2VIMCFiU7ThM,455
|
79
79
|
parsl/executors/high_throughput/manager_selector.py,sha256=UKcUE6v0tO7PDMTThpKSKxVpOpOUilxDL7UbNgpZCxo,2116
|
80
80
|
parsl/executors/high_throughput/monitoring_info.py,sha256=HC0drp6nlXQpAop5PTUKNjdXMgtZVvrBL0JzZJebPP4,298
|
@@ -290,9 +290,8 @@ parsl/tests/test_bash_apps/test_std_uri.py,sha256=CvAt8BUhNl2pA5chq9YyhkD6eo2IUH
|
|
290
290
|
parsl/tests/test_bash_apps/test_stdout.py,sha256=lNBzCJGst0IhKaSl8CM8-mTJ5eaK7hTlZ8gY-M2TDBU,3244
|
291
291
|
parsl/tests/test_checkpointing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
292
292
|
parsl/tests/test_checkpointing/test_periodic.py,sha256=nfMgrG7sZ8rkMu6iOHS6lp_iTU4IsOyQLQ2Gur_FMmE,1509
|
293
|
-
parsl/tests/test_checkpointing/test_python_checkpoint_1.py,sha256=
|
293
|
+
parsl/tests/test_checkpointing/test_python_checkpoint_1.py,sha256=TP6kSK_0qpCqecpp7O50AbTsnLKU6wvTvNG89hh4LQw,637
|
294
294
|
parsl/tests/test_checkpointing/test_python_checkpoint_2.py,sha256=Q_cXeAVz_dJuDDeiemUIGd-wmb7aCY3ggpqYjRRhHRc,1089
|
295
|
-
parsl/tests/test_checkpointing/test_python_checkpoint_3.py,sha256=y4esbbp1h9FP-_rBfnFhohB6a7o3VOpjqKdTovV8HbA,805
|
296
295
|
parsl/tests/test_checkpointing/test_regression_232.py,sha256=AsI6AJ0DcFaefAbEY9qWa41ER0VX-4yLuIdlgvBw360,2637
|
297
296
|
parsl/tests/test_checkpointing/test_regression_233.py,sha256=jii7BKuygK6KMIGtg4IeBjix7Z28cYhv57rE9ixoXMU,1774
|
298
297
|
parsl/tests/test_checkpointing/test_regression_239.py,sha256=xycW1_IwVC55L25oMES_OzJU58TN5BoMvRUZ_xB69jU,2441
|
@@ -366,7 +365,7 @@ parsl/tests/test_python_apps/test_dep_standard_futures.py,sha256=kMOMZLaxJMmpABC
|
|
366
365
|
parsl/tests/test_python_apps/test_dependencies.py,sha256=IRiTI_lPoWBSFSFnaBlE6Bv08PKEaf-qj5dfqO2RjT0,272
|
367
366
|
parsl/tests/test_python_apps/test_dependencies_deep.py,sha256=Cuow2LLGY7zffPFj89AOIwKlXxHtsin3v_UIhfdwV_w,1542
|
368
367
|
parsl/tests/test_python_apps/test_depfail_propagation.py,sha256=3q3HlVWrOixFtXWBvR_ypKtbdAHAJcKndXQ5drwrBQU,1488
|
369
|
-
parsl/tests/test_python_apps/test_fail.py,sha256=
|
368
|
+
parsl/tests/test_python_apps/test_fail.py,sha256=gMuZwxZNaUCaonlUX-7SOBvXg8kidkBcEeqKLEvqpYM,1692
|
370
369
|
parsl/tests/test_python_apps/test_fibonacci_iterative.py,sha256=ly2s5HuB9R53Z2FM_zy0WWdOk01iVhgcwSpQyK6ErIY,573
|
371
370
|
parsl/tests/test_python_apps/test_fibonacci_recursive.py,sha256=q7LMFcu_pJSNPdz8iY0UiRoIweEWIBGwMjQffHWAuDc,592
|
372
371
|
parsl/tests/test_python_apps/test_futures.py,sha256=EWnzmPn5sVCgeMxc0Uz2ieaaVYr98tFZ7g8YJFqYuC8,2355
|
@@ -376,7 +375,7 @@ parsl/tests/test_python_apps/test_inputs_default.py,sha256=J2GR1NgdvEucNSJkfO6GC
|
|
376
375
|
parsl/tests/test_python_apps/test_join.py,sha256=OWd6_A0Cf-1Xpjr0OT3HaJ1IMYcJ0LFL1VnmL0cZkL8,2988
|
377
376
|
parsl/tests/test_python_apps/test_lifted.py,sha256=Na6qC_dZSeYJcZdkGn-dCjgYkQV267HmGFfaqFcRVcQ,3408
|
378
377
|
parsl/tests/test_python_apps/test_mapred.py,sha256=C7nTl0NsP_2TCtcmZXWFMpvAG4pwGswrIJKr-5sRUNY,786
|
379
|
-
parsl/tests/test_python_apps/test_memoize_1.py,sha256=
|
378
|
+
parsl/tests/test_python_apps/test_memoize_1.py,sha256=E_VQAaykFKT_G7yRUWOhXxfOICj07qLq2R7onZ4oY9g,449
|
380
379
|
parsl/tests/test_python_apps/test_memoize_2.py,sha256=uG9zG9j3ap1FqeJ8aB0Gj_dX191pN3dxWXeQ-asxPgU,553
|
381
380
|
parsl/tests/test_python_apps/test_memoize_4.py,sha256=CdK_vHW5s-phi5KPqcAQm_BRh8xek91GVGeQRjfJ4Bk,569
|
382
381
|
parsl/tests/test_python_apps/test_memoize_bad_id_for_memo.py,sha256=5v25zdU6koXexRTkccj_3sSSdXqHdsU8ZdNrnZ3ONZU,1436
|
@@ -456,13 +455,13 @@ parsl/usage_tracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
456
455
|
parsl/usage_tracking/api.py,sha256=iaCY58Dc5J4UM7_dJzEEs871P1p1HdxBMtNGyVdzc9g,1821
|
457
456
|
parsl/usage_tracking/levels.py,sha256=xbfzYEsd55KiZJ-mzNgPebvOH4rRHum04hROzEf41tU,291
|
458
457
|
parsl/usage_tracking/usage.py,sha256=f9k6QcpbQxkGyP5WTC9PVyv0CA05s9NDpRe5wwRdBTM,9163
|
459
|
-
parsl-2025.
|
460
|
-
parsl-2025.
|
461
|
-
parsl-2025.
|
462
|
-
parsl-2025.
|
463
|
-
parsl-2025.
|
464
|
-
parsl-2025.
|
465
|
-
parsl-2025.
|
466
|
-
parsl-2025.
|
467
|
-
parsl-2025.
|
468
|
-
parsl-2025.
|
458
|
+
parsl-2025.2.3.data/scripts/exec_parsl_function.py,sha256=YXKVVIa4zXmOtz-0Ca4E_5nQfN_3S2bh2tB75uZZB4w,7774
|
459
|
+
parsl-2025.2.3.data/scripts/interchange.py,sha256=dAo8zl82gUFaeu_vNz-OXjkbeORZmwnqj4CMv8DahKY,29130
|
460
|
+
parsl-2025.2.3.data/scripts/parsl_coprocess.py,sha256=zrVjEqQvFOHxsLufPi00xzMONagjVwLZbavPM7bbjK4,5722
|
461
|
+
parsl-2025.2.3.data/scripts/process_worker_pool.py,sha256=82FoJTye2SysJzPg-N8BpenuHGU7hOI8-Bedq8HV9C0,41851
|
462
|
+
parsl-2025.2.3.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
463
|
+
parsl-2025.2.3.dist-info/METADATA,sha256=nqLV9gMKl_5N-QYsDt-PJZ2W-9W6pqJEzWKvJ3LNf8g,4026
|
464
|
+
parsl-2025.2.3.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
465
|
+
parsl-2025.2.3.dist-info/entry_points.txt,sha256=XqnsWDYoEcLbsMcpnYGKLEnSBmaIe1YoM5YsBdJG2tI,176
|
466
|
+
parsl-2025.2.3.dist-info/top_level.txt,sha256=PIheYoUFQtF2icLsgOykgU-Cjuwr2Oi6On2jo5RYgRM,6
|
467
|
+
parsl-2025.2.3.dist-info/RECORD,,
|
@@ -1,42 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
|
3
|
-
import pytest
|
4
|
-
|
5
|
-
import parsl
|
6
|
-
from parsl.app.app import python_app
|
7
|
-
from parsl.tests.configs.local_threads import config
|
8
|
-
|
9
|
-
|
10
|
-
def local_setup():
|
11
|
-
global dfk
|
12
|
-
dfk = parsl.load(config)
|
13
|
-
|
14
|
-
|
15
|
-
def local_teardown():
|
16
|
-
parsl.dfk().cleanup()
|
17
|
-
|
18
|
-
|
19
|
-
@python_app
|
20
|
-
def slow_double(x, sleep_dur=1, cache=True):
|
21
|
-
import time
|
22
|
-
time.sleep(sleep_dur)
|
23
|
-
return x * 2
|
24
|
-
|
25
|
-
|
26
|
-
@pytest.mark.local
|
27
|
-
def test_checkpointing():
|
28
|
-
"""Testing code snippet from documentation
|
29
|
-
"""
|
30
|
-
|
31
|
-
N = 5 # Number of calls to slow_double
|
32
|
-
d = [] # List to store the futures
|
33
|
-
for i in range(0, N):
|
34
|
-
d.append(slow_double(i))
|
35
|
-
|
36
|
-
# Wait for the results
|
37
|
-
[i.result() for i in d]
|
38
|
-
|
39
|
-
checkpoint_dir = dfk.checkpoint()
|
40
|
-
print(checkpoint_dir)
|
41
|
-
|
42
|
-
assert os.path.exists(checkpoint_dir), "Checkpoint dir does not exist"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|