parsl 2023.11.13__py3-none-any.whl → 2023.11.20__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/__init__.py CHANGED
@@ -62,6 +62,7 @@ __all__ = [
62
62
  'ThreadPoolExecutor',
63
63
  'HighThroughputExecutor',
64
64
  'WorkQueueExecutor',
65
+ 'RadicalPilotExecutor',
65
66
 
66
67
  # monitoring
67
68
  'MonitoringHub',
parsl/executors/base.py CHANGED
@@ -5,8 +5,6 @@ from typing_extensions import Literal, Self
5
5
 
6
6
  from parsl.jobs.states import JobStatus
7
7
 
8
- import parsl # noqa F401
9
-
10
8
 
11
9
  class ParslExecutor(metaclass=ABCMeta):
12
10
  """Executors are abstractions that represent available compute resources
@@ -8,7 +8,7 @@ import datetime
8
8
  import pickle
9
9
  import warnings
10
10
  from multiprocessing import Queue
11
- from typing import Dict, Sequence # noqa F401 (used in type annotation)
11
+ from typing import Dict, Sequence
12
12
  from typing import List, Optional, Tuple, Union, Callable
13
13
  import math
14
14
 
@@ -0,0 +1,4 @@
1
+ from parsl.executors.radical.executor import RadicalPilotExecutor
2
+ from parsl.executors.radical.rpex_resources import ResourceConfig
3
+
4
+ __all__ = ['RadicalPilotExecutor', 'ResourceConfig']
@@ -0,0 +1,550 @@
1
+ """RadicalPilotExecutor builds on the RADICAL-Pilot/Parsl
2
+ """
3
+ import os
4
+ import sys
5
+ import time
6
+ import parsl
7
+ import queue
8
+ import logging
9
+ import inspect
10
+ import requests
11
+ import typeguard
12
+ import threading as mt
13
+
14
+ from functools import partial
15
+ from typing import Optional, Dict
16
+ from pathlib import Path, PosixPath
17
+ from concurrent.futures import Future
18
+
19
+ from parsl.app.python import timeout
20
+ from .rpex_resources import ResourceConfig
21
+ from parsl.data_provider.files import File
22
+ from parsl.utils import RepresentationMixin
23
+ from parsl.app.errors import BashExitFailure
24
+ from parsl.executors.base import ParslExecutor
25
+ from parsl.app.errors import RemoteExceptionWrapper
26
+ from parsl.serialize import pack_apply_message, deserialize
27
+ from parsl.serialize.errors import SerializationError, DeserializationError
28
+
29
+ try:
30
+ import radical.pilot as rp
31
+ import radical.utils as ru
32
+ except ImportError:
33
+ _rp_enabled = False
34
+ else:
35
+ _rp_enabled = True
36
+
37
+
38
+ RPEX = 'RPEX'
39
+ BASH = 'bash'
40
+ PYTHON = 'python'
41
+
42
+ CWD = os.getcwd()
43
+ PWD = os.path.abspath(os.path.dirname(__file__))
44
+
45
+ PARSL_RP_RESOURCE_MAP = {'cores': 'ranks',
46
+ 'disk': 'lfs_per_rank',
47
+ 'memory': 'mem_per_rank'}
48
+
49
+ logger = logging.getLogger(__name__)
50
+
51
+
52
+ class RadicalPilotExecutor(ParslExecutor, RepresentationMixin):
53
+ """Executor is designed for executing heterogeneous tasks
54
+ in terms of type/resource.
55
+
56
+ The RadicalPilotExecutor system has the following components:
57
+
58
+ 1. "start" :creating the RADICAL-executor session and pilot.
59
+ 2. "translate":unwrap/identify/ out of parsl task and construct RP task.
60
+ 3. "submit" :translating and submitting Parsl tasks to Radical Pilot.
61
+ 4. "shut_down":shutting down the RADICAL-executor components.
62
+
63
+ Here is a diagram
64
+
65
+ .. code:: python
66
+
67
+ ----------------------------------------------------------------------------
68
+ Parsl Data Flow Kernel | Task Translator | rp.TaskManager
69
+ ---------------------------------------|-------------------|----------------
70
+ | |
71
+ -> Dep. check ------> Parsl_tasks{} <--+--> Parsl Task |
72
+ Data management +dfk.submit | | |
73
+ | v |
74
+ | RP Task(s) -> | submit(task)
75
+ ----------------------------------------------------------------------------
76
+
77
+ The RadicalPilotExecutor creates a ``rp.Session``, ``rp.TaskManager``,
78
+ and ``rp.PilotManager``. The executor receives the parsl apps from the
79
+ DFK and translates these apps (in-memory) into ``rp.TaskDescription``
80
+ object to be passed to the ``rp.TaskManager``. This executor has two
81
+ submission mechanisms:
82
+
83
+ 1. Default_mode: where the executor submits the tasks directly to
84
+ RADICAL-Pilot.
85
+
86
+ 2. Bulk_mode: where the executor accumulates N tasks (functions and
87
+ executables) and submit them.
88
+
89
+ Parameters
90
+ ----------
91
+ rpex_cfg : :class: `~parsl.executors.rpex_resources.ResourceConfig`
92
+ a dataclass specifying resource configuration.
93
+ Default is ResourceConfig instance.
94
+
95
+ label : str
96
+ Label for this executor instance.
97
+ Default is "RPEX".
98
+
99
+ bulk_mode : bool
100
+ Enable bulk mode submission and execution. Default is False (stream).
101
+
102
+ resource : Optional[str]
103
+ The resource name of the targeted HPC machine or cluster.
104
+ Default is local.localhost (user local machine).
105
+
106
+ runtime : int
107
+ The maximum runtime for the entire job in minutes.
108
+ Default is 30.
109
+
110
+ working_dir : str
111
+ The working dir to be used by the executor.
112
+
113
+ rpex_pilot_kwargs: Dict of kwargs that are passed directly to the rp.PilotDescription object.
114
+
115
+ For more information: https://radicalpilot.readthedocs.io/en/stable/
116
+ """
117
+
118
+ @typeguard.typechecked
119
+ def __init__(self,
120
+ resource: str,
121
+ label: str = RPEX,
122
+ bulk_mode: bool = False,
123
+ working_dir: Optional[str] = None,
124
+ rpex_cfg: Optional[ResourceConfig] = None, **rpex_pilot_kwargs):
125
+
126
+ super().__init__()
127
+ self.pmgr = None
128
+ self.tmgr = None
129
+ self.run_dir = '.'
130
+ self.label = label
131
+ self.session = None
132
+ self.resource = resource
133
+ self._uid = RPEX.lower()
134
+ self.bulk_mode = bulk_mode
135
+ self.working_dir = working_dir
136
+ self.pilot_kwargs = rpex_pilot_kwargs
137
+ self.future_tasks: Dict[str, Future] = {}
138
+
139
+ if rpex_cfg:
140
+ self.rpex_cfg = rpex_cfg
141
+ elif not rpex_cfg and 'local' in resource:
142
+ self.rpex_cfg = ResourceConfig()
143
+ else:
144
+ raise ValueError('Resource config file must be '
145
+ 'specified for a non-local execution')
146
+
147
+ def task_state_cb(self, task, state):
148
+ """
149
+ Update the state of Parsl Future tasks
150
+ Based on RP task state callbacks.
151
+ """
152
+ if not task.uid.startswith('master'):
153
+ parsl_task = self.future_tasks[task.uid]
154
+
155
+ if state == rp.DONE:
156
+ if task.description['mode'] in [rp.TASK_EXEC,
157
+ rp.TASK_PROC,
158
+ rp.TASK_EXECUTABLE]:
159
+ parsl_task.set_result(int(task.exit_code))
160
+ else:
161
+ # we do not support MPI function output
162
+ # serialization. TODO: To be fixed soon.
163
+ if not task.description.get('use_mpi'):
164
+ result = deserialize(eval(task.return_value))
165
+ parsl_task.set_result(result)
166
+ else:
167
+ parsl_task.set_result(task.return_value)
168
+
169
+ elif state == rp.CANCELED:
170
+ parsl_task.cancel()
171
+
172
+ elif state == rp.FAILED:
173
+ if task.description['mode'] in [rp.TASK_EXEC,
174
+ rp.TASK_EXECUTABLE]:
175
+ parsl_task.set_exception(BashExitFailure(task.name,
176
+ task.exit_code))
177
+ else:
178
+ if task.exception:
179
+ # unpack a serialized exception
180
+ if not task.description.get('use_mpi') or task.description['mode'] == rp.TASK_PROC:
181
+ self._unpack_and_set_parsl_exception(parsl_task, task.exception)
182
+ # we do not serialize mpi function exception
183
+ else:
184
+ parsl_task.set_exception(eval(task.exception))
185
+ else:
186
+ parsl_task.set_exception('Task failed for an unknown reason')
187
+
188
+ def start(self):
189
+ """Create the Pilot component and pass it.
190
+ """
191
+ logger.info("starting RadicalPilotExecutor")
192
+ logger.info('Parsl: {0}'.format(parsl.__version__))
193
+ logger.info('RADICAL pilot: {0}'.format(rp.version))
194
+ self.session = rp.Session(cfg={'base': self.run_dir},
195
+ uid=ru.generate_id('rpex.session',
196
+ mode=ru.ID_PRIVATE))
197
+ logger.info("RPEX session is created: {0}".format(self.session.path))
198
+
199
+ pd_init = {**self.pilot_kwargs,
200
+ 'exit_on_error': True,
201
+ 'resource': self.resource}
202
+
203
+ if not self.resource or 'local' in self.resource:
204
+ # move the agent sandbox to the working dir mainly
205
+ # for debugging purposes. This will allow parsl
206
+ # to include the agent sandbox with the ci artifacts.
207
+ if os.environ.get("LOCAL_SANDBOX"):
208
+ pd_init['sandbox'] = self.run_dir
209
+ os.environ["RADICAL_LOG_LVL"] = "DEBUG"
210
+
211
+ logger.info("RPEX will be running in the local mode")
212
+
213
+ pd = rp.PilotDescription(pd_init)
214
+ pd.verify()
215
+
216
+ self.rpex_cfg = self.rpex_cfg._get_cfg_file(path=self.run_dir)
217
+ cfg = ru.Config(cfg=ru.read_json(self.rpex_cfg))
218
+
219
+ self.master = cfg.master_descr
220
+ self.n_masters = cfg.n_masters
221
+
222
+ tds = list()
223
+ master_path = '{0}/rpex_master.py'.format(PWD)
224
+ worker_path = '{0}/rpex_worker.py'.format(PWD)
225
+
226
+ for i in range(self.n_masters):
227
+ td = rp.TaskDescription(self.master)
228
+ td.mode = rp.RAPTOR_MASTER
229
+ td.uid = ru.generate_id('master.%(item_counter)06d', ru.ID_CUSTOM,
230
+ ns=self.session.uid)
231
+ td.ranks = 1
232
+ td.cores_per_rank = 1
233
+ td.arguments = [self.rpex_cfg, i]
234
+ td.input_staging = self._stage_files([File(master_path),
235
+ File(worker_path),
236
+ File(self.rpex_cfg)], mode='in')
237
+ tds.append(td)
238
+
239
+ self.pmgr = rp.PilotManager(session=self.session)
240
+ self.tmgr = rp.TaskManager(session=self.session)
241
+
242
+ # submit pilot(s)
243
+ pilot = self.pmgr.submit_pilots(pd)
244
+ if not pilot.description.get('cores'):
245
+ logger.warning('no "cores" per pilot was set, using default resources {0}'.format(pilot.resources))
246
+
247
+ self.tmgr.submit_tasks(tds)
248
+
249
+ # prepare or use the current env for the agent/pilot side environment
250
+ if cfg.pilot_env_mode != 'client':
251
+ logger.info("creating {0} environment for the executor".format(cfg.pilot_env.name))
252
+ pilot.prepare_env(env_name=cfg.pilot_env.name,
253
+ env_spec=cfg.pilot_env.as_dict())
254
+ else:
255
+ client_env = sys.prefix
256
+ logger.info("reusing ({0}) environment for the executor".format(client_env))
257
+
258
+ self.tmgr.add_pilots(pilot)
259
+ self.tmgr.register_callback(self.task_state_cb)
260
+
261
+ # create a bulking thread to run the actual task submission
262
+ # to RP in bulks
263
+ if self.bulk_mode:
264
+ self._max_bulk_size = 1024
265
+ self._max_bulk_time = 3 # seconds
266
+ self._min_bulk_time = 0.1 # seconds
267
+
268
+ self._bulk_queue = queue.Queue()
269
+ self._bulk_thread = mt.Thread(target=self._bulk_collector)
270
+
271
+ self._bulk_thread.daemon = True
272
+ self._bulk_thread.start()
273
+
274
+ return True
275
+
276
+ def unwrap(self, func, args):
277
+ """
278
+ Unwrap a parsl app and its args for further processing.
279
+
280
+ Parameters
281
+ ----------
282
+ func : callable
283
+ The function to be unwrapped.
284
+
285
+ args : tuple
286
+ The arguments associated with the function.
287
+
288
+ Returns
289
+ -------
290
+ tuple
291
+ A tuple containing the unwrapped function, adjusted arguments,
292
+ and task type information.
293
+ """
294
+
295
+ task_type = ''
296
+
297
+ while hasattr(func, '__wrapped__'):
298
+ func = func.__wrapped__
299
+
300
+ try:
301
+ if isinstance(func, partial):
302
+ try:
303
+ task_type = inspect.getsource(func.args[0]).split('\n')[0]
304
+ if BASH in task_type:
305
+ task_type = BASH
306
+ func = func.args[0]
307
+ else:
308
+ task_type = PYTHON
309
+
310
+ except Exception:
311
+ logger.exception('unwrap failed')
312
+
313
+ return func, args, task_type
314
+
315
+ else:
316
+ task_type = inspect.getsource(func).split('\n')[0]
317
+ if PYTHON in task_type:
318
+ task_type = PYTHON
319
+ else:
320
+ task_type = ''
321
+ except Exception as e:
322
+ raise Exception('failed to obtain task type: {0}'.format(e))
323
+
324
+ return func, args, task_type
325
+
326
+ def task_translate(self, tid, func, parsl_resource_specification, args, kwargs):
327
+ """
328
+ Convert parsl function to RADICAL-Pilot rp.TaskDescription
329
+ """
330
+
331
+ task = rp.TaskDescription()
332
+ task.name = func.__name__
333
+
334
+ if parsl_resource_specification and isinstance(parsl_resource_specification, dict):
335
+ logger.debug('mapping parsl resource specifications >> rp resource specifications')
336
+ for key, val in parsl_resource_specification.items():
337
+ if key not in task.as_dict():
338
+ key = PARSL_RP_RESOURCE_MAP.get(key, None)
339
+ if not key:
340
+ logger.warning('ignoring "{0}" key from task resource specification as it is not supported by RP'.format(key))
341
+ continue
342
+ setattr(task, key, val)
343
+
344
+ func, args, task_type = self.unwrap(func, args)
345
+
346
+ if BASH in task_type:
347
+ if callable(func):
348
+ # if the user specifies the executable mode then we expect the
349
+ # a code in a file that need to be executed in an isolated env.
350
+ if parsl_resource_specification.get('mode') == rp.TASK_EXECUTABLE:
351
+ # These lines of code are from parsl/app/bash.py
352
+ try:
353
+ # Execute the func to get the command
354
+ bash_app = func(*args, **kwargs)
355
+ if not isinstance(bash_app, str):
356
+ raise ValueError("Expected a str for bash_app cmd,"
357
+ "got: {0}".format(type(bash_app)))
358
+ except AttributeError as e:
359
+ raise Exception("failed to obtain bash app cmd") from e
360
+
361
+ task.executable = bash_app
362
+ task.mode = rp.TASK_EXECUTABLE
363
+
364
+ # This is the default mode where the bash_app will be executed as
365
+ # as a single core process by RP. For cores > 1 the user must use
366
+ # above or use MPI functions if their code is Python.
367
+ else:
368
+ task.mode = rp.TASK_PROC
369
+ task.raptor_id = 'master.%06d' % (tid % self.n_masters)
370
+ task.executable = self._pack_and_apply_message(func, args, kwargs)
371
+
372
+ elif PYTHON in task_type or not task_type:
373
+ task.mode = rp.TASK_FUNCTION
374
+ task.raptor_id = 'master.%06d' % (tid % self.n_masters)
375
+ if kwargs.get('walltime'):
376
+ func = timeout(func, kwargs['walltime'])
377
+
378
+ # we process MPI function differently
379
+ if 'comm' in kwargs:
380
+ task.function = rp.PythonTask(func, *args, **kwargs)
381
+ else:
382
+ task.function = self._pack_and_apply_message(func, args, kwargs)
383
+
384
+ task.input_staging = self._stage_files(kwargs.get("inputs", []),
385
+ mode='in')
386
+ task.output_staging = self._stage_files(kwargs.get("outputs", []),
387
+ mode='out')
388
+
389
+ task.input_staging.extend(self._stage_files(list(args), mode='in'))
390
+
391
+ self._set_stdout_stderr(task, kwargs)
392
+
393
+ try:
394
+ task.verify()
395
+ except ru.typeddict.TDKeyError as e:
396
+ raise Exception(f'{e}. Please check Radical.Pilot TaskDescription documentation')
397
+
398
+ return task
399
+
400
+ def _pack_and_apply_message(self, func, args, kwargs):
401
+ try:
402
+ buffer = pack_apply_message(func, args, kwargs,
403
+ buffer_threshold=1024 * 1024)
404
+ task_func = rp.utils.serialize_bson(buffer)
405
+ except TypeError:
406
+ raise SerializationError(func.__name__)
407
+
408
+ return task_func
409
+
410
+ def _unpack_and_set_parsl_exception(self, parsl_task, exception):
411
+ try:
412
+ s = rp.utils.deserialize_bson(exception)
413
+ if isinstance(s, RemoteExceptionWrapper):
414
+ try:
415
+ s.reraise()
416
+ except Exception as e:
417
+ parsl_task.set_exception(e)
418
+ elif isinstance(s, Exception):
419
+ parsl_task.set_exception(s)
420
+ else:
421
+ raise ValueError("Unknown exception-like type received: {}".format(type(s)))
422
+ except Exception as e:
423
+ parsl_task.set_exception(
424
+ DeserializationError("Received exception, but handling also threw an exception: {}".format(e)))
425
+
426
+ def _set_stdout_stderr(self, task, kwargs):
427
+ """
428
+ set the stdout and stderr of a task
429
+ """
430
+ for k in ['stdout', 'stderr']:
431
+ k_val = kwargs.get(k, '')
432
+ if k_val:
433
+ # check the type of the stderr/out
434
+ if isinstance(k_val, File):
435
+ k_val = k_val.filepath
436
+ elif isinstance(k_val, PosixPath):
437
+ k_val = k_val.__str__()
438
+
439
+ # if the stderr/out has no path
440
+ # then we consider it local and
441
+ # we just set the path to the cwd
442
+ if '/' not in k_val:
443
+ k_val = CWD + '/' + k_val
444
+
445
+ # finally set the stderr/out to
446
+ # the desired name by the user
447
+ setattr(task, k, k_val)
448
+ task.sandbox = Path(k_val).parent.__str__()
449
+
450
+ def _stage_files(self, files, mode):
451
+ """
452
+ a function to stage list of input/output
453
+ files between two locations.
454
+ """
455
+ to_stage = []
456
+ files = [f for f in files if isinstance(f, File)]
457
+ for file in files:
458
+ if mode == 'in':
459
+ # a workaround RP not supporting
460
+ # staging https file
461
+ if file.scheme == 'https':
462
+ r = requests.get(file.url)
463
+ p = CWD + '/' + file.filename
464
+ with open(p, 'wb') as ff:
465
+ ff.write(r.content)
466
+ file = File(p)
467
+
468
+ f = {'source': file.url,
469
+ 'action': rp.TRANSFER}
470
+ to_stage.append(f)
471
+
472
+ elif mode == 'out':
473
+ # this indicates that the user
474
+ # did not provided a specific
475
+ # output file and RP will stage out
476
+ # the task.output from pilot://task_folder
477
+ # to the CWD or file.url
478
+ if '/' not in file.url:
479
+ f = {'source': file.filename,
480
+ 'target': file.url,
481
+ 'action': rp.TRANSFER}
482
+ to_stage.append(f)
483
+ else:
484
+ raise ValueError('unknown staging mode')
485
+
486
+ return to_stage
487
+
488
+ def _bulk_collector(self):
489
+
490
+ bulk = list()
491
+
492
+ while True:
493
+
494
+ now = time.time() # time of last submission
495
+
496
+ # collect tasks for min bulk time
497
+ # NOTE: total collect time could actually be max_time + min_time
498
+ while time.time() - now < self._max_bulk_time:
499
+
500
+ try:
501
+ task = self._bulk_queue.get(block=True,
502
+ timeout=self._min_bulk_time)
503
+ except queue.Empty:
504
+ task = None
505
+
506
+ if task:
507
+ bulk.append(task)
508
+
509
+ if len(bulk) >= self._max_bulk_size:
510
+ break
511
+
512
+ if bulk:
513
+ logger.debug('submit bulk: %d', len(bulk))
514
+ self.tmgr.submit_tasks(bulk)
515
+ bulk = list()
516
+
517
+ def submit(self, func, resource_specification, *args, **kwargs):
518
+ """
519
+ Submits tasks in stream mode or bulks (bulk mode)
520
+ to RADICAL-Pilot rp.TaskManager.
521
+ """
522
+ rp_tid = ru.generate_id('task.%(item_counter)06d', ru.ID_CUSTOM,
523
+ ns=self.session.uid)
524
+ parsl_tid = int(rp_tid.split('task.')[1])
525
+
526
+ logger.debug("got Task {0} from parsl-dfk".format(parsl_tid))
527
+ task = self.task_translate(parsl_tid, func, resource_specification, args, kwargs)
528
+
529
+ # assign task id for rp task
530
+ task.uid = rp_tid
531
+
532
+ # set the future with corresponding id
533
+ self.future_tasks[rp_tid] = Future()
534
+
535
+ if self.bulk_mode:
536
+ # push task to rp submit thread
537
+ self._bulk_queue.put(task)
538
+ else:
539
+ # submit the task to rp
540
+ logger.debug("put {0} to rp-TMGR".format(rp_tid))
541
+ self.tmgr.submit_tasks(task)
542
+
543
+ return self.future_tasks[rp_tid]
544
+
545
+ def shutdown(self, hub=True, targets='all', block=False):
546
+ """Shutdown the executor, including all RADICAL-Pilot components."""
547
+ logger.info("RadicalPilotExecutor shutdown")
548
+ self.session.close(download=True)
549
+
550
+ return True
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+
5
+ import radical.utils as ru
6
+ import radical.pilot as rp
7
+
8
+
9
+ # ------------------------------------------------------------------------------
10
+ #
11
+ if __name__ == '__main__':
12
+
13
+ # The purpose of this master is to (a) spawn a set or workers
14
+ # within the same allocation, (b) to distribute work items to
15
+ # those workers, and (c) to collect the responses again.
16
+ cfg_fname = str(sys.argv[1])
17
+ cfg = ru.Config(cfg=ru.read_json(cfg_fname))
18
+ cfg.rank = int(sys.argv[2])
19
+
20
+ worker_descr = cfg.worker_descr
21
+ n_workers = cfg.n_workers
22
+ gpus_per_node = cfg.gpus_per_node
23
+ cores_per_node = cfg.cores_per_node
24
+ nodes_per_worker = cfg.nodes_per_worker
25
+
26
+ # create a master class instance - this will establish communication
27
+ # to the pilot agent
28
+ master = rp.raptor.Master(cfg)
29
+
30
+ # insert `n` worker into the agent. The agent will schedule (place)
31
+ # those workers and execute them.
32
+ worker_descr['ranks'] = nodes_per_worker * cores_per_node
33
+ worker_descr['gpus_per_rank'] = nodes_per_worker * gpus_per_node
34
+ worker_ids = master.submit_workers(
35
+ [rp.TaskDescription(worker_descr) for _ in range(n_workers)])
36
+
37
+ # wait for all workers
38
+ master.wait_workers()
39
+ master.start()
40
+ master.join()
41
+
42
+ # ------------------------------------------------------------------------------
@@ -0,0 +1,165 @@
1
+ import sys
2
+ import json
3
+
4
+ from typing import List
5
+
6
+ _setup_paths: List[str]
7
+ try:
8
+ import radical.pilot as rp
9
+ import radical.utils as ru
10
+ except ImportError:
11
+ _setup_paths = []
12
+ else:
13
+ _setup_paths = [rp.sdist_path,
14
+ ru.sdist_path]
15
+
16
+
17
+ MPI = "mpi"
18
+ RP_ENV = "rp"
19
+ CLIENT = "client"
20
+ RPEX_ENV = "ve_rpex"
21
+ MPI_WORKER = "MPIWorker"
22
+ DEFAULT_WORKER = "DefaultWorker"
23
+
24
+
25
+ class ResourceConfig:
26
+ """
27
+ This ResourceConfig class is an abstraction of the resource
28
+ configuration of the RAPTOR layer in the RADICAL-Pilot runtime system.
29
+
30
+ This class sets up the default configuration values for the executor and
31
+ allows the user to specify different resource requirements flexibly.
32
+
33
+ For more information:
34
+ https://radicalpilot.readthedocs.io/en/stable/tutorials/raptor.html
35
+
36
+ Parameters
37
+ ----------
38
+ masters : int
39
+ The number of masters to be deployed by RAPTOR.
40
+ Default is 1.
41
+
42
+ workers : int
43
+ The number of workers to be deployed by RAPTOR.
44
+ Default is 1.
45
+
46
+ worker_gpus_per_node : int
47
+ The number of GPUs a worker will operate on per node.
48
+ Default is 0.
49
+
50
+ worker_cores_per_node : int
51
+ The number of CPU cores a worker will operate on per node.
52
+ Default is 4.
53
+
54
+ cores_per_master : int
55
+ The number of cores a master will operate on per node.
56
+ Default is 1.
57
+
58
+ nodes_per_worker : int
59
+ The number of nodes to be occupied by every worker.
60
+ Default is 1.
61
+
62
+ pilot_env_path : str
63
+ The path to an exisitng pilot environment.
64
+ Default is an empty string (RADICAL-Pilot will create one).
65
+
66
+ pilot_env_name : str
67
+ The name of the pilot environment.
68
+ Default is "ve_rpex".
69
+
70
+ pilot_env_pre_exec : list
71
+ List of commands to be executed before starting the pilot environment.
72
+ Default is an empty list.
73
+
74
+ pilot_env_type : str
75
+ The type of the pilot environment (e.g., 'venv', 'conda').
76
+ Default is "venv".
77
+
78
+ pilot_env_setup : list
79
+ List of setup commands/packages for the pilot environment.
80
+ Default setup includes "parsl", rp.sdist_path, and ru.sdist_path.
81
+
82
+ python_v : str
83
+ The Python version to be used in the pilot environment.
84
+ Default is determined by the system's Python version.
85
+
86
+ worker_type : str
87
+ The type of worker(s) to be deployed by RAPTOR on the compute
88
+ resources.
89
+ Default is "DefaultWorker".
90
+ """
91
+
92
+ masters: int = 1
93
+ workers: int = 1
94
+
95
+ worker_gpus_per_node: int = 0
96
+ worker_cores_per_node: int = 4
97
+
98
+ cores_per_master: int = 1
99
+ nodes_per_worker: int = 1
100
+
101
+ pilot_env_mode: str = CLIENT
102
+ pilot_env_path: str = ""
103
+ pilot_env_type: str = "venv"
104
+ pilot_env_name: str = RP_ENV
105
+ pilot_env_pre_exec: List[str] = []
106
+ pilot_env_setup: List[str] = _setup_paths
107
+
108
+ python_v: str = f'{sys.version_info[0]}.{sys.version_info[1]}'
109
+ worker_type: str = DEFAULT_WORKER
110
+
111
+ def _get_cfg_file(cls, path=None):
112
+
113
+ # Default ENV mode for RP is to reuse
114
+ # the client side. If this is not the case,
115
+ # then RP will create a new env named ve_rpex
116
+ # The user need to make sure that under:
117
+ # $HOME/.radical/pilot/configs/*_resource.json
118
+ # that virtenv_mode = local
119
+ if cls.pilot_env_mode != CLIENT:
120
+ cls.pilot_env_name = RPEX_ENV
121
+
122
+ if MPI in cls.worker_type.lower() and \
123
+ "mpi4py" not in cls.pilot_env_setup:
124
+ cls.pilot_env_setup.append("mpi4py")
125
+
126
+ cfg = {
127
+ 'n_masters': cls.masters,
128
+ 'n_workers': cls.workers,
129
+ 'gpus_per_node': cls.worker_gpus_per_node,
130
+ 'cores_per_node': cls.worker_cores_per_node,
131
+ 'cores_per_master': cls.cores_per_master,
132
+ 'nodes_per_worker': cls.nodes_per_worker,
133
+
134
+ 'pilot_env': {
135
+ "version": cls.python_v,
136
+ "name": cls.pilot_env_name,
137
+ "path": cls.pilot_env_path,
138
+ "type": cls.pilot_env_type,
139
+ "setup": cls.pilot_env_setup,
140
+ "pre_exec": cls.pilot_env_pre_exec
141
+ },
142
+
143
+ 'pilot_env_mode': cls.pilot_env_mode,
144
+
145
+ 'master_descr': {
146
+ "mode": rp.RAPTOR_MASTER,
147
+ "named_env": cls.pilot_env_name,
148
+ "executable": "python3 rpex_master.py",
149
+ },
150
+
151
+ 'worker_descr': {
152
+ "mode": rp.RAPTOR_WORKER,
153
+ "named_env": cls.pilot_env_name,
154
+ "raptor_file": "./rpex_worker.py",
155
+ "raptor_class": cls.worker_type if
156
+ cls.worker_type.lower() != MPI else MPI_WORKER,
157
+ }}
158
+
159
+ # Convert the class instance to a cfg file.
160
+ config_path = 'rpex.cfg'
161
+ if path:
162
+ config_path = path + '/' + config_path
163
+ with open(config_path, 'w') as f:
164
+ json.dump(cfg, f, indent=4)
165
+ return config_path
@@ -0,0 +1,61 @@
1
+ import sys
2
+ import radical.pilot as rp
3
+
4
+ import parsl.app.errors as pe
5
+ from parsl.app.bash import remote_side_bash_executor
6
+ from parsl.serialize import unpack_apply_message, serialize
7
+ from parsl.executors.high_throughput.process_worker_pool import execute_task
8
+
9
+
10
+ class ParslWorker:
11
+
12
+ def _dispatch_func(self, task):
13
+
14
+ try:
15
+ buffer = rp.utils.deserialize_bson(task['description']['function'])
16
+ result = execute_task(buffer)
17
+ val = str(serialize(result, buffer_threshold=1000000))
18
+ exc = (None, None)
19
+ ret = 0
20
+ out = None
21
+ err = None
22
+ except Exception:
23
+ val = None
24
+ exc = (rp.utils.serialize_bson(pe.RemoteExceptionWrapper(*sys.exc_info())), None)
25
+ ret = 1
26
+ out = None
27
+ err = None
28
+
29
+ return out, err, ret, val, exc
30
+
31
+ def _dispatch_proc(self, task):
32
+
33
+ try:
34
+ buffer = rp.utils.deserialize_bson(task['description']['executable'])
35
+ func, args, kwargs = unpack_apply_message(buffer, {}, copy=False)
36
+ ret = remote_side_bash_executor(func, *args, **kwargs)
37
+ exc = (None, None)
38
+ val = None
39
+ out = None
40
+ err = None
41
+ except Exception:
42
+ val = None
43
+ exc = (rp.utils.serialize_bson(pe.RemoteExceptionWrapper(*sys.exc_info())), None)
44
+ ret = 1
45
+ out = None
46
+ err = None
47
+
48
+ return out, err, ret, val, exc
49
+
50
+
51
+ class MPIWorker(rp.raptor.MPIWorker):
52
+ def _dispatch_func(self, task):
53
+ return super()._dispatch_func(task)
54
+
55
+
56
+ class DefaultWorker(rp.raptor.DefaultWorker):
57
+ def _dispatch_func(self, task):
58
+ return ParslWorker()._dispatch_func(task)
59
+
60
+ def _dispatch_proc(self, task):
61
+ return ParslWorker()._dispatch_proc(task)
@@ -6,7 +6,6 @@ from abc import abstractmethod, abstractproperty
6
6
  from concurrent.futures import Future
7
7
  from typing import List, Any, Dict, Optional, Tuple, Union, Callable
8
8
 
9
- import parsl # noqa F401
10
9
  from parsl.executors.base import ParslExecutor
11
10
  from parsl.executors.errors import BadStateException, ScalingFailed
12
11
  from parsl.jobs.states import JobStatus, JobState
@@ -1,9 +1,8 @@
1
1
  import logging
2
- import parsl # noqa F401 (used in string type annotation)
2
+ import parsl
3
3
  import time
4
4
  import zmq
5
- from typing import Dict, Sequence
6
- from typing import List # noqa F401 (used in type annotation)
5
+ from typing import Dict, List, Sequence
7
6
 
8
7
  from parsl.jobs.states import JobStatus, JobState
9
8
  from parsl.jobs.strategy import Strategy
@@ -0,0 +1,20 @@
1
+ import os
2
+
3
+ from parsl.config import Config
4
+ from parsl.executors.radical import RadicalPilotExecutor
5
+ from parsl.executors.radical import ResourceConfig
6
+
7
+
8
+ rpex_cfg = ResourceConfig()
9
+
10
+
11
+ def fresh_config():
12
+
13
+ return Config(
14
+ executors=[
15
+ RadicalPilotExecutor(
16
+ label='RPEXBulk',
17
+ rpex_cfg=rpex_cfg,
18
+ bulk_mode=True,
19
+ resource='local.localhost',
20
+ runtime=30, cores=4)])
@@ -0,0 +1,20 @@
1
+ import os
2
+ from parsl.config import Config
3
+
4
+
5
+ def fresh_config():
6
+ from parsl.executors.radical import ResourceConfig
7
+ from parsl.executors.radical import RadicalPilotExecutor
8
+
9
+ rpex_cfg = ResourceConfig()
10
+ rpex_cfg.worker_type = "MPI"
11
+ rpex_cfg.worker_cores_per_node = 7
12
+
13
+ return Config(
14
+ executors=[
15
+ RadicalPilotExecutor(
16
+ label='RPEXMPI',
17
+ rpex_cfg=rpex_cfg,
18
+ bulk_mode=True,
19
+ resource='local.localhost',
20
+ runtime=30, cores=8)])
@@ -5,7 +5,7 @@ import pytest
5
5
 
6
6
  import parsl
7
7
  from parsl.app.app import python_app
8
- from parsl.tests.configs.local_threads import fresh_config as local_config # noqa
8
+ from parsl.tests.configs.local_threads import fresh_config as local_config
9
9
 
10
10
 
11
11
  @python_app
File without changes
@@ -0,0 +1,27 @@
1
+ import parsl
2
+ import pytest
3
+
4
+ from parsl.tests.configs.local_radical_mpi import fresh_config as local_config
5
+
6
+
7
+ @parsl.python_app
8
+ def test_mpi_func(msg, sleep, comm=None, parsl_resource_specification={}):
9
+ import time
10
+ msg = 'hello %d/%d: %s' % (comm.rank, comm.size, msg)
11
+ time.sleep(sleep)
12
+ print(msg)
13
+ return comm.size
14
+
15
+
16
+ apps = []
17
+
18
+
19
+ @pytest.mark.local
20
+ def test_radical_mpi(n=7):
21
+ # rank size should be > 1 for the
22
+ # radical runtime system to run this function in MPI env
23
+ for i in range(2, n):
24
+ spec = {'ranks': i}
25
+ t = test_mpi_func(msg='mpi.func.%06d' % i, sleep=1, comm=None, parsl_resource_specification=spec)
26
+ apps.append(t)
27
+ assert [len(app.result()) for app in apps] == list(range(2, n))
@@ -4,7 +4,7 @@ import time
4
4
  import pytest
5
5
 
6
6
  import parsl
7
- from parsl.tests.configs.local_threads import fresh_config as local_config # noqa
7
+ from parsl.tests.configs.local_threads import fresh_config as local_config
8
8
 
9
9
 
10
10
  @parsl.python_app
parsl/version.py CHANGED
@@ -3,4 +3,4 @@
3
3
  Year.Month.Day[alpha/beta/..]
4
4
  Alphas will be numbered like this -> 2024.12.10a0
5
5
  """
6
- VERSION = '2023.11.13'
6
+ VERSION = '2023.11.20'
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: parsl
3
- Version: 2023.11.13
3
+ Version: 2023.11.20
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/2023.11.13.tar.gz
6
+ Download-URL: https://github.com/Parsl/parsl/archive/2023.11.20.tar.gz
7
7
  Author: The Parsl Team
8
8
  Author-email: parsl@googlegroups.com
9
9
  License: Apache 2.0
@@ -53,6 +53,7 @@ Requires-Dist: pyyaml ; extra == 'all'
53
53
  Requires-Dist: cffi ; extra == 'all'
54
54
  Requires-Dist: jsonschema ; extra == 'all'
55
55
  Requires-Dist: proxystore ; extra == 'all'
56
+ Requires-Dist: radical.pilot ; extra == 'all'
56
57
  Provides-Extra: aws
57
58
  Requires-Dist: boto3 ; extra == 'aws'
58
59
  Provides-Extra: azure
@@ -79,6 +80,8 @@ Provides-Extra: oauth_ssh
79
80
  Requires-Dist: oauth-ssh >=0.9 ; extra == 'oauth_ssh'
80
81
  Provides-Extra: proxystore
81
82
  Requires-Dist: proxystore ; extra == 'proxystore'
83
+ Provides-Extra: radical-pilot
84
+ Requires-Dist: radical.pilot ; extra == 'radical-pilot'
82
85
  Provides-Extra: visualization
83
86
  Requires-Dist: pydot ; extra == 'visualization'
84
87
  Requires-Dist: networkx <2.6,>=2.5 ; extra == 'visualization'
@@ -1,4 +1,4 @@
1
- parsl/__init__.py,sha256=Upt6uQ2ipDF5IV6nZ2angE91MPhxBmo6l75YVi6Mbro,1815
1
+ parsl/__init__.py,sha256=hq8rJmP59wzd9-yxaGcmq5gPpshOopH-Y1K0BkUBNY0,1843
2
2
  parsl/addresses.py,sha256=L4RjQ-jGY9RfT-hBpsGw1uCzWaIdrEKxcPWV-TkGJes,4767
3
3
  parsl/config.py,sha256=ysUWBfm9bygayHHdItaJbP4oozkHJJmVQVnWCt5igjE,6808
4
4
  parsl/errors.py,sha256=SzINzQFZDBDbj9l-DPQznD0TbGkNhHIRAPkcBCogf_A,1019
@@ -7,7 +7,7 @@ parsl/multiprocessing.py,sha256=uY64wcQmWt2rgylQm4lmr3HE8AxwFGeQQj4l1jKnnrY,1970
7
7
  parsl/process_loggers.py,sha256=1G3Rfrh5wuZNo2X03grG4kTYPGOxz7hHCyG6L_A3b0A,1137
8
8
  parsl/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  parsl/utils.py,sha256=_flbNpTu6IXHbzIyE5JkUbOBIK4poc1R1bjBtwJUVdo,11622
10
- parsl/version.py,sha256=xpF7zP_5i_aMX1CNkBN0gPhAOjA5nZ7j1nKxfeubjG8,131
10
+ parsl/version.py,sha256=BWsiVCPivo19VLVK8LWkn31R3xA4BSgmmc-A9-ilTX4,131
11
11
  parsl/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  parsl/app/app.py,sha256=wAHchJetgnicT1pn0NJKDeDX0lV3vDFlG8cQd_Ciax4,8522
13
13
  parsl/app/bash.py,sha256=bx9x1XFwkOTpZZD3CPwnVL9SyNRDjbUGtOnuGLvxN_8,5396
@@ -70,9 +70,9 @@ parsl/dataflow/rundirs.py,sha256=XKmBZpBEIsGACBhYOkbbs2e5edC0pQegJcSlk4FWeag,115
70
70
  parsl/dataflow/states.py,sha256=hV6mfv-y4A6xrujeQglcomnfEs7y3Xm2g6JFwC6dvgQ,2612
71
71
  parsl/dataflow/taskrecord.py,sha256=QlsOUacZZq-sU25P-VmA0xDVUktRFEVRmJoWv3Oyzz0,3105
72
72
  parsl/executors/__init__.py,sha256=J50N97Nm9YRjz6K0oNXDxUYIsDjL43_tp3LVb2w7n-M,381
73
- parsl/executors/base.py,sha256=dINuxWwJnd-As238pNwUPQ_PrCj-kMseDqeaMEAUmeA,4367
73
+ parsl/executors/base.py,sha256=DyKzXZztPagh9xQykTUuJRMR9g3i6qkbZi8YpWrIfXM,4340
74
74
  parsl/executors/errors.py,sha256=xVswxgi7vmJcUMCeYDAPK8sQT2kHFFROVoOr0dnmcWE,2098
75
- parsl/executors/status_handling.py,sha256=KSWUEnhC1dkpr94wWsBApsmrvOVn-WRJCoIUUNnsTw4,10966
75
+ parsl/executors/status_handling.py,sha256=GNBYrAhOpHSnmJA0NouK9by_uJxXn8vlPgBhcp0pSFo,10940
76
76
  parsl/executors/threads.py,sha256=bMU3JFghm17Lpcua13pr3NgQhkUDDc2mqvF2yJBrVNQ,3353
77
77
  parsl/executors/flux/__init__.py,sha256=P9grTTeRPXfqXurFhlSS7XhmE6tTbnCnyQ1f9b-oYHE,136
78
78
  parsl/executors/flux/execute_parsl_task.py,sha256=yUG_WjZLcX8LrgPl26mpEBWZhQMlVNbRLGu08yIjdf4,1553
@@ -80,13 +80,18 @@ parsl/executors/flux/executor.py,sha256=tf9xPmWgEsgEjzs89dJ-sMx-QaqRpM1R1crX3tp0
80
80
  parsl/executors/flux/flux_instance_manager.py,sha256=tTEOATClm9SwdgLeBRWPC6D55iNDuh0YxqJOw3c3eQ4,2036
81
81
  parsl/executors/high_throughput/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
82
82
  parsl/executors/high_throughput/errors.py,sha256=vl69wLuVOplbKxHI9WphEGBExHWkTn5n8T9QhBXuNH0,380
83
- parsl/executors/high_throughput/executor.py,sha256=583WqjGRa3G5yHe9uElYQecYrok5LB4txwFXe0DCjyA,33845
83
+ parsl/executors/high_throughput/executor.py,sha256=LXKYFoFqtCQ5H-B3_NI-Syncln8NlwcfWF01sd-eQcc,33806
84
84
  parsl/executors/high_throughput/interchange.py,sha256=tX_EvQf7WkSKMJG-TNmA-WADjhtKZqviYpM406Td4dA,29334
85
85
  parsl/executors/high_throughput/manager_record.py,sha256=T8-JVMfDJU6SJfzJRooD0mO8AHGMXlcn3PBOM0m_vng,366
86
86
  parsl/executors/high_throughput/monitoring_info.py,sha256=3gQpwQjjNDEBz0cQqJZB6hRiwLiWwXs83zkQDmbOwxY,297
87
87
  parsl/executors/high_throughput/probe.py,sha256=lvnuf-vBv57tHvFh-J51F9sDYBES7jCgs6KYgWvmKRs,2749
88
88
  parsl/executors/high_throughput/process_worker_pool.py,sha256=SFDDeDmKmVbP94rKDG9cV1tnO75zoXj4-InakIWCRZk,33032
89
89
  parsl/executors/high_throughput/zmq_pipes.py,sha256=3-UrPu4DlXYb6JufjBcENspLd31Qk5URDaZP6IyC6SM,5720
90
+ parsl/executors/radical/__init__.py,sha256=CKbtV2numw5QvgIBq1htMUrt9TqDCIC2zifyf2svTNU,186
91
+ parsl/executors/radical/executor.py,sha256=ZYycq58jXlBlhmIO1355JCK1xIJHkspiy62NN1XiMYQ,20729
92
+ parsl/executors/radical/rpex_master.py,sha256=nMGxYWw3r-8_vZVnEwfB5eCfdTqXkeQDP5yvU0jXgc8,1368
93
+ parsl/executors/radical/rpex_resources.py,sha256=d7QlJYBkWE-lPauetroEGIbA8RyVILA699LG0uNviws,4960
94
+ parsl/executors/radical/rpex_worker.py,sha256=xPkjYdlXc3kjN-95NTMiQ5s-yU3JFDCA3sruPTxF3XU,1834
90
95
  parsl/executors/taskvine/__init__.py,sha256=sWIJdvSLgQKul9dlSjIkNat7yBDgU3SrBF3X2yhT86E,293
91
96
  parsl/executors/taskvine/errors.py,sha256=MNS_NjpvHjwevQXOjqjSEBFroqEWi-LT1ZEVZ2C5Dx0,652
92
97
  parsl/executors/taskvine/exec_parsl_function.py,sha256=oUAKbPWwpbzWwQ47bZQlVDxS8txhnhPsonMf3AOEMGQ,7085
@@ -105,7 +110,7 @@ parsl/executors/workqueue/parsl_coprocess_stub.py,sha256=_bJmpPIgL42qM6bVzeEKt1M
105
110
  parsl/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
111
  parsl/jobs/error_handlers.py,sha256=WYn69jtWgfEsThCMxkGvJ2qoCfebc4IGd4JEolj2ww8,2279
107
112
  parsl/jobs/errors.py,sha256=cpSQXCrlKtuHsQf7usjF-lX8XsDkFnE5kWpmFjiN6OU,178
108
- parsl/jobs/job_status_poller.py,sha256=RmbBCyEYJXPas6MTMCDQWKy3JG6CiX6gJRQJWBLzpRo,4964
113
+ parsl/jobs/job_status_poller.py,sha256=Q57s6FfWP7LjfUSPAOZ0sT59zpUxnyFNE5BZu-xRNvM,4861
109
114
  parsl/jobs/states.py,sha256=olCTMvAGRSDKLxdpH5ZhtwwSLbqDhJPCUJJyTqPkMhk,3809
110
115
  parsl/jobs/strategy.py,sha256=9V07D8bydpyxvNNRH89JZa0Pt-bjjowrSmCc5mv6awY,12903
111
116
  parsl/launchers/__init__.py,sha256=k8zAB3IBP-brfqXUptKwGkvsIRaXjAJZNBJa2XVtY1A,546
@@ -214,6 +219,8 @@ parsl/tests/configs/htex_local_alternate.py,sha256=6FsizxGFamcY_GhvJL4dHEXkchAAe
214
219
  parsl/tests/configs/htex_local_intask_staging.py,sha256=RZfHQbSN_GcAKssxZgMG6uD_VE9l2X1VMZ5eyqpU-d4,860
215
220
  parsl/tests/configs/htex_local_rsync_staging.py,sha256=KL76K8gEgbdf5S9RGDStBWrKteEY3B_2HuXVHF2b5xI,914
216
221
  parsl/tests/configs/local_adhoc.py,sha256=y91RPFcKFcH--9oljkRywxwy5KgEo6JwxRkFt97YDqM,443
222
+ parsl/tests/configs/local_radical.py,sha256=-fk0vRUsYETBirm_PHSxBxu8qMwUwqidV_HarDxUZpQ,489
223
+ parsl/tests/configs/local_radical_mpi.py,sha256=K6V2HbARujaow5DBAUYSIWt1RaYbt898FCVe7UJ5Ckw,570
217
224
  parsl/tests/configs/local_threads.py,sha256=oEnQSlom_JMLFX9_Ln49JAfOP3nSMbw8gTaDJo_NYfo,202
218
225
  parsl/tests/configs/local_threads_checkpoint.py,sha256=Ex7CI1Eo6wVRsem9uXTtbVJrkKc_vOYlVvCNa2RLpIo,286
219
226
  parsl/tests/configs/local_threads_checkpoint_dfk_exit.py,sha256=ECL1n0uBsXDuW3sLCmjiwe8s3Xd7EFIj5wt446w6bh4,254
@@ -351,7 +358,7 @@ parsl/tests/test_python_apps/test_fail.py,sha256=0Gld8LS6NB0Io1bU82vVR73twkuL5nW
351
358
  parsl/tests/test_python_apps/test_fibonacci_iterative.py,sha256=ly2s5HuB9R53Z2FM_zy0WWdOk01iVhgcwSpQyK6ErIY,573
352
359
  parsl/tests/test_python_apps/test_fibonacci_recursive.py,sha256=q7LMFcu_pJSNPdz8iY0UiRoIweEWIBGwMjQffHWAuDc,592
353
360
  parsl/tests/test_python_apps/test_futures.py,sha256=ye0i4pQz6uPflOOxkJ8BPM1xSphmLthTxaL3OrlY17o,2354
354
- parsl/tests/test_python_apps/test_garbage_collect.py,sha256=60XA6SuG9jhuIIQuNIy7haS4_MxT1_mdVhkqyAA_aTc,1084
361
+ parsl/tests/test_python_apps/test_garbage_collect.py,sha256=RPntrLuzPkeNbhS7mmqEnHbyOcuV1YVppgZ8BaX-h84,1076
355
362
  parsl/tests/test_python_apps/test_import_fail.py,sha256=Vd8IMa_UsbHYkr3IGnS-rgGb6zKxB1tOTqMZY5lc_xY,691
356
363
  parsl/tests/test_python_apps/test_join.py,sha256=g4VKJwKdIqDLbuNCkIpjVfME0q4mAci_iQ8wAx3uVUc,2679
357
364
  parsl/tests/test_python_apps/test_lifted.py,sha256=WELQv7VueH6I7d6hhMCWQi6s-0sMH8ek_2v4KH0cTFc,3311
@@ -368,9 +375,11 @@ parsl/tests/test_python_apps/test_pipeline.py,sha256=vl5bDAzwW0qoTIarzdkFv7cAuaI
368
375
  parsl/tests/test_python_apps/test_simple.py,sha256=LYGjdHvRizTpYzZePPvwKSPwrr2MPuMggYTknHeNhWM,733
369
376
  parsl/tests/test_python_apps/test_timeout.py,sha256=uENfT-1DharQkqkeG7a89E-gU1gjE7ATJrBZGUKvZSA,998
370
377
  parsl/tests/test_python_apps/test_type5.py,sha256=kUyA1NuFu-DDXsJNNvJLZVyewZBt7QAOhcGm2DWFTQw,777
378
+ parsl/tests/test_radical/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
379
+ parsl/tests/test_radical/test_mpi_funcs.py,sha256=vCb5u6fjafEhrPgxwOzUjGXC3O6Yjf3lSYaxUE98pKg,744
371
380
  parsl/tests/test_regression/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
372
381
  parsl/tests/test_regression/test_1480.py,sha256=HNhuw7OYkBGMhN--XgKIl2JPHUj_hXlgL74oS3FqWk4,545
373
- parsl/tests/test_regression/test_1606_wait_for_current_tasks.py,sha256=cmnMtErEosFL6sjgy1bwyheL_gevv7Fj1-Jz6h63GwM,1142
382
+ parsl/tests/test_regression/test_1606_wait_for_current_tasks.py,sha256=frqPtaiVysevj9nCWoQlAeh9K1jQO5zaahr9ev_Mx_0,1134
374
383
  parsl/tests/test_regression/test_1653.py,sha256=ki75gl4Sn5nm26r_6qpJOqxrN5UjTWzViVikU0-Ef24,563
375
384
  parsl/tests/test_regression/test_221.py,sha256=jOS0EVu_2sbh10eg5hnivPvhNt0my_50vQ7jQYS1Bfg,520
376
385
  parsl/tests/test_regression/test_226.py,sha256=9EumcLPJzvbD0IUD6-LmPFbf86gaA1Kj8J8J4P4XdTY,1087
@@ -404,12 +413,12 @@ parsl/tests/test_threads/test_configs.py,sha256=QA9YjIMAtZ2jmkfOWqBzEfzQQcFVCDiz
404
413
  parsl/tests/test_threads/test_lazy_errors.py,sha256=nGhYfCMHFZYSy6YJ4gnAmiLl9SfYs0WVnuvj8DXQ9bw,560
405
414
  parsl/usage_tracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
406
415
  parsl/usage_tracking/usage.py,sha256=TEuAIm_U_G2ojZxvd0bbVa6gZlU61_mVRa2yJC9mGiI,7555
407
- parsl-2023.11.13.data/scripts/exec_parsl_function.py,sha256=NtWNeBvRqksej38eRPw8zPBJ1CeW6vgaitve0tfz_qc,7801
408
- parsl-2023.11.13.data/scripts/parsl_coprocess.py,sha256=2pS8OUjBTtOO__VZy-OhUUhr72dhgRpJBNbwqm0fzn4,5439
409
- parsl-2023.11.13.data/scripts/process_worker_pool.py,sha256=I-hBCzHA9eiTDjyE5W2uYwLi4A0rnKOBSWpicIcL4g4,33018
410
- parsl-2023.11.13.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
411
- parsl-2023.11.13.dist-info/METADATA,sha256=1wOX7YXt8TRtNAiMXuGhGhPe0V__VLumpr49LcuAXp4,3686
412
- parsl-2023.11.13.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
413
- parsl-2023.11.13.dist-info/entry_points.txt,sha256=XqnsWDYoEcLbsMcpnYGKLEnSBmaIe1YoM5YsBdJG2tI,176
414
- parsl-2023.11.13.dist-info/top_level.txt,sha256=PIheYoUFQtF2icLsgOykgU-Cjuwr2Oi6On2jo5RYgRM,6
415
- parsl-2023.11.13.dist-info/RECORD,,
416
+ parsl-2023.11.20.data/scripts/exec_parsl_function.py,sha256=NtWNeBvRqksej38eRPw8zPBJ1CeW6vgaitve0tfz_qc,7801
417
+ parsl-2023.11.20.data/scripts/parsl_coprocess.py,sha256=2pS8OUjBTtOO__VZy-OhUUhr72dhgRpJBNbwqm0fzn4,5439
418
+ parsl-2023.11.20.data/scripts/process_worker_pool.py,sha256=I-hBCzHA9eiTDjyE5W2uYwLi4A0rnKOBSWpicIcL4g4,33018
419
+ parsl-2023.11.20.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
420
+ parsl-2023.11.20.dist-info/METADATA,sha256=NwTE626WKDg4f2EEkgiSNKby3oWEcznmmnpNiojiw9I,3818
421
+ parsl-2023.11.20.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
422
+ parsl-2023.11.20.dist-info/entry_points.txt,sha256=XqnsWDYoEcLbsMcpnYGKLEnSBmaIe1YoM5YsBdJG2tI,176
423
+ parsl-2023.11.20.dist-info/top_level.txt,sha256=PIheYoUFQtF2icLsgOykgU-Cjuwr2Oi6On2jo5RYgRM,6
424
+ parsl-2023.11.20.dist-info/RECORD,,