parsl 2025.2.24__py3-none-any.whl → 2025.3.10__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 +1 -1
- parsl/executors/high_throughput/mpi_resource_management.py +13 -4
- parsl/executors/taskvine/factory.py +4 -0
- parsl/executors/workqueue/executor.py +3 -3
- parsl/monitoring/monitoring.py +75 -79
- parsl/monitoring/radios/filesystem_router.py +52 -0
- parsl/monitoring/{router.py → radios/udp_router.py} +17 -80
- parsl/monitoring/radios/zmq_router.py +138 -0
- parsl/tests/test_mpi_apps/test_mpi_scheduler.py +25 -0
- parsl/tests/test_scaling/test_shutdown_scalein.py +4 -1
- parsl/tests/test_shutdown/test_kill_monitoring.py +1 -1
- parsl/utils.py +2 -0
- parsl/version.py +1 -1
- {parsl-2025.2.24.dist-info → parsl-2025.3.10.dist-info}/METADATA +2 -2
- {parsl-2025.2.24.dist-info → parsl-2025.3.10.dist-info}/RECORD +23 -21
- {parsl-2025.2.24.data → parsl-2025.3.10.data}/scripts/exec_parsl_function.py +0 -0
- {parsl-2025.2.24.data → parsl-2025.3.10.data}/scripts/interchange.py +0 -0
- {parsl-2025.2.24.data → parsl-2025.3.10.data}/scripts/parsl_coprocess.py +0 -0
- {parsl-2025.2.24.data → parsl-2025.3.10.data}/scripts/process_worker_pool.py +0 -0
- {parsl-2025.2.24.dist-info → parsl-2025.3.10.dist-info}/LICENSE +0 -0
- {parsl-2025.2.24.dist-info → parsl-2025.3.10.dist-info}/WHEEL +0 -0
- {parsl-2025.2.24.dist-info → parsl-2025.3.10.dist-info}/entry_points.txt +0 -0
- {parsl-2025.2.24.dist-info → parsl-2025.3.10.dist-info}/top_level.txt +0 -0
@@ -536,7 +536,7 @@ class HighThroughputExecutor(BlockProviderExecutor, RepresentationMixin, UsageIn
|
|
536
536
|
"interchange_address": self.address,
|
537
537
|
"worker_ports": self.worker_ports,
|
538
538
|
"worker_port_range": self.worker_port_range,
|
539
|
-
"hub_address": self.
|
539
|
+
"hub_address": self.loopback_address,
|
540
540
|
"hub_zmq_port": self.hub_zmq_port,
|
541
541
|
"logdir": self.logdir,
|
542
542
|
"heartbeat_threshold": self.heartbeat_threshold,
|
@@ -4,6 +4,7 @@ import os
|
|
4
4
|
import pickle
|
5
5
|
import queue
|
6
6
|
import subprocess
|
7
|
+
from dataclasses import dataclass, field
|
7
8
|
from enum import Enum
|
8
9
|
from typing import Dict, List, Optional
|
9
10
|
|
@@ -69,6 +70,14 @@ class MPINodesUnavailable(Exception):
|
|
69
70
|
return f"MPINodesUnavailable(requested={self.requested} available={self.available})"
|
70
71
|
|
71
72
|
|
73
|
+
@dataclass(order=True)
|
74
|
+
class PrioritizedTask:
|
75
|
+
# Comparing dict will fail since they are unhashable
|
76
|
+
# This dataclass limits comparison to the priority field
|
77
|
+
priority: int
|
78
|
+
task: Dict = field(compare=False)
|
79
|
+
|
80
|
+
|
72
81
|
class TaskScheduler:
|
73
82
|
"""Default TaskScheduler that does no taskscheduling
|
74
83
|
|
@@ -111,7 +120,7 @@ class MPITaskScheduler(TaskScheduler):
|
|
111
120
|
super().__init__(pending_task_q, pending_result_q)
|
112
121
|
self.scheduler = identify_scheduler()
|
113
122
|
# PriorityQueue is threadsafe
|
114
|
-
self._backlog_queue: queue.PriorityQueue = queue.PriorityQueue()
|
123
|
+
self._backlog_queue: queue.PriorityQueue[PrioritizedTask] = queue.PriorityQueue()
|
115
124
|
self._map_tasks_to_nodes: Dict[str, List[str]] = {}
|
116
125
|
self.available_nodes = get_nodes_in_batchjob(self.scheduler)
|
117
126
|
self._free_node_counter = SpawnContext.Value("i", len(self.available_nodes))
|
@@ -169,7 +178,7 @@ class MPITaskScheduler(TaskScheduler):
|
|
169
178
|
allocated_nodes = self._get_nodes(nodes_needed)
|
170
179
|
except MPINodesUnavailable:
|
171
180
|
logger.info(f"Not enough resources, placing task {tid} into backlog")
|
172
|
-
self._backlog_queue.put((nodes_needed, task_package))
|
181
|
+
self._backlog_queue.put(PrioritizedTask(nodes_needed, task_package))
|
173
182
|
return
|
174
183
|
else:
|
175
184
|
resource_spec["MPI_NODELIST"] = ",".join(allocated_nodes)
|
@@ -183,8 +192,8 @@ class MPITaskScheduler(TaskScheduler):
|
|
183
192
|
def _schedule_backlog_tasks(self):
|
184
193
|
"""Attempt to schedule backlogged tasks"""
|
185
194
|
try:
|
186
|
-
|
187
|
-
self.put_task(
|
195
|
+
prioritized_task = self._backlog_queue.get(block=False)
|
196
|
+
self.put_task(prioritized_task.task)
|
188
197
|
except queue.Empty:
|
189
198
|
return
|
190
199
|
else:
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import logging
|
2
|
+
import os
|
2
3
|
|
3
4
|
from parsl.executors.taskvine.errors import TaskVineFactoryFailure
|
4
5
|
from parsl.process_loggers import wrap_with_logs
|
@@ -43,6 +44,9 @@ def _taskvine_factory(should_stop, factory_config):
|
|
43
44
|
factory.max_workers = factory_config.max_workers
|
44
45
|
factory.workers_per_cycle = factory_config.workers_per_cycle
|
45
46
|
|
47
|
+
# create scratch dir if factory process gets ahead of the manager.
|
48
|
+
os.makedirs(factory.scratch_dir, exist_ok=True)
|
49
|
+
|
46
50
|
if factory_config.worker_options:
|
47
51
|
factory.extra_options = factory_config.worker_options
|
48
52
|
factory.timeout = factory_config.worker_timeout
|
@@ -333,7 +333,7 @@ class WorkQueueExecutor(BlockProviderExecutor, putils.RepresentationMixin):
|
|
333
333
|
|
334
334
|
logger.debug("Starting WorkQueueExecutor")
|
335
335
|
|
336
|
-
|
336
|
+
port_mailbox = multiprocessing.Queue()
|
337
337
|
|
338
338
|
# Create a Process to perform WorkQueue submissions
|
339
339
|
submit_process_kwargs = {"task_queue": self.task_queue,
|
@@ -351,7 +351,7 @@ class WorkQueueExecutor(BlockProviderExecutor, putils.RepresentationMixin):
|
|
351
351
|
"wq_log_dir": self.wq_log_dir,
|
352
352
|
"project_password_file": self.project_password_file,
|
353
353
|
"project_name": self.project_name,
|
354
|
-
"port_mailbox":
|
354
|
+
"port_mailbox": port_mailbox,
|
355
355
|
"coprocess": self.coprocess
|
356
356
|
}
|
357
357
|
self.submit_process = multiprocessing.Process(target=_work_queue_submit_wait,
|
@@ -366,7 +366,7 @@ class WorkQueueExecutor(BlockProviderExecutor, putils.RepresentationMixin):
|
|
366
366
|
self.submit_process.start()
|
367
367
|
self.collector_thread.start()
|
368
368
|
|
369
|
-
self._chosen_port =
|
369
|
+
self._chosen_port = port_mailbox.get(timeout=60)
|
370
370
|
|
371
371
|
logger.debug(f"Chosen listening port is {self._chosen_port}")
|
372
372
|
|
parsl/monitoring/monitoring.py
CHANGED
@@ -3,23 +3,21 @@ from __future__ import annotations
|
|
3
3
|
import logging
|
4
4
|
import multiprocessing.synchronize as ms
|
5
5
|
import os
|
6
|
-
import pickle
|
7
6
|
import queue
|
8
|
-
import time
|
9
7
|
from multiprocessing import Event
|
10
8
|
from multiprocessing.queues import Queue
|
11
|
-
from typing import TYPE_CHECKING, Literal, Optional, Tuple, Union
|
9
|
+
from typing import TYPE_CHECKING, Literal, Optional, Tuple, Union
|
12
10
|
|
13
11
|
import typeguard
|
14
12
|
|
15
|
-
from parsl.log_utils import set_file_logger
|
16
13
|
from parsl.monitoring.errors import MonitoringHubStartError
|
14
|
+
from parsl.monitoring.radios.filesystem_router import filesystem_router_starter
|
17
15
|
from parsl.monitoring.radios.multiprocessing import MultiprocessingQueueRadioSender
|
18
|
-
from parsl.monitoring.
|
16
|
+
from parsl.monitoring.radios.udp_router import udp_router_starter
|
17
|
+
from parsl.monitoring.radios.zmq_router import zmq_router_starter
|
19
18
|
from parsl.monitoring.types import TaggedMonitoringMessage
|
20
19
|
from parsl.multiprocessing import ForkProcess, SizedQueue
|
21
|
-
from parsl.
|
22
|
-
from parsl.utils import RepresentationMixin, setproctitle
|
20
|
+
from parsl.utils import RepresentationMixin
|
23
21
|
|
24
22
|
_db_manager_excepts: Optional[Exception]
|
25
23
|
|
@@ -121,11 +119,14 @@ class MonitoringHub(RepresentationMixin):
|
|
121
119
|
# in the future, Queue will allow runtime subscripts.
|
122
120
|
|
123
121
|
if TYPE_CHECKING:
|
124
|
-
|
122
|
+
zmq_comm_q: Queue[Union[int, str]]
|
123
|
+
udp_comm_q: Queue[Union[int, str]]
|
125
124
|
else:
|
126
|
-
|
125
|
+
zmq_comm_q: Queue
|
126
|
+
udp_comm_q: Queue
|
127
127
|
|
128
|
-
|
128
|
+
zmq_comm_q = SizedQueue(maxsize=10)
|
129
|
+
udp_comm_q = SizedQueue(maxsize=10)
|
129
130
|
|
130
131
|
self.exception_q: Queue[Tuple[str, str]]
|
131
132
|
self.exception_q = SizedQueue(maxsize=10)
|
@@ -136,21 +137,35 @@ class MonitoringHub(RepresentationMixin):
|
|
136
137
|
self.router_exit_event: ms.Event
|
137
138
|
self.router_exit_event = Event()
|
138
139
|
|
139
|
-
self.
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
140
|
+
self.zmq_router_proc = ForkProcess(target=zmq_router_starter,
|
141
|
+
kwargs={"comm_q": zmq_comm_q,
|
142
|
+
"exception_q": self.exception_q,
|
143
|
+
"resource_msgs": self.resource_msgs,
|
144
|
+
"exit_event": self.router_exit_event,
|
145
|
+
"hub_address": self.hub_address,
|
146
|
+
"zmq_port_range": self.hub_port_range,
|
147
|
+
"run_dir": dfk_run_dir,
|
148
|
+
"logging_level": logging.DEBUG if self.monitoring_debug else logging.INFO,
|
149
|
+
},
|
150
|
+
name="Monitoring-ZMQ-Router-Process",
|
151
|
+
daemon=True,
|
152
|
+
)
|
153
|
+
self.zmq_router_proc.start()
|
154
|
+
|
155
|
+
self.udp_router_proc = ForkProcess(target=udp_router_starter,
|
156
|
+
kwargs={"comm_q": udp_comm_q,
|
157
|
+
"exception_q": self.exception_q,
|
158
|
+
"resource_msgs": self.resource_msgs,
|
159
|
+
"exit_event": self.router_exit_event,
|
160
|
+
"hub_address": self.hub_address,
|
161
|
+
"udp_port": self.hub_port,
|
162
|
+
"run_dir": dfk_run_dir,
|
163
|
+
"logging_level": logging.DEBUG if self.monitoring_debug else logging.INFO,
|
164
|
+
},
|
165
|
+
name="Monitoring-UDP-Router-Process",
|
166
|
+
daemon=True,
|
167
|
+
)
|
168
|
+
self.udp_router_proc.start()
|
154
169
|
|
155
170
|
self.dbm_proc = ForkProcess(target=dbm_starter,
|
156
171
|
args=(self.exception_q, self.resource_msgs,),
|
@@ -162,9 +177,10 @@ class MonitoringHub(RepresentationMixin):
|
|
162
177
|
daemon=True,
|
163
178
|
)
|
164
179
|
self.dbm_proc.start()
|
165
|
-
logger.info("Started
|
180
|
+
logger.info("Started ZMQ router process %s, UDP router process %s and DBM process %s",
|
181
|
+
self.zmq_router_proc.pid, self.udp_router_proc.pid, self.dbm_proc.pid)
|
166
182
|
|
167
|
-
self.filesystem_proc = ForkProcess(target=
|
183
|
+
self.filesystem_proc = ForkProcess(target=filesystem_router_starter,
|
168
184
|
args=(self.resource_msgs, dfk_run_dir),
|
169
185
|
name="Monitoring-Filesystem-Process",
|
170
186
|
daemon=True
|
@@ -175,25 +191,36 @@ class MonitoringHub(RepresentationMixin):
|
|
175
191
|
self.radio = MultiprocessingQueueRadioSender(self.resource_msgs)
|
176
192
|
|
177
193
|
try:
|
178
|
-
|
179
|
-
|
180
|
-
|
194
|
+
zmq_comm_q_result = zmq_comm_q.get(block=True, timeout=120)
|
195
|
+
zmq_comm_q.close()
|
196
|
+
zmq_comm_q.join_thread()
|
181
197
|
except queue.Empty:
|
182
|
-
logger.error("
|
198
|
+
logger.error("Monitoring ZMQ Router has not reported port in 120s. Aborting")
|
183
199
|
raise MonitoringHubStartError()
|
184
200
|
|
185
|
-
if isinstance(
|
186
|
-
logger.error("MonitoringRouter sent an error message: %s",
|
187
|
-
raise RuntimeError(f"MonitoringRouter failed to start: {
|
201
|
+
if isinstance(zmq_comm_q_result, str):
|
202
|
+
logger.error("MonitoringRouter sent an error message: %s", zmq_comm_q_result)
|
203
|
+
raise RuntimeError(f"MonitoringRouter failed to start: {zmq_comm_q_result}")
|
204
|
+
|
205
|
+
self.hub_zmq_port = zmq_comm_q_result
|
206
|
+
|
207
|
+
try:
|
208
|
+
udp_comm_q_result = udp_comm_q.get(block=True, timeout=120)
|
209
|
+
udp_comm_q.close()
|
210
|
+
udp_comm_q.join_thread()
|
211
|
+
except queue.Empty:
|
212
|
+
logger.error("Monitoring UDP router has not reported port in 120s. Aborting")
|
213
|
+
raise MonitoringHubStartError()
|
188
214
|
|
189
|
-
|
215
|
+
if isinstance(udp_comm_q_result, str):
|
216
|
+
logger.error("MonitoringRouter sent an error message: %s", udp_comm_q_result)
|
217
|
+
raise RuntimeError(f"MonitoringRouter failed to start: {udp_comm_q_result}")
|
190
218
|
|
219
|
+
udp_port = udp_comm_q_result
|
191
220
|
self.monitoring_hub_url = "udp://{}:{}".format(self.hub_address, udp_port)
|
192
221
|
|
193
222
|
logger.info("Monitoring Hub initialized")
|
194
223
|
|
195
|
-
self.hub_zmq_port = zmq_port
|
196
|
-
|
197
224
|
def send(self, message: TaggedMonitoringMessage) -> None:
|
198
225
|
logger.debug("Sending message type %s", message[0])
|
199
226
|
self.radio.send(message)
|
@@ -216,14 +243,21 @@ class MonitoringHub(RepresentationMixin):
|
|
216
243
|
exception_msg[0],
|
217
244
|
exception_msg[1]
|
218
245
|
)
|
219
|
-
self.
|
246
|
+
self.zmq_router_proc.terminate()
|
247
|
+
self.udp_router_proc.terminate()
|
220
248
|
self.dbm_proc.terminate()
|
221
249
|
self.filesystem_proc.terminate()
|
222
250
|
logger.info("Setting router termination event")
|
223
251
|
self.router_exit_event.set()
|
224
|
-
|
225
|
-
|
226
|
-
self.
|
252
|
+
|
253
|
+
logger.info("Waiting for ZMQ router to terminate")
|
254
|
+
self.zmq_router_proc.join()
|
255
|
+
self.zmq_router_proc.close()
|
256
|
+
|
257
|
+
logger.info("Waiting for UDP router to terminate")
|
258
|
+
self.udp_router_proc.join()
|
259
|
+
self.udp_router_proc.close()
|
260
|
+
|
227
261
|
logger.debug("Finished waiting for router termination")
|
228
262
|
if len(exception_msgs) == 0:
|
229
263
|
logger.debug("Sending STOP to DBM")
|
@@ -248,41 +282,3 @@ class MonitoringHub(RepresentationMixin):
|
|
248
282
|
self.resource_msgs.close()
|
249
283
|
self.resource_msgs.join_thread()
|
250
284
|
logger.info("Closed monitoring multiprocessing queues")
|
251
|
-
|
252
|
-
|
253
|
-
@wrap_with_logs
|
254
|
-
def filesystem_receiver(q: Queue[TaggedMonitoringMessage], run_dir: str) -> None:
|
255
|
-
logger = set_file_logger(f"{run_dir}/monitoring_filesystem_radio.log",
|
256
|
-
name="monitoring_filesystem_radio",
|
257
|
-
level=logging.INFO)
|
258
|
-
|
259
|
-
logger.info("Starting filesystem radio receiver")
|
260
|
-
setproctitle("parsl: monitoring filesystem receiver")
|
261
|
-
base_path = f"{run_dir}/monitor-fs-radio/"
|
262
|
-
tmp_dir = f"{base_path}/tmp/"
|
263
|
-
new_dir = f"{base_path}/new/"
|
264
|
-
logger.debug("Creating new and tmp paths under %s", base_path)
|
265
|
-
|
266
|
-
target_radio = MultiprocessingQueueRadioSender(q)
|
267
|
-
|
268
|
-
os.makedirs(tmp_dir, exist_ok=True)
|
269
|
-
os.makedirs(new_dir, exist_ok=True)
|
270
|
-
|
271
|
-
while True: # this loop will end on process termination
|
272
|
-
logger.debug("Start filesystem radio receiver loop")
|
273
|
-
|
274
|
-
# iterate over files in new_dir
|
275
|
-
for filename in os.listdir(new_dir):
|
276
|
-
try:
|
277
|
-
logger.info("Processing filesystem radio file %s", filename)
|
278
|
-
full_path_filename = f"{new_dir}/{filename}"
|
279
|
-
with open(full_path_filename, "rb") as f:
|
280
|
-
message = pickle.load(f)
|
281
|
-
logger.debug("Message received is: %s", message)
|
282
|
-
assert isinstance(message, tuple)
|
283
|
-
target_radio.send(cast(TaggedMonitoringMessage, message))
|
284
|
-
os.remove(full_path_filename)
|
285
|
-
except Exception:
|
286
|
-
logger.exception("Exception processing %s - probably will be retried next iteration", filename)
|
287
|
-
|
288
|
-
time.sleep(1) # whats a good time for this poll?
|
@@ -0,0 +1,52 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import os
|
5
|
+
import pickle
|
6
|
+
import time
|
7
|
+
from multiprocessing.queues import Queue
|
8
|
+
from typing import cast
|
9
|
+
|
10
|
+
from parsl.log_utils import set_file_logger
|
11
|
+
from parsl.monitoring.radios.multiprocessing import MultiprocessingQueueRadioSender
|
12
|
+
from parsl.monitoring.types import TaggedMonitoringMessage
|
13
|
+
from parsl.process_loggers import wrap_with_logs
|
14
|
+
from parsl.utils import setproctitle
|
15
|
+
|
16
|
+
|
17
|
+
@wrap_with_logs
|
18
|
+
def filesystem_router_starter(q: Queue[TaggedMonitoringMessage], run_dir: str) -> None:
|
19
|
+
logger = set_file_logger(f"{run_dir}/monitoring_filesystem_radio.log",
|
20
|
+
name="monitoring_filesystem_radio",
|
21
|
+
level=logging.INFO)
|
22
|
+
|
23
|
+
logger.info("Starting filesystem radio receiver")
|
24
|
+
setproctitle("parsl: monitoring filesystem receiver")
|
25
|
+
base_path = f"{run_dir}/monitor-fs-radio/"
|
26
|
+
tmp_dir = f"{base_path}/tmp/"
|
27
|
+
new_dir = f"{base_path}/new/"
|
28
|
+
logger.debug("Creating new and tmp paths under %s", base_path)
|
29
|
+
|
30
|
+
target_radio = MultiprocessingQueueRadioSender(q)
|
31
|
+
|
32
|
+
os.makedirs(tmp_dir, exist_ok=True)
|
33
|
+
os.makedirs(new_dir, exist_ok=True)
|
34
|
+
|
35
|
+
while True: # this loop will end on process termination
|
36
|
+
logger.debug("Start filesystem radio receiver loop")
|
37
|
+
|
38
|
+
# iterate over files in new_dir
|
39
|
+
for filename in os.listdir(new_dir):
|
40
|
+
try:
|
41
|
+
logger.info("Processing filesystem radio file %s", filename)
|
42
|
+
full_path_filename = f"{new_dir}/{filename}"
|
43
|
+
with open(full_path_filename, "rb") as f:
|
44
|
+
message = pickle.load(f)
|
45
|
+
logger.debug("Message received is: %s", message)
|
46
|
+
assert isinstance(message, tuple)
|
47
|
+
target_radio.send(cast(TaggedMonitoringMessage, message))
|
48
|
+
os.remove(full_path_filename)
|
49
|
+
except Exception:
|
50
|
+
logger.exception("Exception processing %s - probably will be retried next iteration", filename)
|
51
|
+
|
52
|
+
time.sleep(1) # whats a good time for this poll?
|
@@ -5,17 +5,14 @@ import multiprocessing.queues as mpq
|
|
5
5
|
import os
|
6
6
|
import pickle
|
7
7
|
import socket
|
8
|
-
import threading
|
9
8
|
import time
|
10
9
|
from multiprocessing.synchronize import Event
|
11
|
-
from typing import Optional
|
10
|
+
from typing import Optional
|
12
11
|
|
13
12
|
import typeguard
|
14
|
-
import zmq
|
15
13
|
|
16
14
|
from parsl.log_utils import set_file_logger
|
17
15
|
from parsl.monitoring.radios.multiprocessing import MultiprocessingQueueRadioSender
|
18
|
-
from parsl.monitoring.types import TaggedMonitoringMessage
|
19
16
|
from parsl.process_loggers import wrap_with_logs
|
20
17
|
from parsl.utils import setproctitle
|
21
18
|
|
@@ -28,7 +25,6 @@ class MonitoringRouter:
|
|
28
25
|
*,
|
29
26
|
hub_address: str,
|
30
27
|
udp_port: Optional[int] = None,
|
31
|
-
zmq_port_range: Tuple[int, int] = (55050, 56000),
|
32
28
|
|
33
29
|
monitoring_hub_address: str = "127.0.0.1",
|
34
30
|
run_dir: str = ".",
|
@@ -45,9 +41,6 @@ class MonitoringRouter:
|
|
45
41
|
The ip address at which the workers will be able to reach the Hub.
|
46
42
|
udp_port : int
|
47
43
|
The specific port at which workers will be able to reach the Hub via UDP. Default: None
|
48
|
-
zmq_port_range : tuple(int, int)
|
49
|
-
The MonitoringHub picks ports at random from the range which will be used by Hub.
|
50
|
-
Default: (55050, 56000)
|
51
44
|
run_dir : str
|
52
45
|
Parsl log directory paths. Logs and temp files go here. Default: '.'
|
53
46
|
logging_level : int
|
@@ -60,7 +53,7 @@ class MonitoringRouter:
|
|
60
53
|
An event that the main Parsl process will set to signal that the monitoring router should shut down.
|
61
54
|
"""
|
62
55
|
os.makedirs(run_dir, exist_ok=True)
|
63
|
-
self.logger = set_file_logger(f"{run_dir}/
|
56
|
+
self.logger = set_file_logger(f"{run_dir}/monitoring_udp_router.log",
|
64
57
|
name="monitoring_router",
|
65
58
|
level=logging_level)
|
66
59
|
self.logger.debug("Monitoring router starting")
|
@@ -88,37 +81,12 @@ class MonitoringRouter:
|
|
88
81
|
self.udp_sock.settimeout(self.loop_freq / 1000)
|
89
82
|
self.logger.info("Initialized the UDP socket on 0.0.0.0:{}".format(self.udp_port))
|
90
83
|
|
91
|
-
self._context = zmq.Context()
|
92
|
-
self.zmq_receiver_channel = self._context.socket(zmq.DEALER)
|
93
|
-
self.zmq_receiver_channel.setsockopt(zmq.LINGER, 0)
|
94
|
-
self.zmq_receiver_channel.set_hwm(0)
|
95
|
-
self.zmq_receiver_channel.RCVTIMEO = int(self.loop_freq) # in milliseconds
|
96
|
-
self.logger.debug("hub_address: {}. zmq_port_range {}".format(hub_address, zmq_port_range))
|
97
|
-
self.zmq_receiver_port = self.zmq_receiver_channel.bind_to_random_port("tcp://*",
|
98
|
-
min_port=zmq_port_range[0],
|
99
|
-
max_port=zmq_port_range[1])
|
100
|
-
|
101
84
|
self.target_radio = MultiprocessingQueueRadioSender(resource_msgs)
|
102
85
|
self.exit_event = exit_event
|
103
86
|
|
104
87
|
@wrap_with_logs(target="monitoring_router")
|
105
88
|
def start(self) -> None:
|
106
|
-
self.logger.info("Starting UDP listener
|
107
|
-
udp_radio_receiver_thread = threading.Thread(target=self.start_udp_listener, daemon=True)
|
108
|
-
udp_radio_receiver_thread.start()
|
109
|
-
|
110
|
-
self.logger.info("Starting ZMQ listener thread")
|
111
|
-
zmq_radio_receiver_thread = threading.Thread(target=self.start_zmq_listener, daemon=True)
|
112
|
-
zmq_radio_receiver_thread.start()
|
113
|
-
|
114
|
-
self.logger.info("Joining on ZMQ listener thread")
|
115
|
-
zmq_radio_receiver_thread.join()
|
116
|
-
self.logger.info("Joining on UDP listener thread")
|
117
|
-
udp_radio_receiver_thread.join()
|
118
|
-
self.logger.info("Joined on both ZMQ and UDP listener threads")
|
119
|
-
|
120
|
-
@wrap_with_logs(target="monitoring_router")
|
121
|
-
def start_udp_listener(self) -> None:
|
89
|
+
self.logger.info("Starting UDP listener")
|
122
90
|
try:
|
123
91
|
while not self.exit_event.is_set():
|
124
92
|
try:
|
@@ -145,55 +113,24 @@ class MonitoringRouter:
|
|
145
113
|
finally:
|
146
114
|
self.logger.info("UDP listener finished")
|
147
115
|
|
148
|
-
@wrap_with_logs(target="monitoring_router")
|
149
|
-
def start_zmq_listener(self) -> None:
|
150
|
-
try:
|
151
|
-
while not self.exit_event.is_set():
|
152
|
-
try:
|
153
|
-
dfk_loop_start = time.time()
|
154
|
-
while time.time() - dfk_loop_start < 1.0: # TODO make configurable
|
155
|
-
# note that nothing checks that msg really is of the annotated type
|
156
|
-
msg: TaggedMonitoringMessage
|
157
|
-
msg = self.zmq_receiver_channel.recv_pyobj()
|
158
|
-
|
159
|
-
assert isinstance(msg, tuple), "ZMQ Receiver expects only tuples, got {}".format(msg)
|
160
|
-
assert len(msg) >= 1, "ZMQ Receiver expects tuples of length at least 1, got {}".format(msg)
|
161
|
-
assert len(msg) == 2, "ZMQ Receiver expects message tuples of exactly length 2, got {}".format(msg)
|
162
|
-
|
163
|
-
self.target_radio.send(msg)
|
164
|
-
except zmq.Again:
|
165
|
-
pass
|
166
|
-
except Exception:
|
167
|
-
# This will catch malformed messages. What happens if the
|
168
|
-
# channel is broken in such a way that it always raises
|
169
|
-
# an exception? Looping on this would maybe be the wrong
|
170
|
-
# thing to do.
|
171
|
-
self.logger.warning("Failure processing a ZMQ message", exc_info=True)
|
172
|
-
|
173
|
-
self.logger.info("ZMQ listener finishing normally")
|
174
|
-
finally:
|
175
|
-
self.logger.info("ZMQ listener finished")
|
176
|
-
|
177
116
|
|
178
117
|
@wrap_with_logs
|
179
118
|
@typeguard.typechecked
|
180
|
-
def
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
setproctitle("parsl: monitoring router")
|
119
|
+
def udp_router_starter(*,
|
120
|
+
comm_q: mpq.Queue,
|
121
|
+
exception_q: mpq.Queue,
|
122
|
+
resource_msgs: mpq.Queue,
|
123
|
+
exit_event: Event,
|
124
|
+
|
125
|
+
hub_address: str,
|
126
|
+
udp_port: Optional[int],
|
127
|
+
|
128
|
+
run_dir: str,
|
129
|
+
logging_level: int) -> None:
|
130
|
+
setproctitle("parsl: monitoring UDP router")
|
193
131
|
try:
|
194
132
|
router = MonitoringRouter(hub_address=hub_address,
|
195
133
|
udp_port=udp_port,
|
196
|
-
zmq_port_range=zmq_port_range,
|
197
134
|
run_dir=run_dir,
|
198
135
|
logging_level=logging_level,
|
199
136
|
resource_msgs=resource_msgs,
|
@@ -202,11 +139,11 @@ def router_starter(*,
|
|
202
139
|
logger.error("MonitoringRouter construction failed.", exc_info=True)
|
203
140
|
comm_q.put(f"Monitoring router construction failed: {e}")
|
204
141
|
else:
|
205
|
-
comm_q.put(
|
142
|
+
comm_q.put(router.udp_port)
|
206
143
|
|
207
144
|
router.logger.info("Starting MonitoringRouter in router_starter")
|
208
145
|
try:
|
209
146
|
router.start()
|
210
147
|
except Exception as e:
|
211
|
-
router.logger.exception("router
|
148
|
+
router.logger.exception("UDP router start exception")
|
212
149
|
exception_q.put(('Hub', str(e)))
|
@@ -0,0 +1,138 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import logging
|
4
|
+
import multiprocessing.queues as mpq
|
5
|
+
import os
|
6
|
+
import time
|
7
|
+
from multiprocessing.synchronize import Event
|
8
|
+
from typing import Tuple
|
9
|
+
|
10
|
+
import typeguard
|
11
|
+
import zmq
|
12
|
+
|
13
|
+
from parsl.log_utils import set_file_logger
|
14
|
+
from parsl.monitoring.radios.multiprocessing import MultiprocessingQueueRadioSender
|
15
|
+
from parsl.monitoring.types import TaggedMonitoringMessage
|
16
|
+
from parsl.process_loggers import wrap_with_logs
|
17
|
+
from parsl.utils import setproctitle
|
18
|
+
|
19
|
+
logger = logging.getLogger(__name__)
|
20
|
+
|
21
|
+
|
22
|
+
class MonitoringRouter:
|
23
|
+
|
24
|
+
def __init__(self,
|
25
|
+
*,
|
26
|
+
hub_address: str,
|
27
|
+
zmq_port_range: Tuple[int, int] = (55050, 56000),
|
28
|
+
|
29
|
+
run_dir: str = ".",
|
30
|
+
logging_level: int = logging.INFO,
|
31
|
+
resource_msgs: mpq.Queue,
|
32
|
+
exit_event: Event,
|
33
|
+
):
|
34
|
+
""" Initializes a monitoring configuration class.
|
35
|
+
|
36
|
+
Parameters
|
37
|
+
----------
|
38
|
+
hub_address : str
|
39
|
+
The ip address at which the workers will be able to reach the Hub.
|
40
|
+
zmq_port_range : tuple(int, int)
|
41
|
+
The MonitoringHub picks ports at random from the range which will be used by Hub.
|
42
|
+
Default: (55050, 56000)
|
43
|
+
run_dir : str
|
44
|
+
Parsl log directory paths. Logs and temp files go here. Default: '.'
|
45
|
+
logging_level : int
|
46
|
+
Logging level as defined in the logging module. Default: logging.INFO
|
47
|
+
resource_msgs : multiprocessing.Queue
|
48
|
+
A multiprocessing queue to receive messages to be routed onwards to the database process
|
49
|
+
exit_event : Event
|
50
|
+
An event that the main Parsl process will set to signal that the monitoring router should shut down.
|
51
|
+
"""
|
52
|
+
os.makedirs(run_dir, exist_ok=True)
|
53
|
+
self.logger = set_file_logger(f"{run_dir}/monitoring_zmq_router.log",
|
54
|
+
name="monitoring_router",
|
55
|
+
level=logging_level)
|
56
|
+
self.logger.debug("Monitoring router starting")
|
57
|
+
|
58
|
+
self.hub_address = hub_address
|
59
|
+
|
60
|
+
self.loop_freq = 10.0 # milliseconds
|
61
|
+
|
62
|
+
self._context = zmq.Context()
|
63
|
+
self.zmq_receiver_channel = self._context.socket(zmq.DEALER)
|
64
|
+
self.zmq_receiver_channel.setsockopt(zmq.LINGER, 0)
|
65
|
+
self.zmq_receiver_channel.set_hwm(0)
|
66
|
+
self.zmq_receiver_channel.RCVTIMEO = int(self.loop_freq) # in milliseconds
|
67
|
+
self.logger.debug("hub_address: {}. zmq_port_range {}".format(hub_address, zmq_port_range))
|
68
|
+
self.zmq_receiver_port = self.zmq_receiver_channel.bind_to_random_port("tcp://*",
|
69
|
+
min_port=zmq_port_range[0],
|
70
|
+
max_port=zmq_port_range[1])
|
71
|
+
|
72
|
+
self.target_radio = MultiprocessingQueueRadioSender(resource_msgs)
|
73
|
+
self.exit_event = exit_event
|
74
|
+
|
75
|
+
@wrap_with_logs(target="monitoring_router")
|
76
|
+
def start(self) -> None:
|
77
|
+
self.logger.info("Starting ZMQ listener")
|
78
|
+
try:
|
79
|
+
while not self.exit_event.is_set():
|
80
|
+
try:
|
81
|
+
dfk_loop_start = time.time()
|
82
|
+
while time.time() - dfk_loop_start < 1.0: # TODO make configurable
|
83
|
+
# note that nothing checks that msg really is of the annotated type
|
84
|
+
msg: TaggedMonitoringMessage
|
85
|
+
msg = self.zmq_receiver_channel.recv_pyobj()
|
86
|
+
|
87
|
+
assert isinstance(msg, tuple), "ZMQ Receiver expects only tuples, got {}".format(msg)
|
88
|
+
assert len(msg) >= 1, "ZMQ Receiver expects tuples of length at least 1, got {}".format(msg)
|
89
|
+
assert len(msg) == 2, "ZMQ Receiver expects message tuples of exactly length 2, got {}".format(msg)
|
90
|
+
|
91
|
+
self.target_radio.send(msg)
|
92
|
+
except zmq.Again:
|
93
|
+
pass
|
94
|
+
except Exception:
|
95
|
+
# This will catch malformed messages. What happens if the
|
96
|
+
# channel is broken in such a way that it always raises
|
97
|
+
# an exception? Looping on this would maybe be the wrong
|
98
|
+
# thing to do.
|
99
|
+
self.logger.warning("Failure processing a ZMQ message", exc_info=True)
|
100
|
+
|
101
|
+
self.logger.info("ZMQ listener finishing normally")
|
102
|
+
finally:
|
103
|
+
self.logger.info("ZMQ listener finished")
|
104
|
+
|
105
|
+
|
106
|
+
@wrap_with_logs
|
107
|
+
@typeguard.typechecked
|
108
|
+
def zmq_router_starter(*,
|
109
|
+
comm_q: mpq.Queue,
|
110
|
+
exception_q: mpq.Queue,
|
111
|
+
resource_msgs: mpq.Queue,
|
112
|
+
exit_event: Event,
|
113
|
+
|
114
|
+
hub_address: str,
|
115
|
+
zmq_port_range: Tuple[int, int],
|
116
|
+
|
117
|
+
run_dir: str,
|
118
|
+
logging_level: int) -> None:
|
119
|
+
setproctitle("parsl: monitoring zmq router")
|
120
|
+
try:
|
121
|
+
router = MonitoringRouter(hub_address=hub_address,
|
122
|
+
zmq_port_range=zmq_port_range,
|
123
|
+
run_dir=run_dir,
|
124
|
+
logging_level=logging_level,
|
125
|
+
resource_msgs=resource_msgs,
|
126
|
+
exit_event=exit_event)
|
127
|
+
except Exception as e:
|
128
|
+
logger.error("MonitoringRouter construction failed.", exc_info=True)
|
129
|
+
comm_q.put(f"Monitoring router construction failed: {e}")
|
130
|
+
else:
|
131
|
+
comm_q.put(router.zmq_receiver_port)
|
132
|
+
|
133
|
+
router.logger.info("Starting MonitoringRouter in router_starter")
|
134
|
+
try:
|
135
|
+
router.start()
|
136
|
+
except Exception as e:
|
137
|
+
router.logger.exception("ZMQ router start exception")
|
138
|
+
exception_q.put(('Hub', str(e)))
|
@@ -161,3 +161,28 @@ def test_MPISched_contention():
|
|
161
161
|
assert task_on_worker_side['task_id'] == 2
|
162
162
|
_, _, _, resource_spec = unpack_res_spec_apply_message(task_on_worker_side['buffer'])
|
163
163
|
assert len(resource_spec['MPI_NODELIST'].split(',')) == 8
|
164
|
+
|
165
|
+
|
166
|
+
@pytest.mark.local
|
167
|
+
def test_hashable_backlog_queue():
|
168
|
+
"""Run multiple large tasks that to force entry into backlog_queue
|
169
|
+
where queue.PriorityQueue expects hashability/comparability
|
170
|
+
"""
|
171
|
+
|
172
|
+
task_q, result_q = SpawnContext.Queue(), SpawnContext.Queue()
|
173
|
+
scheduler = MPITaskScheduler(task_q, result_q)
|
174
|
+
|
175
|
+
assert scheduler.available_nodes
|
176
|
+
assert len(scheduler.available_nodes) == 8
|
177
|
+
|
178
|
+
assert scheduler._free_node_counter.value == 8
|
179
|
+
|
180
|
+
for i in range(3):
|
181
|
+
mock_task_buffer = pack_res_spec_apply_message("func", "args", "kwargs",
|
182
|
+
resource_specification={
|
183
|
+
"num_nodes": 8,
|
184
|
+
"ranks_per_node": 2
|
185
|
+
})
|
186
|
+
task_package = {"task_id": i, "buffer": mock_task_buffer}
|
187
|
+
scheduler.put_task(task_package)
|
188
|
+
assert scheduler._backlog_queue.qsize() == 2, "Expected 2 backlogged tasks"
|
@@ -14,6 +14,9 @@ from parsl.providers import LocalProvider
|
|
14
14
|
# timeout later on.
|
15
15
|
BLOCK_COUNT = 3
|
16
16
|
|
17
|
+
# the try_assert timeout for the above number of blocks to get started
|
18
|
+
PERMITTED_STARTUP_TIME_S = 30
|
19
|
+
|
17
20
|
|
18
21
|
class AccumulatingLocalProvider(LocalProvider):
|
19
22
|
def __init__(self, *args, **kwargs):
|
@@ -67,7 +70,7 @@ def test_shutdown_scalein_blocks(tmpd_cwd, try_assert):
|
|
67
70
|
|
68
71
|
with parsl.load(config):
|
69
72
|
# this will wait for everything to be scaled out fully
|
70
|
-
try_assert(lambda: len(htex.connected_managers()) == BLOCK_COUNT)
|
73
|
+
try_assert(lambda: len(htex.connected_managers()) == BLOCK_COUNT, timeout_ms=PERMITTED_STARTUP_TIME_S * 1000)
|
71
74
|
|
72
75
|
assert len(accumulating_provider.submit_job_ids) == BLOCK_COUNT, f"Exactly {BLOCK_COUNT} blocks should have been launched"
|
73
76
|
assert len(accumulating_provider.cancel_job_ids) == BLOCK_COUNT, f"Exactly {BLOCK_COUNT} blocks should have been scaled in"
|
@@ -30,7 +30,7 @@ def test_no_kills():
|
|
30
30
|
|
31
31
|
@pytest.mark.local
|
32
32
|
@pytest.mark.parametrize("sig", [signal.SIGINT, signal.SIGTERM, signal.SIGKILL, signal.SIGQUIT])
|
33
|
-
@pytest.mark.parametrize("process_attr", ["
|
33
|
+
@pytest.mark.parametrize("process_attr", ["zmq_router_proc", "udp_router_proc", "dbm_proc", "filesystem_proc"])
|
34
34
|
def test_kill_monitoring_helper_process(sig, process_attr, try_assert):
|
35
35
|
"""This tests that we can kill a monitoring process and still have successful shutdown.
|
36
36
|
SIGINT emulates some racy behaviour when ctrl-C is pressed: that
|
parsl/utils.py
CHANGED
parsl/version.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: parsl
|
3
|
-
Version: 2025.
|
3
|
+
Version: 2025.3.10
|
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.03.10.tar.gz
|
7
7
|
Author: The Parsl Team
|
8
8
|
Author-email: parsl@googlegroups.com
|
9
9
|
License: Apache 2.0
|
@@ -7,8 +7,8 @@ parsl/log_utils.py,sha256=u14Fkl5eDfS4HMpl0JjseNNPdbvPaugWPRQj1_af_Zo,3273
|
|
7
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
|
-
parsl/utils.py,sha256=
|
11
|
-
parsl/version.py,sha256=
|
10
|
+
parsl/utils.py,sha256=codTX6_KLhgeTwNkRzc1lo4bgc1M93eJ-lkqOO98fvk,14331
|
11
|
+
parsl/version.py,sha256=3cSnT_xfCul6H60imXWe7VlUXG29OzzoAFknr7Fc3TQ,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,14 +73,14 @@ 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=
|
76
|
+
parsl/executors/high_throughput/executor.py,sha256=esMYMgPHmgD0wPTb0U61vBX96DaPrWj9sQLzpKvB06k,38752
|
77
77
|
parsl/executors/high_throughput/interchange.py,sha256=7sKIvxP3a7HSzqEq25ZCpABx-1Q2f585pFDGzUvo7_4,29459
|
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
|
81
81
|
parsl/executors/high_throughput/mpi_executor.py,sha256=U-aatbLF_Mu1p6lP0HmT7Yn1Swn3cc7hPmDfuUb9TpI,4797
|
82
82
|
parsl/executors/high_throughput/mpi_prefix_composer.py,sha256=DmpKugANNa1bdYlqQBLHkrFc15fJpefPPhW9hkAlh1s,4308
|
83
|
-
parsl/executors/high_throughput/mpi_resource_management.py,sha256=
|
83
|
+
parsl/executors/high_throughput/mpi_resource_management.py,sha256=xeJp4h4LysG8KuBLKqy1sgFahL1eqiG7XLpr09VLwy4,8144
|
84
84
|
parsl/executors/high_throughput/probe.py,sha256=QOEaliO3x5cB6ltMOZMsZQ-ath9AAuFqXcBzRgWOM60,2754
|
85
85
|
parsl/executors/high_throughput/process_worker_pool.py,sha256=YOJvTUMg3eIHr9fYfBWFHRiI1QQ898IGiuXyj5VRQNo,41084
|
86
86
|
parsl/executors/high_throughput/zmq_pipes.py,sha256=NUK25IEh0UkxzdqQQyM8tMtuZmjSiTeWu1DzkkAIOhA,8980
|
@@ -92,7 +92,7 @@ parsl/executors/taskvine/__init__.py,sha256=9rwp3M8B0YyEhZMLO0RHaNw7u1nc01WHbXLq
|
|
92
92
|
parsl/executors/taskvine/errors.py,sha256=euIYkSslrNSI85kyi2s0xzOaO9ik4c1fYHstMIeiBJk,652
|
93
93
|
parsl/executors/taskvine/exec_parsl_function.py,sha256=ftGdJU78lKPPkphSHlEi4rj164mhuMHJjghVqfgeXKk,7085
|
94
94
|
parsl/executors/taskvine/executor.py,sha256=4c0mt83G-F4ZFMxhdJByvYjG05QdLrLYYHsmpPXY6YE,30906
|
95
|
-
parsl/executors/taskvine/factory.py,sha256=
|
95
|
+
parsl/executors/taskvine/factory.py,sha256=GU5JryEAKJuYKwrSc162BN-lhcKhapvBZHT820pxwic,2772
|
96
96
|
parsl/executors/taskvine/factory_config.py,sha256=ZQC5vyDe8cM0nuv7fbBCV2xnWGAZ87iLlT2UqmFFI1U,3695
|
97
97
|
parsl/executors/taskvine/manager.py,sha256=SUi5mqqMm_rnkBLrZtTQe7RiHqWDn1oOejQscYzfwAU,25797
|
98
98
|
parsl/executors/taskvine/manager_config.py,sha256=96G1LMBvgg74sHX4UcOzkCXhEdtVXry4ZzMDEYLWBTQ,7669
|
@@ -100,7 +100,7 @@ parsl/executors/taskvine/utils.py,sha256=iSrIogeiauL3UNy_9tiZp1cBSNn6fIJkMYQRVi1
|
|
100
100
|
parsl/executors/workqueue/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
101
101
|
parsl/executors/workqueue/errors.py,sha256=XO2naYhAsHHyiOBH6hpObg3mPNDmvMoFqErsj0-v7jc,541
|
102
102
|
parsl/executors/workqueue/exec_parsl_function.py,sha256=YXKVVIa4zXmOtz-0Ca4E_5nQfN_3S2bh2tB75uZZB4w,7774
|
103
|
-
parsl/executors/workqueue/executor.py,sha256=
|
103
|
+
parsl/executors/workqueue/executor.py,sha256=QYJ02jt0AG-83XN0-mUa9LSUezroYSbh1OkOGzGLIqo,49693
|
104
104
|
parsl/executors/workqueue/parsl_coprocess.py,sha256=cF1UmTgVLoey6QzBcbYgEiEsRidSaFfuO54f1HFw_EM,5737
|
105
105
|
parsl/executors/workqueue/parsl_coprocess_stub.py,sha256=_bJmpPIgL42qM6bVzeEKt1Mn1trSP41rtJguXxPGfHI,735
|
106
106
|
parsl/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -117,19 +117,21 @@ parsl/monitoring/__init__.py,sha256=0ywNz6i0lM1xo_7_BIxhETDGeVd2C_0wwD7qgeaMR4c,
|
|
117
117
|
parsl/monitoring/db_manager.py,sha256=ra5PqmbUstfDx0o_bkBYI8GIUi461-GV3b4A-Q6DVVE,33300
|
118
118
|
parsl/monitoring/errors.py,sha256=D6jpYzEzp0d6FmVKGqhvjAxr4ztZfJX2s-aXemH9bBU,148
|
119
119
|
parsl/monitoring/message_type.py,sha256=Khn88afNxcOIciKiCK4GLnn90I5BlRTiOL3zK-P07yQ,401
|
120
|
-
parsl/monitoring/monitoring.py,sha256=
|
120
|
+
parsl/monitoring/monitoring.py,sha256=p79F982lyPsplXeTVxqlvNuB8G1p3PAI8nTMHcZJ5UE,13113
|
121
121
|
parsl/monitoring/remote.py,sha256=t0qCTUMCzeJ_JOARFpjqlTNrAWdEb20BxhmZh9X7kEM,13728
|
122
|
-
parsl/monitoring/router.py,sha256=GUNdvixMcVGITk5LHEfgtbKBE7DqRtQIO-fkR4Z_VYM,9224
|
123
122
|
parsl/monitoring/types.py,sha256=oOCrzv-ab-_rv4pb8o58Sdb8G_RGp1aZriRbdf9zBEk,339
|
124
123
|
parsl/monitoring/queries/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
125
124
|
parsl/monitoring/queries/pandas.py,sha256=0Z2r0rjTKCemf0eaDkF1irvVHn5g7KC5SYETvQPRxwU,2232
|
126
125
|
parsl/monitoring/radios/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
127
126
|
parsl/monitoring/radios/base.py,sha256=Ep5kHf07Sm-ApMBJVudRhoWRyuiu0udjO4NvEir5LEk,291
|
128
127
|
parsl/monitoring/radios/filesystem.py,sha256=ioZ3jOKX5Qf0DYRtWmpCEorfuMVbS58OMS_QV7DOFOs,1765
|
128
|
+
parsl/monitoring/radios/filesystem_router.py,sha256=ZxPImntYHw-3arvCTMYOC67kUgkyk7I7XLDrCXnvkBw,2055
|
129
129
|
parsl/monitoring/radios/htex.py,sha256=qBu4O5NYnSETHX0ptdwxSpqa2Pp3Z_V6a6lb3TbjKm4,1643
|
130
130
|
parsl/monitoring/radios/multiprocessing.py,sha256=fsfaaoMDp6VJv1DSAl-P0R2ofO6jp13byx6NsPItV3Y,655
|
131
131
|
parsl/monitoring/radios/udp.py,sha256=bTpt7JYp-5hyBBLzgiLj1_BlSTn28UVp39OYgVGLXCw,1613
|
132
|
+
parsl/monitoring/radios/udp_router.py,sha256=Dtat4lVNz4cpnzZmXTjo5VA1Xcri5VTSNNpyepSjIVE,5868
|
132
133
|
parsl/monitoring/radios/zmq.py,sha256=fhoHp9ylhf-D3eTJb2aSHRsuic8-FJ_oRNGnniGkCAI,592
|
134
|
+
parsl/monitoring/radios/zmq_router.py,sha256=oKfMg_dc3UxJcSzDe1ZqkGJYQcOa4somvyGPzwOqQuA,5860
|
133
135
|
parsl/monitoring/visualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
134
136
|
parsl/monitoring/visualization/app.py,sha256=xMeRlAnzl5lHddAOdSBcqY3D5lmOYw3Z3Z2_YyoVwnw,1425
|
135
137
|
parsl/monitoring/visualization/models.py,sha256=C7CcF6w6PhtrdvDX9VgDH-aSrpLfvYU1fJ4-HDUeFVQ,5138
|
@@ -347,7 +349,7 @@ parsl/tests/test_mpi_apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
347
349
|
parsl/tests/test_mpi_apps/test_bad_mpi_config.py,sha256=QKvEUSrHIBrvqu2fRj1MAqxsYxDfcrdQ7dzWdOZejuU,1320
|
348
350
|
parsl/tests/test_mpi_apps/test_mpi_mode_enabled.py,sha256=_fpiaDq9yEUuBxTiuxLFsBt5r1oX9S-3S-YL5yRB13E,5423
|
349
351
|
parsl/tests/test_mpi_apps/test_mpi_prefix.py,sha256=yJslZvYK3JeL9UgxMwF9DDPR9QD4zJLGVjubD0F-utc,1950
|
350
|
-
parsl/tests/test_mpi_apps/test_mpi_scheduler.py,sha256=
|
352
|
+
parsl/tests/test_mpi_apps/test_mpi_scheduler.py,sha256=LPvk5wywYANQNCoQ8muwOLEznnZqwler4jJglinAT9I,7370
|
351
353
|
parsl/tests/test_mpi_apps/test_mpiex.py,sha256=mlFdHK3A1B6NsEhxTQQX8lhs9qVza36FMG99vNrBRW4,2021
|
352
354
|
parsl/tests/test_mpi_apps/test_resource_spec.py,sha256=5k6HM2jtb6sa7jetpI-Tl1nPQiN33VLaM7YT10c307E,3756
|
353
355
|
parsl/tests/test_providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -409,7 +411,7 @@ parsl/tests/test_scaling/test_regression_3696_oscillation.py,sha256=7Xc3vgocXXUb
|
|
409
411
|
parsl/tests/test_scaling/test_scale_down.py,sha256=vHJOMRUriW6xPtaY8GTKYXd5P0WJkQV6Q1IPui05aLU,2736
|
410
412
|
parsl/tests/test_scaling/test_scale_down_htex_auto_scale.py,sha256=EnVNllKO2AGKkGa6927cLrzvvG6mpNQeFDzVktv6x08,4521
|
411
413
|
parsl/tests/test_scaling/test_scale_down_htex_unregistered.py,sha256=OrdnYmd58n7UfkANPJ7mzha4WSCPdbgJRX1O1Zdu0tI,1954
|
412
|
-
parsl/tests/test_scaling/test_shutdown_scalein.py,sha256=
|
414
|
+
parsl/tests/test_scaling/test_shutdown_scalein.py,sha256=sr40of5DwxeyQI97MDZxFqJILZSXZJb9Dv7qTf2gql8,2471
|
413
415
|
parsl/tests/test_scaling/test_worker_interchange_bad_messages_3262.py,sha256=GaXmRli1srTal-JQmCGDTP4BAwAKI_daXMmrjULsZkY,2788
|
414
416
|
parsl/tests/test_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
415
417
|
parsl/tests/test_serialization/test_2555_caching_deserializer.py,sha256=jEXJvbriaLVI7frV5t-iJRKYyeQ7a9_-t3X9lhhBWQo,767
|
@@ -420,7 +422,7 @@ parsl/tests/test_serialization/test_pack_resource_spec.py,sha256=-Vtyh8KyezZw8e7
|
|
420
422
|
parsl/tests/test_serialization/test_proxystore_configured.py,sha256=lGWOSEWul16enDWhW-s7CK0d3eMDzm1324Fmj0cZMVU,2293
|
421
423
|
parsl/tests/test_serialization/test_proxystore_impl.py,sha256=uGd45sfPm9rJhzqKV0rI3lqdSOAUddQf-diEpcJAlcY,1228
|
422
424
|
parsl/tests/test_shutdown/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
423
|
-
parsl/tests/test_shutdown/test_kill_monitoring.py,sha256=
|
425
|
+
parsl/tests/test_shutdown/test_kill_monitoring.py,sha256=BycTDLwxhHbbV68Qkgrmn8UUzSr55SvbNvydp35UCTM,1948
|
424
426
|
parsl/tests/test_staging/__init__.py,sha256=WZl9EHSkfYiSoE3Gbulcq2ifmn7IFGUkasJIobL5T5A,208
|
425
427
|
parsl/tests/test_staging/staging_provider.py,sha256=6FDpImkWOLgysqM68NbCAoXZciZokI8dmBWRAxnggBk,3242
|
426
428
|
parsl/tests/test_staging/test_1316.py,sha256=eS0e2BDM2vmPNF60aDr35wcuGgDPfXjTjRV6kyBZOQc,2652
|
@@ -455,13 +457,13 @@ parsl/usage_tracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
455
457
|
parsl/usage_tracking/api.py,sha256=iaCY58Dc5J4UM7_dJzEEs871P1p1HdxBMtNGyVdzc9g,1821
|
456
458
|
parsl/usage_tracking/levels.py,sha256=xbfzYEsd55KiZJ-mzNgPebvOH4rRHum04hROzEf41tU,291
|
457
459
|
parsl/usage_tracking/usage.py,sha256=f9k6QcpbQxkGyP5WTC9PVyv0CA05s9NDpRe5wwRdBTM,9163
|
458
|
-
parsl-2025.
|
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.
|
460
|
+
parsl-2025.3.10.data/scripts/exec_parsl_function.py,sha256=YXKVVIa4zXmOtz-0Ca4E_5nQfN_3S2bh2tB75uZZB4w,7774
|
461
|
+
parsl-2025.3.10.data/scripts/interchange.py,sha256=17MrOc7-FXxKBWTwkzIbUoa8fvvDfPelfjByd3ZD2Wk,29446
|
462
|
+
parsl-2025.3.10.data/scripts/parsl_coprocess.py,sha256=zrVjEqQvFOHxsLufPi00xzMONagjVwLZbavPM7bbjK4,5722
|
463
|
+
parsl-2025.3.10.data/scripts/process_worker_pool.py,sha256=BbVJ1PS7ZW2grz0iAPPV0BgJyRMyQ7bbXSzLzWCBkyU,41070
|
464
|
+
parsl-2025.3.10.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
465
|
+
parsl-2025.3.10.dist-info/METADATA,sha256=rEtmq9LYtfBbXFR2JuX9DmmPQyDshdmj0GuakTMQeSM,4027
|
466
|
+
parsl-2025.3.10.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
467
|
+
parsl-2025.3.10.dist-info/entry_points.txt,sha256=XqnsWDYoEcLbsMcpnYGKLEnSBmaIe1YoM5YsBdJG2tI,176
|
468
|
+
parsl-2025.3.10.dist-info/top_level.txt,sha256=PIheYoUFQtF2icLsgOykgU-Cjuwr2Oi6On2jo5RYgRM,6
|
469
|
+
parsl-2025.3.10.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|