parsl 2025.5.26__py3-none-any.whl → 2025.6.9__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/executors/high_throughput/executor.py +4 -2
- parsl/executors/high_throughput/interchange.py +69 -42
- parsl/monitoring/radios/base.py +0 -3
- parsl/providers/slurm/slurm.py +47 -14
- parsl/tests/conftest.py +15 -4
- parsl/tests/test_error_handling/test_resource_spec.py +2 -2
- parsl/tests/test_htex/test_priority_queue.py +70 -0
- parsl/tests/test_htex/test_resource_spec_validation.py +7 -0
- parsl/tests/test_monitoring/test_basic.py +65 -11
- parsl/tests/test_scaling/test_scale_down.py +1 -0
- parsl/usage_tracking/usage.py +1 -0
- parsl/version.py +1 -1
- {parsl-2025.5.26.data → parsl-2025.6.9.data}/scripts/interchange.py +69 -42
- {parsl-2025.5.26.dist-info → parsl-2025.6.9.dist-info}/METADATA +3 -2
- {parsl-2025.5.26.dist-info → parsl-2025.6.9.dist-info}/RECORD +22 -21
- {parsl-2025.5.26.data → parsl-2025.6.9.data}/scripts/exec_parsl_function.py +0 -0
- {parsl-2025.5.26.data → parsl-2025.6.9.data}/scripts/parsl_coprocess.py +0 -0
- {parsl-2025.5.26.data → parsl-2025.6.9.data}/scripts/process_worker_pool.py +0 -0
- {parsl-2025.5.26.dist-info → parsl-2025.6.9.dist-info}/LICENSE +0 -0
- {parsl-2025.5.26.dist-info → parsl-2025.6.9.dist-info}/WHEEL +0 -0
- {parsl-2025.5.26.dist-info → parsl-2025.6.9.dist-info}/entry_points.txt +0 -0
- {parsl-2025.5.26.dist-info → parsl-2025.6.9.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ import warnings
|
|
8
8
|
from collections import defaultdict
|
9
9
|
from concurrent.futures import Future
|
10
10
|
from dataclasses import dataclass
|
11
|
-
from typing import Callable, Dict, List, Optional, Sequence,
|
11
|
+
from typing import Callable, Dict, List, Optional, Sequence, Tuple, Union
|
12
12
|
|
13
13
|
import typeguard
|
14
14
|
|
@@ -363,7 +363,9 @@ class HighThroughputExecutor(BlockProviderExecutor, RepresentationMixin, UsageIn
|
|
363
363
|
|
364
364
|
def validate_resource_spec(self, resource_specification: dict):
|
365
365
|
if resource_specification:
|
366
|
-
|
366
|
+
"""HTEX supports the following *Optional* resource specifications:
|
367
|
+
priority: lower value is higher priority"""
|
368
|
+
acceptable_fields = {'priority'} # add new resource spec field names here to make htex accept them
|
367
369
|
keys = set(resource_specification.keys())
|
368
370
|
invalid_keys = keys - acceptable_fields
|
369
371
|
if invalid_keys:
|
@@ -5,13 +5,13 @@ import logging
|
|
5
5
|
import os
|
6
6
|
import pickle
|
7
7
|
import platform
|
8
|
-
import queue
|
9
8
|
import sys
|
10
9
|
import threading
|
11
10
|
import time
|
12
11
|
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, cast
|
13
12
|
|
14
13
|
import zmq
|
14
|
+
from sortedcontainers import SortedList
|
15
15
|
|
16
16
|
from parsl import curvezmq
|
17
17
|
from parsl.addresses import tcp_url
|
@@ -131,7 +131,7 @@ class Interchange:
|
|
131
131
|
self.hub_address = hub_address
|
132
132
|
self.hub_zmq_port = hub_zmq_port
|
133
133
|
|
134
|
-
self.pending_task_queue:
|
134
|
+
self.pending_task_queue: SortedList[Any] = SortedList(key=lambda tup: (tup[0], tup[1]))
|
135
135
|
|
136
136
|
# count of tasks that have been received from the submit side
|
137
137
|
self.task_counter = 0
|
@@ -196,13 +196,12 @@ class Interchange:
|
|
196
196
|
eg. [{'task_id':<x>, 'buffer':<buf>} ... ]
|
197
197
|
"""
|
198
198
|
tasks = []
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
tasks.append(x)
|
199
|
+
try:
|
200
|
+
for _ in range(count):
|
201
|
+
*_, task = self.pending_task_queue.pop()
|
202
|
+
tasks.append(task)
|
203
|
+
except IndexError:
|
204
|
+
pass
|
206
205
|
|
207
206
|
return tasks
|
208
207
|
|
@@ -342,8 +341,15 @@ class Interchange:
|
|
342
341
|
if self.task_incoming in self.socks and self.socks[self.task_incoming] == zmq.POLLIN:
|
343
342
|
logger.debug("start task_incoming section")
|
344
343
|
msg = self.task_incoming.recv_pyobj()
|
344
|
+
|
345
|
+
# Process priority, higher number = lower priority
|
346
|
+
resource_spec = msg.get('resource_spec', {})
|
347
|
+
priority = resource_spec.get('priority', float('inf'))
|
348
|
+
queue_entry = (-priority, -self.task_counter, msg)
|
349
|
+
|
345
350
|
logger.debug("putting message onto pending_task_queue")
|
346
|
-
|
351
|
+
|
352
|
+
self.pending_task_queue.add(queue_entry)
|
347
353
|
self.task_counter += 1
|
348
354
|
logger.debug(f"Fetched {self.task_counter} tasks so far")
|
349
355
|
|
@@ -378,23 +384,24 @@ class Interchange:
|
|
378
384
|
return
|
379
385
|
|
380
386
|
if msg['type'] == 'registration':
|
381
|
-
|
382
|
-
self.
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
387
|
+
ix_minor_py = self.current_platform['python_v'].rsplit(".", 1)[0]
|
388
|
+
ix_parsl_v = self.current_platform['parsl_v']
|
389
|
+
mgr_minor_py = msg['python_v'].rsplit(".", 1)[0]
|
390
|
+
mgr_parsl_v = msg['parsl_v']
|
391
|
+
|
392
|
+
m = ManagerRecord(
|
393
|
+
block_id=None,
|
394
|
+
start_time=msg['start_time'],
|
395
|
+
tasks=[],
|
396
|
+
worker_count=0,
|
397
|
+
max_capacity=0,
|
398
|
+
active=True,
|
399
|
+
draining=False,
|
400
|
+
last_heartbeat=time.time(),
|
401
|
+
idle_since=time.time(),
|
402
|
+
parsl_version=mgr_parsl_v,
|
403
|
+
python_version=msg['python_v'],
|
404
|
+
)
|
398
405
|
|
399
406
|
# m is a ManagerRecord, but msg is a dict[Any,Any] and so can
|
400
407
|
# contain arbitrary fields beyond those in ManagerRecord (and
|
@@ -405,23 +412,43 @@ class Interchange:
|
|
405
412
|
logger.info(f"Registration info for manager {manager_id!r}: {msg}")
|
406
413
|
self._send_monitoring_info(monitoring_radio, m)
|
407
414
|
|
408
|
-
if (
|
409
|
-
msg['parsl_v'] != self.current_platform['parsl_v']):
|
410
|
-
logger.error(f"Manager {manager_id!r} has incompatible version info with the interchange")
|
411
|
-
logger.debug("Setting kill event")
|
415
|
+
if (mgr_minor_py, mgr_parsl_v) != (ix_minor_py, ix_parsl_v):
|
412
416
|
kill_event.set()
|
413
|
-
e = VersionMismatch(
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
417
|
+
e = VersionMismatch(
|
418
|
+
f"py.v={ix_minor_py} parsl.v={ix_parsl_v}",
|
419
|
+
f"py.v={mgr_minor_py} parsl.v={mgr_parsl_v}",
|
420
|
+
)
|
421
|
+
result_package = {
|
422
|
+
'type': 'result',
|
423
|
+
'task_id': -1,
|
424
|
+
'exception': serialize_object(e),
|
425
|
+
}
|
419
426
|
pkl_package = pickle.dumps(result_package)
|
420
427
|
self.results_outgoing.send(pkl_package)
|
421
|
-
logger.error(
|
428
|
+
logger.error(
|
429
|
+
"Manager has incompatible version info with the interchange;"
|
430
|
+
" sending failure reports and shutting down:"
|
431
|
+
f"\n Interchange: {e.interchange_version}"
|
432
|
+
f"\n Manager: {e.manager_version}"
|
433
|
+
)
|
434
|
+
|
422
435
|
else:
|
423
|
-
|
424
|
-
|
436
|
+
# We really should update the associated data structure; but not
|
437
|
+
# at this time. *kicks can down the road*
|
438
|
+
assert m['block_id'] is not None, "Verified externally currently"
|
439
|
+
|
440
|
+
# set up entry only if we accept the registration
|
441
|
+
self._ready_managers[manager_id] = m
|
442
|
+
self.connected_block_history.append(m['block_id'])
|
443
|
+
|
444
|
+
interesting_managers.add(manager_id)
|
445
|
+
|
446
|
+
logger.info(
|
447
|
+
f"Registered manager {manager_id!r} (py{mgr_minor_py},"
|
448
|
+
f" {mgr_parsl_v}) and added to ready queue"
|
449
|
+
)
|
450
|
+
logger.debug("Manager %r -> %s", manager_id, m)
|
451
|
+
|
425
452
|
elif msg['type'] == 'heartbeat':
|
426
453
|
manager = self._ready_managers.get(manager_id)
|
427
454
|
if manager:
|
@@ -461,10 +488,10 @@ class Interchange:
|
|
461
488
|
len(self._ready_managers)
|
462
489
|
)
|
463
490
|
|
464
|
-
if interesting_managers and
|
491
|
+
if interesting_managers and self.pending_task_queue:
|
465
492
|
shuffled_managers = self.manager_selector.sort_managers(self._ready_managers, interesting_managers)
|
466
493
|
|
467
|
-
while shuffled_managers and
|
494
|
+
while shuffled_managers and self.pending_task_queue: # cf. the if statement above...
|
468
495
|
manager_id = shuffled_managers.pop()
|
469
496
|
m = self._ready_managers[manager_id]
|
470
497
|
tasks_inflight = len(m['tasks'])
|
parsl/monitoring/radios/base.py
CHANGED
parsl/providers/slurm/slurm.py
CHANGED
@@ -2,6 +2,7 @@ import logging
|
|
2
2
|
import math
|
3
3
|
import os
|
4
4
|
import re
|
5
|
+
import sys
|
5
6
|
import time
|
6
7
|
from typing import Any, Dict, Optional
|
7
8
|
|
@@ -50,6 +51,28 @@ squeue_translate_table = {
|
|
50
51
|
}
|
51
52
|
|
52
53
|
|
54
|
+
if sys.version_info < (3, 12):
|
55
|
+
from itertools import islice
|
56
|
+
from typing import Iterable
|
57
|
+
|
58
|
+
def batched(
|
59
|
+
iterable: Iterable[tuple[object, Any]], n: int, *, strict: bool = False
|
60
|
+
):
|
61
|
+
"""Batched
|
62
|
+
Turns a list into a batch of size n. This code is from
|
63
|
+
https://docs.python.org/3.12/library/itertools.html#itertools.batched
|
64
|
+
and in versions 3.12+ this can be replaced with
|
65
|
+
itertools.batched
|
66
|
+
"""
|
67
|
+
if n < 1:
|
68
|
+
raise ValueError("n must be at least one")
|
69
|
+
iterator = iter(iterable)
|
70
|
+
while batch := tuple(islice(iterator, n)):
|
71
|
+
yield batch
|
72
|
+
else:
|
73
|
+
from itertools import batched
|
74
|
+
|
75
|
+
|
53
76
|
class SlurmProvider(ClusterProvider, RepresentationMixin):
|
54
77
|
"""Slurm Execution Provider
|
55
78
|
|
@@ -99,6 +122,12 @@ class SlurmProvider(ClusterProvider, RepresentationMixin):
|
|
99
122
|
symbolic group for the job ID.
|
100
123
|
worker_init : str
|
101
124
|
Command to be run before starting a worker, such as 'module load Anaconda; source activate env'.
|
125
|
+
cmd_timeout : int (Default = 10)
|
126
|
+
Number of seconds to wait for slurm commands to finish. For schedulers with many this
|
127
|
+
may need to be increased to wait longer for scheduler information.
|
128
|
+
status_batch_size: int (Default = 50)
|
129
|
+
Number of jobs to batch together in calls to the scheduler status. For schedulers
|
130
|
+
with many jobs this may need to be decreased to get jobs in smaller batches.
|
102
131
|
exclusive : bool (Default = True)
|
103
132
|
Requests nodes which are not shared with other running jobs.
|
104
133
|
launcher : Launcher
|
@@ -127,6 +156,7 @@ class SlurmProvider(ClusterProvider, RepresentationMixin):
|
|
127
156
|
regex_job_id: str = r"Submitted batch job (?P<id>\S*)",
|
128
157
|
worker_init: str = '',
|
129
158
|
cmd_timeout: int = 10,
|
159
|
+
status_batch_size: int = 50,
|
130
160
|
exclusive: bool = True,
|
131
161
|
launcher: Launcher = SingleNodeLauncher()):
|
132
162
|
label = 'slurm'
|
@@ -148,6 +178,8 @@ class SlurmProvider(ClusterProvider, RepresentationMixin):
|
|
148
178
|
self.qos = qos
|
149
179
|
self.constraint = constraint
|
150
180
|
self.clusters = clusters
|
181
|
+
# Used to batch requests to sacct/squeue for long jobs lists
|
182
|
+
self.status_batch_size = status_batch_size
|
151
183
|
self.scheduler_options = scheduler_options + '\n'
|
152
184
|
if exclusive:
|
153
185
|
self.scheduler_options += "#SBATCH --exclusive\n"
|
@@ -199,22 +231,23 @@ class SlurmProvider(ClusterProvider, RepresentationMixin):
|
|
199
231
|
Returns:
|
200
232
|
[status...] : Status list of all jobs
|
201
233
|
'''
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
if not job_id_list:
|
206
|
-
logger.debug('No active jobs, skipping status update')
|
234
|
+
active_jobs = {jid: job for jid, job in self.resources.items() if not job["status"].terminal}
|
235
|
+
if len(active_jobs) == 0:
|
236
|
+
logger.debug("No active jobs, skipping status update")
|
207
237
|
return
|
208
238
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
239
|
+
stdout = ""
|
240
|
+
for job_batch in batched(active_jobs.items(), self.status_batch_size):
|
241
|
+
job_id_list = ",".join(jid for jid, job in job_batch if not job["status"].terminal)
|
242
|
+
cmd = self._cmd.format(job_id_list)
|
243
|
+
logger.debug("Executing %s", cmd)
|
244
|
+
retcode, batch_stdout, batch_stderr = self.execute_wait(cmd)
|
245
|
+
logger.debug(f"sacct/squeue returned {retcode} {batch_stdout} {batch_stderr}")
|
246
|
+
stdout += batch_stdout
|
247
|
+
# Execute_wait failed. Do no update
|
248
|
+
if retcode != 0:
|
249
|
+
logger.warning("sacct/squeue failed with non-zero exit code {}".format(retcode))
|
250
|
+
return
|
218
251
|
|
219
252
|
jobs_missing = set(self.resources.keys())
|
220
253
|
for line in stdout.split('\n'):
|
parsl/tests/conftest.py
CHANGED
@@ -167,6 +167,14 @@ def pytest_configure(config):
|
|
167
167
|
'markers',
|
168
168
|
'issue_3620: Marks tests that do not work correctly on GlobusComputeExecutor (ref: issue 3620)'
|
169
169
|
)
|
170
|
+
config.addinivalue_line(
|
171
|
+
'markers',
|
172
|
+
'workqueue: Marks local tests that require a working Work Queue installation'
|
173
|
+
)
|
174
|
+
config.addinivalue_line(
|
175
|
+
'markers',
|
176
|
+
'taskvine: Marks local tests that require a working Task Vine installation'
|
177
|
+
)
|
170
178
|
|
171
179
|
|
172
180
|
@pytest.fixture(autouse=True, scope='session')
|
@@ -400,13 +408,13 @@ def try_assert():
|
|
400
408
|
check_period_ms: int = 20,
|
401
409
|
):
|
402
410
|
tb = create_traceback(start=1)
|
403
|
-
timeout_s = abs(timeout_ms) / 1000.0
|
404
411
|
check_period_s = abs(check_period_ms) / 1000.0
|
405
412
|
if attempts > 0:
|
406
413
|
for _attempt_no in range(attempts):
|
414
|
+
time.sleep(random.random() * check_period_s) # jitter
|
415
|
+
check_period_s *= 2
|
407
416
|
if test_func():
|
408
417
|
return
|
409
|
-
time.sleep(check_period_s)
|
410
418
|
else:
|
411
419
|
att_fail = (
|
412
420
|
f"\n (Still failing after attempt limit [{attempts}], testing"
|
@@ -415,12 +423,15 @@ def try_assert():
|
|
415
423
|
exc = AssertionError(f"{str(fail_msg)}{att_fail}".strip())
|
416
424
|
raise exc.with_traceback(tb)
|
417
425
|
|
418
|
-
elif
|
426
|
+
elif timeout_ms > 0:
|
427
|
+
timeout_s = abs(timeout_ms) / 1000.0
|
419
428
|
end = time.monotonic() + timeout_s
|
420
429
|
while time.monotonic() < end:
|
430
|
+
wait_for = random.random() * check_period_s # jitter
|
431
|
+
time.sleep(min(wait_for, end - time.monotonic()))
|
432
|
+
check_period_s *= 2
|
421
433
|
if test_func():
|
422
434
|
return
|
423
|
-
time.sleep(check_period_s)
|
424
435
|
att_fail = (
|
425
436
|
f"\n (Still failing after timeout [{timeout_ms}ms], with attempts "
|
426
437
|
f"every {check_period_ms}ms)"
|
@@ -23,7 +23,7 @@ def test_resource(n=2):
|
|
23
23
|
break
|
24
24
|
|
25
25
|
# Specify incorrect number of resources
|
26
|
-
spec = {'cores':
|
26
|
+
spec = {'cores': 1, 'memory': 1}
|
27
27
|
fut = double(n, parsl_resource_specification=spec)
|
28
28
|
try:
|
29
29
|
fut.result()
|
@@ -35,7 +35,7 @@ def test_resource(n=2):
|
|
35
35
|
|
36
36
|
# Specify resources with wrong types
|
37
37
|
# 'cpus' is incorrect, should be 'cores'
|
38
|
-
spec = {'cpus':
|
38
|
+
spec = {'cpus': 1, 'memory': 1, 'disk': 1}
|
39
39
|
fut = double(n, parsl_resource_specification=spec)
|
40
40
|
try:
|
41
41
|
fut.result()
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
import parsl
|
4
|
+
from parsl.app.app import python_app
|
5
|
+
from parsl.config import Config
|
6
|
+
from parsl.executors import HighThroughputExecutor
|
7
|
+
from parsl.executors.high_throughput.manager_selector import RandomManagerSelector
|
8
|
+
from parsl.providers import LocalProvider
|
9
|
+
from parsl.usage_tracking.levels import LEVEL_1
|
10
|
+
|
11
|
+
|
12
|
+
@python_app
|
13
|
+
def fake_task(parsl_resource_specification=None):
|
14
|
+
import time
|
15
|
+
return time.time()
|
16
|
+
|
17
|
+
|
18
|
+
@pytest.mark.local
|
19
|
+
def test_priority_queue():
|
20
|
+
provider = LocalProvider(
|
21
|
+
init_blocks=0,
|
22
|
+
max_blocks=0,
|
23
|
+
min_blocks=0,
|
24
|
+
)
|
25
|
+
|
26
|
+
htex = HighThroughputExecutor(
|
27
|
+
label="htex_local",
|
28
|
+
max_workers_per_node=1,
|
29
|
+
manager_selector=RandomManagerSelector(),
|
30
|
+
provider=provider,
|
31
|
+
)
|
32
|
+
|
33
|
+
config = Config(
|
34
|
+
executors=[htex],
|
35
|
+
strategy="htex_auto_scale",
|
36
|
+
usage_tracking=LEVEL_1,
|
37
|
+
)
|
38
|
+
|
39
|
+
with parsl.load(config):
|
40
|
+
futures = {}
|
41
|
+
|
42
|
+
# Submit tasks with mixed priorities
|
43
|
+
# Priorities: [10, 10, 5, 5, 1, 1] to test fallback behavior
|
44
|
+
for i, priority in enumerate([10, 10, 5, 5, 1, 1]):
|
45
|
+
spec = {'priority': priority}
|
46
|
+
futures[(priority, i)] = fake_task(parsl_resource_specification=spec)
|
47
|
+
|
48
|
+
provider.max_blocks = 1
|
49
|
+
|
50
|
+
# Wait for completion
|
51
|
+
results = {
|
52
|
+
key: future.result() for key, future in futures.items()
|
53
|
+
}
|
54
|
+
|
55
|
+
# Sort by finish time
|
56
|
+
sorted_by_completion = sorted(results.items(), key=lambda item: item[1])
|
57
|
+
execution_order = [key for key, _ in sorted_by_completion]
|
58
|
+
|
59
|
+
# check priority queue functionality
|
60
|
+
priorities_only = [p for (p, i) in execution_order]
|
61
|
+
assert priorities_only == sorted(priorities_only), "Priority execution order failed"
|
62
|
+
|
63
|
+
# check FIFO fallback
|
64
|
+
from collections import defaultdict
|
65
|
+
seen = defaultdict(list)
|
66
|
+
for (priority, idx) in execution_order:
|
67
|
+
seen[priority].append(idx)
|
68
|
+
|
69
|
+
for priority, indices in seen.items():
|
70
|
+
assert indices == sorted(indices), f"FIFO fallback violated for priority {priority}"
|
@@ -36,3 +36,10 @@ def test_resource_spec_validation_bad_keys():
|
|
36
36
|
|
37
37
|
with pytest.raises(InvalidResourceSpecification):
|
38
38
|
htex.validate_resource_spec({"num_nodes": 2})
|
39
|
+
|
40
|
+
|
41
|
+
@pytest.mark.local
|
42
|
+
def test_resource_spec_validation_one_key():
|
43
|
+
htex = HighThroughputExecutor()
|
44
|
+
ret_val = htex.validate_resource_spec({"priority": 2})
|
45
|
+
assert ret_val is None
|
@@ -4,9 +4,9 @@ import time
|
|
4
4
|
import pytest
|
5
5
|
|
6
6
|
import parsl
|
7
|
-
from parsl import HighThroughputExecutor
|
7
|
+
from parsl import HighThroughputExecutor, ThreadPoolExecutor
|
8
8
|
from parsl.config import Config
|
9
|
-
from parsl.executors.
|
9
|
+
from parsl.executors.status_handling import BlockProviderExecutor
|
10
10
|
from parsl.monitoring import MonitoringHub
|
11
11
|
|
12
12
|
|
@@ -24,6 +24,13 @@ def this_app():
|
|
24
24
|
# The below fresh configs are for use in parametrization, and should return
|
25
25
|
# a configuration that is suitably configured for monitoring.
|
26
26
|
|
27
|
+
def thread_config():
|
28
|
+
c = Config(executors=[ThreadPoolExecutor()],
|
29
|
+
monitoring=MonitoringHub(hub_address="localhost",
|
30
|
+
resource_monitoring_interval=0))
|
31
|
+
return c
|
32
|
+
|
33
|
+
|
27
34
|
def htex_config():
|
28
35
|
"""This config will use htex's default htex-specific monitoring radio mode"""
|
29
36
|
from parsl.tests.configs.htex_local_alternate import fresh_config
|
@@ -54,9 +61,27 @@ def htex_filesystem_config():
|
|
54
61
|
return c
|
55
62
|
|
56
63
|
|
57
|
-
|
58
|
-
|
59
|
-
|
64
|
+
def workqueue_config():
|
65
|
+
from parsl.tests.configs.workqueue_ex import fresh_config
|
66
|
+
c = fresh_config()
|
67
|
+
c.monitoring = MonitoringHub(
|
68
|
+
hub_address="localhost",
|
69
|
+
resource_monitoring_interval=1)
|
70
|
+
return c
|
71
|
+
|
72
|
+
|
73
|
+
def taskvine_config():
|
74
|
+
from parsl.executors.taskvine import TaskVineExecutor, TaskVineManagerConfig
|
75
|
+
c = Config(executors=[TaskVineExecutor(manager_config=TaskVineManagerConfig(port=9000),
|
76
|
+
worker_launch_method='provider')],
|
77
|
+
strategy_period=0.5,
|
78
|
+
|
79
|
+
monitoring=MonitoringHub(hub_address="localhost",
|
80
|
+
resource_monitoring_interval=1))
|
81
|
+
return c
|
82
|
+
|
83
|
+
|
84
|
+
def row_counts_parametrized(tmpd_cwd, fresh_config):
|
60
85
|
# this is imported here rather than at module level because
|
61
86
|
# it isn't available in a plain parsl install, so this module
|
62
87
|
# would otherwise fail to import and break even a basic test
|
@@ -104,12 +129,41 @@ def test_row_counts(tmpd_cwd, fresh_config):
|
|
104
129
|
(c, ) = result.first()
|
105
130
|
assert c == 4
|
106
131
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
132
|
+
if isinstance(config.executors[0], BlockProviderExecutor):
|
133
|
+
# This case assumes that a BlockProviderExecutor is actually being
|
134
|
+
# used with blocks. It might not be (for example, Work Queue and
|
135
|
+
# Task Vine can be configured to launch their own workers; and it
|
136
|
+
# is a valid (although occasional) use of htex to launch executors
|
137
|
+
# manually.
|
138
|
+
# If you just added test cases like that and are wondering why this
|
139
|
+
# assert is failing, that might be why.
|
140
|
+
result = connection.execute(text("SELECT COUNT(*) FROM block"))
|
141
|
+
(c, ) = result.first()
|
142
|
+
assert c >= 2, "There should be at least two block statuses from a BlockProviderExecutor"
|
112
143
|
|
113
144
|
result = connection.execute(text("SELECT COUNT(*) FROM resource"))
|
114
145
|
(c, ) = result.first()
|
115
|
-
|
146
|
+
if isinstance(config.executors[0], ThreadPoolExecutor):
|
147
|
+
assert c == 0, "Thread pool executors should not be recording resources"
|
148
|
+
else:
|
149
|
+
assert c >= 1, "Task execution should have created some resource records"
|
150
|
+
|
151
|
+
|
152
|
+
@pytest.mark.local
|
153
|
+
@pytest.mark.parametrize("fresh_config", [thread_config, htex_config, htex_filesystem_config, htex_udp_config])
|
154
|
+
def test_row_counts_base(tmpd_cwd, fresh_config):
|
155
|
+
row_counts_parametrized(tmpd_cwd, fresh_config)
|
156
|
+
|
157
|
+
|
158
|
+
@pytest.mark.workqueue
|
159
|
+
@pytest.mark.local
|
160
|
+
@pytest.mark.parametrize("fresh_config", [workqueue_config])
|
161
|
+
def test_row_counts_wq(tmpd_cwd, fresh_config):
|
162
|
+
row_counts_parametrized(tmpd_cwd, fresh_config)
|
163
|
+
|
164
|
+
|
165
|
+
@pytest.mark.taskvine
|
166
|
+
@pytest.mark.local
|
167
|
+
@pytest.mark.parametrize("fresh_config", [taskvine_config])
|
168
|
+
def test_row_counts_tv(tmpd_cwd, fresh_config):
|
169
|
+
row_counts_parametrized(tmpd_cwd, fresh_config)
|
parsl/usage_tracking/usage.py
CHANGED
parsl/version.py
CHANGED
@@ -5,13 +5,13 @@ import logging
|
|
5
5
|
import os
|
6
6
|
import pickle
|
7
7
|
import platform
|
8
|
-
import queue
|
9
8
|
import sys
|
10
9
|
import threading
|
11
10
|
import time
|
12
11
|
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, cast
|
13
12
|
|
14
13
|
import zmq
|
14
|
+
from sortedcontainers import SortedList
|
15
15
|
|
16
16
|
from parsl import curvezmq
|
17
17
|
from parsl.addresses import tcp_url
|
@@ -131,7 +131,7 @@ class Interchange:
|
|
131
131
|
self.hub_address = hub_address
|
132
132
|
self.hub_zmq_port = hub_zmq_port
|
133
133
|
|
134
|
-
self.pending_task_queue:
|
134
|
+
self.pending_task_queue: SortedList[Any] = SortedList(key=lambda tup: (tup[0], tup[1]))
|
135
135
|
|
136
136
|
# count of tasks that have been received from the submit side
|
137
137
|
self.task_counter = 0
|
@@ -196,13 +196,12 @@ class Interchange:
|
|
196
196
|
eg. [{'task_id':<x>, 'buffer':<buf>} ... ]
|
197
197
|
"""
|
198
198
|
tasks = []
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
tasks.append(x)
|
199
|
+
try:
|
200
|
+
for _ in range(count):
|
201
|
+
*_, task = self.pending_task_queue.pop()
|
202
|
+
tasks.append(task)
|
203
|
+
except IndexError:
|
204
|
+
pass
|
206
205
|
|
207
206
|
return tasks
|
208
207
|
|
@@ -342,8 +341,15 @@ class Interchange:
|
|
342
341
|
if self.task_incoming in self.socks and self.socks[self.task_incoming] == zmq.POLLIN:
|
343
342
|
logger.debug("start task_incoming section")
|
344
343
|
msg = self.task_incoming.recv_pyobj()
|
344
|
+
|
345
|
+
# Process priority, higher number = lower priority
|
346
|
+
resource_spec = msg.get('resource_spec', {})
|
347
|
+
priority = resource_spec.get('priority', float('inf'))
|
348
|
+
queue_entry = (-priority, -self.task_counter, msg)
|
349
|
+
|
345
350
|
logger.debug("putting message onto pending_task_queue")
|
346
|
-
|
351
|
+
|
352
|
+
self.pending_task_queue.add(queue_entry)
|
347
353
|
self.task_counter += 1
|
348
354
|
logger.debug(f"Fetched {self.task_counter} tasks so far")
|
349
355
|
|
@@ -378,23 +384,24 @@ class Interchange:
|
|
378
384
|
return
|
379
385
|
|
380
386
|
if msg['type'] == 'registration':
|
381
|
-
|
382
|
-
self.
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
387
|
+
ix_minor_py = self.current_platform['python_v'].rsplit(".", 1)[0]
|
388
|
+
ix_parsl_v = self.current_platform['parsl_v']
|
389
|
+
mgr_minor_py = msg['python_v'].rsplit(".", 1)[0]
|
390
|
+
mgr_parsl_v = msg['parsl_v']
|
391
|
+
|
392
|
+
m = ManagerRecord(
|
393
|
+
block_id=None,
|
394
|
+
start_time=msg['start_time'],
|
395
|
+
tasks=[],
|
396
|
+
worker_count=0,
|
397
|
+
max_capacity=0,
|
398
|
+
active=True,
|
399
|
+
draining=False,
|
400
|
+
last_heartbeat=time.time(),
|
401
|
+
idle_since=time.time(),
|
402
|
+
parsl_version=mgr_parsl_v,
|
403
|
+
python_version=msg['python_v'],
|
404
|
+
)
|
398
405
|
|
399
406
|
# m is a ManagerRecord, but msg is a dict[Any,Any] and so can
|
400
407
|
# contain arbitrary fields beyond those in ManagerRecord (and
|
@@ -405,23 +412,43 @@ class Interchange:
|
|
405
412
|
logger.info(f"Registration info for manager {manager_id!r}: {msg}")
|
406
413
|
self._send_monitoring_info(monitoring_radio, m)
|
407
414
|
|
408
|
-
if (
|
409
|
-
msg['parsl_v'] != self.current_platform['parsl_v']):
|
410
|
-
logger.error(f"Manager {manager_id!r} has incompatible version info with the interchange")
|
411
|
-
logger.debug("Setting kill event")
|
415
|
+
if (mgr_minor_py, mgr_parsl_v) != (ix_minor_py, ix_parsl_v):
|
412
416
|
kill_event.set()
|
413
|
-
e = VersionMismatch(
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
417
|
+
e = VersionMismatch(
|
418
|
+
f"py.v={ix_minor_py} parsl.v={ix_parsl_v}",
|
419
|
+
f"py.v={mgr_minor_py} parsl.v={mgr_parsl_v}",
|
420
|
+
)
|
421
|
+
result_package = {
|
422
|
+
'type': 'result',
|
423
|
+
'task_id': -1,
|
424
|
+
'exception': serialize_object(e),
|
425
|
+
}
|
419
426
|
pkl_package = pickle.dumps(result_package)
|
420
427
|
self.results_outgoing.send(pkl_package)
|
421
|
-
logger.error(
|
428
|
+
logger.error(
|
429
|
+
"Manager has incompatible version info with the interchange;"
|
430
|
+
" sending failure reports and shutting down:"
|
431
|
+
f"\n Interchange: {e.interchange_version}"
|
432
|
+
f"\n Manager: {e.manager_version}"
|
433
|
+
)
|
434
|
+
|
422
435
|
else:
|
423
|
-
|
424
|
-
|
436
|
+
# We really should update the associated data structure; but not
|
437
|
+
# at this time. *kicks can down the road*
|
438
|
+
assert m['block_id'] is not None, "Verified externally currently"
|
439
|
+
|
440
|
+
# set up entry only if we accept the registration
|
441
|
+
self._ready_managers[manager_id] = m
|
442
|
+
self.connected_block_history.append(m['block_id'])
|
443
|
+
|
444
|
+
interesting_managers.add(manager_id)
|
445
|
+
|
446
|
+
logger.info(
|
447
|
+
f"Registered manager {manager_id!r} (py{mgr_minor_py},"
|
448
|
+
f" {mgr_parsl_v}) and added to ready queue"
|
449
|
+
)
|
450
|
+
logger.debug("Manager %r -> %s", manager_id, m)
|
451
|
+
|
425
452
|
elif msg['type'] == 'heartbeat':
|
426
453
|
manager = self._ready_managers.get(manager_id)
|
427
454
|
if manager:
|
@@ -461,10 +488,10 @@ class Interchange:
|
|
461
488
|
len(self._ready_managers)
|
462
489
|
)
|
463
490
|
|
464
|
-
if interesting_managers and
|
491
|
+
if interesting_managers and self.pending_task_queue:
|
465
492
|
shuffled_managers = self.manager_selector.sort_managers(self._ready_managers, interesting_managers)
|
466
493
|
|
467
|
-
while shuffled_managers and
|
494
|
+
while shuffled_managers and self.pending_task_queue: # cf. the if statement above...
|
468
495
|
manager_id = shuffled_managers.pop()
|
469
496
|
m = self._ready_managers[manager_id]
|
470
497
|
tasks_inflight = len(m['tasks'])
|
@@ -1,9 +1,9 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: parsl
|
3
|
-
Version: 2025.
|
3
|
+
Version: 2025.6.9
|
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.06.09.tar.gz
|
7
7
|
Author: The Parsl Team
|
8
8
|
Author-email: parsl@googlegroups.com
|
9
9
|
License: Apache 2.0
|
@@ -24,6 +24,7 @@ Requires-Dist: globus-sdk
|
|
24
24
|
Requires-Dist: dill
|
25
25
|
Requires-Dist: tblib
|
26
26
|
Requires-Dist: requests
|
27
|
+
Requires-Dist: sortedcontainers
|
27
28
|
Requires-Dist: psutil>=5.5.1
|
28
29
|
Requires-Dist: setproctitle
|
29
30
|
Requires-Dist: filelock<4,>=3.13
|
@@ -8,7 +8,7 @@ parsl/multiprocessing.py,sha256=JNAfgdZvQSsxVyUp229OOUqWwf_ZUhpmw8X9CdF3i6k,3614
|
|
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=codTX6_KLhgeTwNkRzc1lo4bgc1M93eJ-lkqOO98fvk,14331
|
11
|
-
parsl/version.py,sha256=
|
11
|
+
parsl/version.py,sha256=V1z-I8PIxtzsopTNaicGQyPvTgxgd_XObWB24hDcXS4,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
|
@@ -73,8 +73,8 @@ parsl/executors/flux/executor.py,sha256=ii1i5V7uQnhf1BDq5nnMscmmpXJkCWtrFCuBbDaP
|
|
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=x5759lCTIev1yzsJ4OwMp_0me7Cs108HLqKbz1r_zkg,39812
|
77
|
+
parsl/executors/high_throughput/interchange.py,sha256=lnPhV-5OR_rp4fa_Wj0tmG3avwUdPgJH4LTTBZQcrf8,28922
|
78
78
|
parsl/executors/high_throughput/manager_record.py,sha256=ZMsqFxvreGLRXAw3N-JnODDa9Qfizw2tMmcBhm4lco4,490
|
79
79
|
parsl/executors/high_throughput/manager_selector.py,sha256=UKcUE6v0tO7PDMTThpKSKxVpOpOUilxDL7UbNgpZCxo,2116
|
80
80
|
parsl/executors/high_throughput/monitoring_info.py,sha256=HC0drp6nlXQpAop5PTUKNjdXMgtZVvrBL0JzZJebPP4,298
|
@@ -123,7 +123,7 @@ parsl/monitoring/types.py,sha256=oOCrzv-ab-_rv4pb8o58Sdb8G_RGp1aZriRbdf9zBEk,339
|
|
123
123
|
parsl/monitoring/queries/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
124
124
|
parsl/monitoring/queries/pandas.py,sha256=0Z2r0rjTKCemf0eaDkF1irvVHn5g7KC5SYETvQPRxwU,2232
|
125
125
|
parsl/monitoring/radios/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
126
|
-
parsl/monitoring/radios/base.py,sha256=
|
126
|
+
parsl/monitoring/radios/base.py,sha256=MBW3aAbxQY7TMuNGZEN15dWoy3mAELOqz-GMN1d7vF4,221
|
127
127
|
parsl/monitoring/radios/filesystem.py,sha256=ioZ3jOKX5Qf0DYRtWmpCEorfuMVbS58OMS_QV7DOFOs,1765
|
128
128
|
parsl/monitoring/radios/filesystem_router.py,sha256=kQkinktSpsVwfNESfUggSzBlRZ5JgwjM7IDN-jARAhM,2146
|
129
129
|
parsl/monitoring/radios/htex.py,sha256=qBu4O5NYnSETHX0ptdwxSpqa2Pp3Z_V6a6lb3TbjKm4,1643
|
@@ -183,7 +183,7 @@ parsl/providers/pbspro/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
183
183
|
parsl/providers/pbspro/pbspro.py,sha256=vzopQ80iXcl4Wx0_Bs5OtadYGeoGdbDgK7YHfjUjG_Y,8826
|
184
184
|
parsl/providers/pbspro/template.py,sha256=y-Dher--t5Eury-c7cAuSZs9FEUXWiruFUI07v81558,315
|
185
185
|
parsl/providers/slurm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
186
|
-
parsl/providers/slurm/slurm.py,sha256=
|
186
|
+
parsl/providers/slurm/slurm.py,sha256=J58iSRqcVahhHmyIEvmTFlaL0EVaf7yxkD_fec_ja_o,17323
|
187
187
|
parsl/providers/slurm/template.py,sha256=KpgBEFMc1ps-38jdrk13xUGx9TCivu-iF90jgQDdiEQ,315
|
188
188
|
parsl/providers/torque/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
189
189
|
parsl/providers/torque/template.py,sha256=4qfc2gmlEhRCAD7erFDOs4prJQ43I8s4E8DSUSVQx3A,358
|
@@ -196,7 +196,7 @@ parsl/serialize/facade.py,sha256=3uOuVp0epfyLn7qDzuWqLfsy971YVGD3sqwqcAiRwh0,668
|
|
196
196
|
parsl/serialize/proxystore.py,sha256=o-ha9QAvVhbN8y9S1itk3W0O75eyHYZw2AvB2xu5_Lg,1624
|
197
197
|
parsl/tests/__init__.py,sha256=VTtJzOzz_x6fWNh8IOnsgFqVbdiJShi2AZH21mcmID4,204
|
198
198
|
parsl/tests/callables_helper.py,sha256=ceP1YYsNtrZgKT6MAIvpgdccEjQ_CpFEOnZBGHKGOx0,30
|
199
|
-
parsl/tests/conftest.py,sha256=
|
199
|
+
parsl/tests/conftest.py,sha256=ssY-ZP12a1TXj99ifRc0E83MQjri2bgwvMIe-YF5yd0,15485
|
200
200
|
parsl/tests/test_aalst_patterns.py,sha256=lNIxb7nIgh1yX7hR2fr_ck_mxYJxx8ASKK9zHUVqPno,9614
|
201
201
|
parsl/tests/test_callables.py,sha256=97vrIF1_hfDGd81FM1bhR6FemZMWFcALrH6pVHMTCt8,1974
|
202
202
|
parsl/tests/test_curvezmq.py,sha256=yyhlS4vmaZdMitiySoy4l_ih9H1bsPiN-tMdwIh3H20,12431
|
@@ -309,7 +309,7 @@ parsl/tests/test_error_handling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
|
|
309
309
|
parsl/tests/test_error_handling/test_fail.py,sha256=xx4TGWfL7le4cQ9nvnUkrlmKQJkskhD0l_3W1xwZSEI,282
|
310
310
|
parsl/tests/test_error_handling/test_python_walltime.py,sha256=rdmGZHIkuann2Njt3i62odKJ0FaODGr7-L96rOXNVYg,950
|
311
311
|
parsl/tests/test_error_handling/test_rand_fail.py,sha256=crFg4GmwdDpvx49_7w5Xt2P7H2R_V9f6i1Ar-QkASuU,3864
|
312
|
-
parsl/tests/test_error_handling/test_resource_spec.py,sha256=
|
312
|
+
parsl/tests/test_error_handling/test_resource_spec.py,sha256=dyuzMkS3M_BmZUbu1mF7yojwkJehDbdFvphNlYwU9yM,1458
|
313
313
|
parsl/tests/test_error_handling/test_retries.py,sha256=zJ9D2hrvXQURnK2OIf5LfQFcSDVZ8rhdpp6peGccY7s,2372
|
314
314
|
parsl/tests/test_error_handling/test_retry_handler.py,sha256=8fMHffMBLhRyNreIqkrwamx9TYRZ498uVYNlkcbAoLU,1407
|
315
315
|
parsl/tests/test_error_handling/test_retry_handler_failure.py,sha256=GaGtZZCB9Wb7ieShqTrxUFEUSKy07ZZWytCY4Qixk9Y,552
|
@@ -332,12 +332,13 @@ parsl/tests/test_htex/test_manager_selector_by_block.py,sha256=VQqSE6MDhGpDSjShG
|
|
332
332
|
parsl/tests/test_htex/test_managers_command.py,sha256=SCwkfyGB-Udgu5L2yDMpR5bsaT-aNjNkiXxtuRb25DI,1622
|
333
333
|
parsl/tests/test_htex/test_missing_worker.py,sha256=gyp5i7_t-JHyJGtz_eXZKKBY5w8oqLOIxO6cJgGJMtQ,745
|
334
334
|
parsl/tests/test_htex/test_multiple_disconnected_blocks.py,sha256=2vXZoIx4NuAWYuiNoL5Gxr85w72qZ7Kdb3JGh0FufTg,1867
|
335
|
-
parsl/tests/test_htex/
|
335
|
+
parsl/tests/test_htex/test_priority_queue.py,sha256=vH58WwDZVpyIiMqhjwGkme7Cv5-jupTmM52EOcbdrEg,2106
|
336
|
+
parsl/tests/test_htex/test_resource_spec_validation.py,sha256=ZXW02jDd1rNxjBLh1jHyiz31zNoB9JzDw94aWllXFd4,1102
|
336
337
|
parsl/tests/test_htex/test_worker_failure.py,sha256=Uz-RHI-LK78FMjXUvrUFmo4iYfmpDVBUcBxxRb3UG9M,603
|
337
338
|
parsl/tests/test_htex/test_zmq_binding.py,sha256=G7D2_p9vOekgpB50MBiPRwtIz98DEkUpMqA3rdwzYTQ,4397
|
338
339
|
parsl/tests/test_monitoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
339
340
|
parsl/tests/test_monitoring/test_app_names.py,sha256=A-mOMCVhZDnUyJp32fsTUkHdcyval8o7WPEWacDkbD4,2208
|
340
|
-
parsl/tests/test_monitoring/test_basic.py,sha256=
|
341
|
+
parsl/tests/test_monitoring/test_basic.py,sha256=QJS0SJkf1KV6A4UvikBu-wAIcBJZVoVe-BSVWgyFonI,6180
|
341
342
|
parsl/tests/test_monitoring/test_db_locks.py,sha256=3s3c1xhKo230ZZIJ3f1Ca4U7LcEdXnanOGVXQyNlk2U,2895
|
342
343
|
parsl/tests/test_monitoring/test_exit_helper.py,sha256=ob8Qd1hlkq_mowygfPetTnYN9LfuqeXHRpPilSfDSog,1232
|
343
344
|
parsl/tests/test_monitoring/test_fuzz_zmq.py,sha256=SQNNHhXxHB_LwW4Ujqkgut3lbG0XVW-hliPagQQpiTc,3449
|
@@ -410,7 +411,7 @@ parsl/tests/test_scaling/test_block_error_handler.py,sha256=OS1IyiK8gjRFI1VzpmOv
|
|
410
411
|
parsl/tests/test_scaling/test_regression_1621.py,sha256=e3-bkHR3d8LxA-uY0BugyWgYzksh00I_UbaA-jHOzKY,1872
|
411
412
|
parsl/tests/test_scaling/test_regression_3568_scaledown_vs_MISSING.py,sha256=bjE_NIBoWK6heEz5LN0tzE1977vUA9kVemAYCqcIbzY,2942
|
412
413
|
parsl/tests/test_scaling/test_regression_3696_oscillation.py,sha256=7Xc3vgocXXUbUegh9t5OyXlV91lRXDVMUlrOwErYOXA,3621
|
413
|
-
parsl/tests/test_scaling/test_scale_down.py,sha256=
|
414
|
+
parsl/tests/test_scaling/test_scale_down.py,sha256=GmxzNtlG13SySVDGGlSqXEnaHxyCx6ZVn_Hi1GcBvj8,2765
|
414
415
|
parsl/tests/test_scaling/test_scale_down_htex_auto_scale.py,sha256=EnVNllKO2AGKkGa6927cLrzvvG6mpNQeFDzVktv6x08,4521
|
415
416
|
parsl/tests/test_scaling/test_scale_down_htex_unregistered.py,sha256=OrdnYmd58n7UfkANPJ7mzha4WSCPdbgJRX1O1Zdu0tI,1954
|
416
417
|
parsl/tests/test_scaling/test_shutdown_scalein.py,sha256=sr40of5DwxeyQI97MDZxFqJILZSXZJb9Dv7qTf2gql8,2471
|
@@ -458,14 +459,14 @@ parsl/tests/unit/test_usage_tracking.py,sha256=xEfUlbBRpsFdUdOrCsk1Kz5AfmMxJT7f0
|
|
458
459
|
parsl/usage_tracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
459
460
|
parsl/usage_tracking/api.py,sha256=iaCY58Dc5J4UM7_dJzEEs871P1p1HdxBMtNGyVdzc9g,1821
|
460
461
|
parsl/usage_tracking/levels.py,sha256=xbfzYEsd55KiZJ-mzNgPebvOH4rRHum04hROzEf41tU,291
|
461
|
-
parsl/usage_tracking/usage.py,sha256=
|
462
|
-
parsl-2025.
|
463
|
-
parsl-2025.
|
464
|
-
parsl-2025.
|
465
|
-
parsl-2025.
|
466
|
-
parsl-2025.
|
467
|
-
parsl-2025.
|
468
|
-
parsl-2025.
|
469
|
-
parsl-2025.
|
470
|
-
parsl-2025.
|
471
|
-
parsl-2025.
|
462
|
+
parsl/usage_tracking/usage.py,sha256=hbMo5BYgIWqMcFWqN-HYP1TbwNrTonpv-usfwnCFJKY,9212
|
463
|
+
parsl-2025.6.9.data/scripts/exec_parsl_function.py,sha256=YXKVVIa4zXmOtz-0Ca4E_5nQfN_3S2bh2tB75uZZB4w,7774
|
464
|
+
parsl-2025.6.9.data/scripts/interchange.py,sha256=_FRB1LxkL9vnT3y24NTXHOzotMlDJEXwF5ZZCjGmcww,28909
|
465
|
+
parsl-2025.6.9.data/scripts/parsl_coprocess.py,sha256=zrVjEqQvFOHxsLufPi00xzMONagjVwLZbavPM7bbjK4,5722
|
466
|
+
parsl-2025.6.9.data/scripts/process_worker_pool.py,sha256=__gFeFQJpV5moRofj3WKQCnKp6gmzieXjzkmzVuTmX4,41123
|
467
|
+
parsl-2025.6.9.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
468
|
+
parsl-2025.6.9.dist-info/METADATA,sha256=yAjU-1rMVRSwg9zcdOsAHwW9IOAEkzyILXM2AbEgRhM,4054
|
469
|
+
parsl-2025.6.9.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
470
|
+
parsl-2025.6.9.dist-info/entry_points.txt,sha256=XqnsWDYoEcLbsMcpnYGKLEnSBmaIe1YoM5YsBdJG2tI,176
|
471
|
+
parsl-2025.6.9.dist-info/top_level.txt,sha256=PIheYoUFQtF2icLsgOykgU-Cjuwr2Oi6On2jo5RYgRM,6
|
472
|
+
parsl-2025.6.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|