parsl 2025.3.17__py3-none-any.whl → 2025.3.24__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.
Files changed (30) hide show
  1. parsl/dataflow/dflow.py +1 -3
  2. parsl/executors/base.py +13 -37
  3. parsl/executors/flux/executor.py +1 -0
  4. parsl/executors/globus_compute.py +1 -1
  5. parsl/executors/high_throughput/executor.py +18 -0
  6. parsl/executors/radical/executor.py +1 -0
  7. parsl/executors/status_handling.py +8 -0
  8. parsl/executors/taskvine/executor.py +1 -0
  9. parsl/executors/workqueue/executor.py +1 -0
  10. parsl/monitoring/errors.py +5 -0
  11. parsl/monitoring/monitoring.py +55 -115
  12. parsl/monitoring/radios/zmq_router.py +80 -18
  13. parsl/multiprocessing.py +42 -2
  14. parsl/tests/test_monitoring/test_exit_helper.py +6 -7
  15. parsl/tests/test_monitoring/test_fuzz_zmq.py +1 -1
  16. parsl/tests/test_monitoring/test_radio_zmq.py +27 -0
  17. parsl/tests/test_monitoring/test_stdouterr.py +3 -0
  18. parsl/tests/test_shutdown/test_kill_monitoring.py +1 -1
  19. parsl/usage_tracking/usage.py +2 -2
  20. parsl/version.py +1 -1
  21. {parsl-2025.3.17.dist-info → parsl-2025.3.24.dist-info}/METADATA +2 -2
  22. {parsl-2025.3.17.dist-info → parsl-2025.3.24.dist-info}/RECORD +30 -29
  23. {parsl-2025.3.17.data → parsl-2025.3.24.data}/scripts/exec_parsl_function.py +0 -0
  24. {parsl-2025.3.17.data → parsl-2025.3.24.data}/scripts/interchange.py +0 -0
  25. {parsl-2025.3.17.data → parsl-2025.3.24.data}/scripts/parsl_coprocess.py +0 -0
  26. {parsl-2025.3.17.data → parsl-2025.3.24.data}/scripts/process_worker_pool.py +0 -0
  27. {parsl-2025.3.17.dist-info → parsl-2025.3.24.dist-info}/LICENSE +0 -0
  28. {parsl-2025.3.17.dist-info → parsl-2025.3.24.dist-info}/WHEEL +0 -0
  29. {parsl-2025.3.17.dist-info → parsl-2025.3.24.dist-info}/entry_points.txt +0 -0
  30. {parsl-2025.3.17.dist-info → parsl-2025.3.24.dist-info}/top_level.txt +0 -0
parsl/dataflow/dflow.py CHANGED
@@ -1128,9 +1128,7 @@ class DataFlowKernel:
1128
1128
  executor.run_id = self.run_id
1129
1129
  executor.run_dir = self.run_dir
1130
1130
  if self.monitoring:
1131
- executor.hub_address = self.monitoring.hub_address
1132
- executor.hub_zmq_port = self.monitoring.hub_zmq_port
1133
- executor.submit_monitoring_radio = self.monitoring.radio
1131
+ executor.monitoring_messages = self.monitoring.resource_msgs
1134
1132
  if hasattr(executor, 'provider'):
1135
1133
  if hasattr(executor.provider, 'script_dir'):
1136
1134
  executor.provider.script_dir = os.path.join(self.run_dir, 'submit_scripts')
parsl/executors/base.py CHANGED
@@ -1,11 +1,14 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  from abc import ABCMeta, abstractmethod
3
5
  from concurrent.futures import Future
6
+ from multiprocessing.queues import Queue
4
7
  from typing import Any, Callable, Dict, Optional
5
8
 
6
9
  from typing_extensions import Literal, Self
7
10
 
8
- from parsl.monitoring.radios.base import MonitoringRadioSender
11
+ from parsl.monitoring.types import TaggedMonitoringMessage
9
12
 
10
13
 
11
14
  class ParslExecutor(metaclass=ABCMeta):
@@ -42,6 +45,13 @@ class ParslExecutor(metaclass=ABCMeta):
42
45
  invariant, not co-variant, and it looks like @typeguard cannot be
43
46
  persuaded otherwise. So if you're implementing an executor and want to
44
47
  @typeguard the constructor, you'll have to use List[Any] here.
48
+
49
+ The DataFlowKernel will set this attribute before calling .start(),
50
+ if monitoring is enabled:
51
+
52
+ monitoring_messages: Optional[Queue[TaggedMonitoringMessage]] - an executor
53
+ can send messages to the monitoring hub by putting them into
54
+ this queue.
45
55
  """
46
56
 
47
57
  label: str = "undefined"
@@ -50,15 +60,11 @@ class ParslExecutor(metaclass=ABCMeta):
50
60
  def __init__(
51
61
  self,
52
62
  *,
53
- hub_address: Optional[str] = None,
54
- hub_zmq_port: Optional[int] = None,
55
- submit_monitoring_radio: Optional[MonitoringRadioSender] = None,
63
+ monitoring_messages: Optional[Queue[TaggedMonitoringMessage]] = None,
56
64
  run_dir: str = ".",
57
65
  run_id: Optional[str] = None,
58
66
  ):
59
- self.hub_address = hub_address
60
- self.hub_zmq_port = hub_zmq_port
61
- self.submit_monitoring_radio = submit_monitoring_radio
67
+ self.monitoring_messages = monitoring_messages
62
68
  self.run_dir = os.path.abspath(run_dir)
63
69
  self.run_id = run_id
64
70
 
@@ -125,33 +131,3 @@ class ParslExecutor(metaclass=ABCMeta):
125
131
  @run_id.setter
126
132
  def run_id(self, value: Optional[str]) -> None:
127
133
  self._run_id = value
128
-
129
- @property
130
- def hub_address(self) -> Optional[str]:
131
- """Address to the Hub for monitoring.
132
- """
133
- return self._hub_address
134
-
135
- @hub_address.setter
136
- def hub_address(self, value: Optional[str]) -> None:
137
- self._hub_address = value
138
-
139
- @property
140
- def hub_zmq_port(self) -> Optional[int]:
141
- """Port to the Hub for monitoring.
142
- """
143
- return self._hub_zmq_port
144
-
145
- @hub_zmq_port.setter
146
- def hub_zmq_port(self, value: Optional[int]) -> None:
147
- self._hub_zmq_port = value
148
-
149
- @property
150
- def submit_monitoring_radio(self) -> Optional[MonitoringRadioSender]:
151
- """Local radio for sending monitoring messages
152
- """
153
- return self._submit_monitoring_radio
154
-
155
- @submit_monitoring_radio.setter
156
- def submit_monitoring_radio(self, value: Optional[MonitoringRadioSender]) -> None:
157
- self._submit_monitoring_radio = value
@@ -231,6 +231,7 @@ class FluxExecutor(ParslExecutor, RepresentationMixin):
231
231
 
232
232
  def start(self):
233
233
  """Called when DFK starts the executor when the config is loaded."""
234
+ super().start()
234
235
  os.makedirs(self.working_dir, exist_ok=True)
235
236
  self._submission_thread.start()
236
237
 
@@ -67,7 +67,7 @@ class GlobusComputeExecutor(ParslExecutor, RepresentationMixin):
67
67
 
68
68
  def start(self) -> None:
69
69
  """ Start the Globus Compute Executor """
70
- pass
70
+ super().start()
71
71
 
72
72
  def submit(self, func: Callable, resource_specification: Dict[str, Any], *args: Any, **kwargs: Any) -> Future:
73
73
  """ Submit func to globus-compute
@@ -29,6 +29,7 @@ from parsl.executors.high_throughput.manager_selector import (
29
29
  )
30
30
  from parsl.executors.status_handling import BlockProviderExecutor
31
31
  from parsl.jobs.states import TERMINAL_STATES, JobState, JobStatus
32
+ from parsl.monitoring.radios.zmq_router import ZMQRadioReceiver, start_zmq_receiver
32
33
  from parsl.process_loggers import wrap_with_logs
33
34
  from parsl.providers import LocalProvider
34
35
  from parsl.providers.base import ExecutionProvider
@@ -334,6 +335,10 @@ class HighThroughputExecutor(BlockProviderExecutor, RepresentationMixin, UsageIn
334
335
  self._result_queue_thread_exit = threading.Event()
335
336
  self._result_queue_thread: Optional[threading.Thread] = None
336
337
 
338
+ self.zmq_monitoring: Optional[ZMQRadioReceiver]
339
+ self.zmq_monitoring = None
340
+ self.hub_zmq_port = None
341
+
337
342
  radio_mode = "htex"
338
343
  enable_mpi_mode: bool = False
339
344
  mpi_launcher: str = "mpiexec"
@@ -407,6 +412,7 @@ class HighThroughputExecutor(BlockProviderExecutor, RepresentationMixin, UsageIn
407
412
  def start(self):
408
413
  """Create the Interchange process and connect to it.
409
414
  """
415
+ super().start()
410
416
  if self.encrypted and self.cert_dir is None:
411
417
  logger.debug("Creating CurveZMQ certificates")
412
418
  self.cert_dir = curvezmq.create_certificates(self.logdir)
@@ -427,6 +433,15 @@ class HighThroughputExecutor(BlockProviderExecutor, RepresentationMixin, UsageIn
427
433
  self.loopback_address, self.interchange_port_range, self.cert_dir
428
434
  )
429
435
 
436
+ if self.monitoring_messages is not None:
437
+ self.zmq_monitoring = start_zmq_receiver(monitoring_messages=self.monitoring_messages,
438
+ loopback_address=self.loopback_address,
439
+ port_range=self.interchange_port_range,
440
+ logdir=self.logdir,
441
+ worker_debug=self.worker_debug,
442
+ )
443
+ self.hub_zmq_port = self.zmq_monitoring.port
444
+
430
445
  self._result_queue_thread = None
431
446
  self._start_result_queue_thread()
432
447
  self._start_local_interchange_process()
@@ -861,6 +876,9 @@ class HighThroughputExecutor(BlockProviderExecutor, RepresentationMixin, UsageIn
861
876
  if self._result_queue_thread:
862
877
  self._result_queue_thread.join()
863
878
 
879
+ if self.zmq_monitoring:
880
+ self.zmq_monitoring.close()
881
+
864
882
  logger.info("Finished HighThroughputExecutor shutdown attempt")
865
883
 
866
884
  def get_usage_information(self):
@@ -215,6 +215,7 @@ class RadicalPilotExecutor(ParslExecutor, RepresentationMixin):
215
215
  """Create the Pilot component and pass it.
216
216
  """
217
217
  logger.info("starting RadicalPilotExecutor")
218
+ super().start()
218
219
  logger.info('Parsl: {0}'.format(parsl.__version__))
219
220
  logger.info('RADICAL pilot: {0}'.format(rp.version))
220
221
  self.session = rp.Session(cfg={'base': self.run_dir},
@@ -14,6 +14,7 @@ from parsl.executors.errors import BadStateException, ScalingFailed
14
14
  from parsl.jobs.error_handlers import noop_error_handler, simple_error_handler
15
15
  from parsl.jobs.states import TERMINAL_STATES, JobState, JobStatus
16
16
  from parsl.monitoring.message_type import MessageType
17
+ from parsl.monitoring.radios.multiprocessing import MultiprocessingQueueRadioSender
17
18
  from parsl.providers.base import ExecutionProvider
18
19
  from parsl.utils import AtomicIDCounter
19
20
 
@@ -83,6 +84,13 @@ class BlockProviderExecutor(ParslExecutor):
83
84
  # of pending, active and recently terminated blocks
84
85
  self._status = {} # type: Dict[str, JobStatus]
85
86
 
87
+ self.submit_monitoring_radio: Optional[MultiprocessingQueueRadioSender] = None
88
+
89
+ def start(self):
90
+ super().start()
91
+ if self.monitoring_messages:
92
+ self.submit_monitoring_radio = MultiprocessingQueueRadioSender(self.monitoring_messages)
93
+
86
94
  def _make_status_dict(self, block_ids: List[str], status_list: List[JobStatus]) -> Dict[str, JobStatus]:
87
95
  """Given a list of block ids and a list of corresponding status strings,
88
96
  returns a dictionary mapping each block id to the corresponding status
@@ -239,6 +239,7 @@ class TaskVineExecutor(BlockProviderExecutor, putils.RepresentationMixin):
239
239
  retrieve Parsl tasks within the TaskVine system.
240
240
  """
241
241
 
242
+ super().start()
242
243
  # Synchronize connection and communication settings between the manager and factory
243
244
  self.__synchronize_manager_factory_comm_settings()
244
245
 
@@ -314,6 +314,7 @@ class WorkQueueExecutor(BlockProviderExecutor, putils.RepresentationMixin):
314
314
  """Create submit process and collector thread to create, send, and
315
315
  retrieve Parsl tasks within the Work Queue system.
316
316
  """
317
+ super().start()
317
318
  self.tasks_lock = threading.Lock()
318
319
 
319
320
  # Create directories for data and results
@@ -4,3 +4,8 @@ from parsl.errors import ParslError
4
4
  class MonitoringHubStartError(ParslError):
5
5
  def __str__(self) -> str:
6
6
  return "Hub failed to start"
7
+
8
+
9
+ class MonitoringRouterStartError(ParslError):
10
+ def __str__(self) -> str:
11
+ return "Monitoring router failed to start"
@@ -4,10 +4,9 @@ import logging
4
4
  import multiprocessing.synchronize as ms
5
5
  import os
6
6
  import queue
7
- from multiprocessing import Event
8
- from multiprocessing.context import ForkProcess as ForkProcessType
7
+ import warnings
9
8
  from multiprocessing.queues import Queue
10
- from typing import TYPE_CHECKING, Optional, Tuple, Union
9
+ from typing import TYPE_CHECKING, Any, Optional, Union
11
10
 
12
11
  import typeguard
13
12
 
@@ -15,9 +14,13 @@ from parsl.monitoring.errors import MonitoringHubStartError
15
14
  from parsl.monitoring.radios.filesystem_router import filesystem_router_starter
16
15
  from parsl.monitoring.radios.multiprocessing import MultiprocessingQueueRadioSender
17
16
  from parsl.monitoring.radios.udp_router import udp_router_starter
18
- from parsl.monitoring.radios.zmq_router import zmq_router_starter
19
17
  from parsl.monitoring.types import TaggedMonitoringMessage
20
- from parsl.multiprocessing import ForkProcess, SizedQueue
18
+ from parsl.multiprocessing import (
19
+ SizedQueue,
20
+ SpawnEvent,
21
+ SpawnProcess,
22
+ join_terminate_close_proc,
23
+ )
21
24
  from parsl.utils import RepresentationMixin
22
25
 
23
26
  _db_manager_excepts: Optional[Exception]
@@ -38,7 +41,7 @@ class MonitoringHub(RepresentationMixin):
38
41
  def __init__(self,
39
42
  hub_address: str,
40
43
  hub_port: Optional[int] = None,
41
- hub_port_range: Tuple[int, int] = (55050, 56000),
44
+ hub_port_range: Any = None,
42
45
 
43
46
  workflow_name: Optional[str] = None,
44
47
  workflow_version: Optional[str] = None,
@@ -57,12 +60,11 @@ class MonitoringHub(RepresentationMixin):
57
60
  Note that despite the similar name, this is not related to
58
61
  hub_port_range.
59
62
  Default: None
60
- hub_port_range : tuple(int, int)
61
- The port range for a ZMQ channel from an executor process
62
- (for example, the interchange in the High Throughput Executor)
63
- to deliver monitoring messages to the monitoring router.
64
- Note that despite the similar name, this is not related to hub_port.
65
- Default: (55050, 56000)
63
+ hub_port_range : unused
64
+ Unused, but retained until 2025-09-14 to avoid configuration errors.
65
+ This value previously configured one ZMQ channel inside the
66
+ HighThroughputExecutor. That ZMQ channel is now configured by the
67
+ interchange_port_range parameter of HighThroughputExecutor.
66
68
  workflow_name : str
67
69
  The name for the workflow. Default to the name of the parsl script
68
70
  workflow_version : str
@@ -89,6 +91,13 @@ class MonitoringHub(RepresentationMixin):
89
91
 
90
92
  self.hub_address = hub_address
91
93
  self.hub_port = hub_port
94
+
95
+ if hub_port_range is not None:
96
+ message = "Instead of MonitoringHub.hub_port_range, Use HighThroughputExecutor.interchange_port_range"
97
+ warnings.warn(message, DeprecationWarning)
98
+ logger.warning(message)
99
+ # This is used by RepresentationMixin so needs to exist as an attribute
100
+ # even though now it is otherwise unused.
92
101
  self.hub_port_range = hub_port_range
93
102
 
94
103
  self.logging_endpoint = logging_endpoint
@@ -120,90 +129,59 @@ class MonitoringHub(RepresentationMixin):
120
129
  # in the future, Queue will allow runtime subscripts.
121
130
 
122
131
  if TYPE_CHECKING:
123
- zmq_comm_q: Queue[Union[int, str]]
124
132
  udp_comm_q: Queue[Union[int, str]]
125
133
  else:
126
- zmq_comm_q: Queue
127
134
  udp_comm_q: Queue
128
135
 
129
- zmq_comm_q = SizedQueue(maxsize=10)
130
136
  udp_comm_q = SizedQueue(maxsize=10)
131
137
 
132
138
  self.resource_msgs: Queue[TaggedMonitoringMessage]
133
139
  self.resource_msgs = SizedQueue()
134
140
 
135
141
  self.router_exit_event: ms.Event
136
- self.router_exit_event = Event()
137
-
138
- self.zmq_router_proc = ForkProcess(target=zmq_router_starter,
139
- kwargs={"comm_q": zmq_comm_q,
140
- "resource_msgs": self.resource_msgs,
141
- "exit_event": self.router_exit_event,
142
- "hub_address": self.hub_address,
143
- "zmq_port_range": self.hub_port_range,
144
- "run_dir": dfk_run_dir,
145
- "logging_level": logging.DEBUG if self.monitoring_debug else logging.INFO,
146
- },
147
- name="Monitoring-ZMQ-Router-Process",
148
- daemon=True,
149
- )
150
- self.zmq_router_proc.start()
151
-
152
- self.udp_router_proc = ForkProcess(target=udp_router_starter,
153
- kwargs={"comm_q": udp_comm_q,
154
- "resource_msgs": self.resource_msgs,
155
- "exit_event": self.router_exit_event,
156
- "hub_address": self.hub_address,
157
- "udp_port": self.hub_port,
158
- "run_dir": dfk_run_dir,
159
- "logging_level": logging.DEBUG if self.monitoring_debug else logging.INFO,
160
- },
161
- name="Monitoring-UDP-Router-Process",
162
- daemon=True,
163
- )
142
+ self.router_exit_event = SpawnEvent()
143
+
144
+ self.udp_router_proc = SpawnProcess(target=udp_router_starter,
145
+ kwargs={"comm_q": udp_comm_q,
146
+ "resource_msgs": self.resource_msgs,
147
+ "exit_event": self.router_exit_event,
148
+ "hub_address": self.hub_address,
149
+ "udp_port": self.hub_port,
150
+ "run_dir": dfk_run_dir,
151
+ "logging_level": logging.DEBUG if self.monitoring_debug else logging.INFO,
152
+ },
153
+ name="Monitoring-UDP-Router-Process",
154
+ daemon=True,
155
+ )
164
156
  self.udp_router_proc.start()
165
157
 
166
158
  self.dbm_exit_event: ms.Event
167
- self.dbm_exit_event = Event()
168
-
169
- self.dbm_proc = ForkProcess(target=dbm_starter,
170
- args=(self.resource_msgs,),
171
- kwargs={"run_dir": dfk_run_dir,
172
- "logging_level": logging.DEBUG if self.monitoring_debug else logging.INFO,
173
- "db_url": self.logging_endpoint,
174
- "exit_event": self.dbm_exit_event,
175
- },
176
- name="Monitoring-DBM-Process",
177
- daemon=True,
178
- )
159
+ self.dbm_exit_event = SpawnEvent()
160
+
161
+ self.dbm_proc = SpawnProcess(target=dbm_starter,
162
+ args=(self.resource_msgs,),
163
+ kwargs={"run_dir": dfk_run_dir,
164
+ "logging_level": logging.DEBUG if self.monitoring_debug else logging.INFO,
165
+ "db_url": self.logging_endpoint,
166
+ "exit_event": self.dbm_exit_event,
167
+ },
168
+ name="Monitoring-DBM-Process",
169
+ daemon=True,
170
+ )
179
171
  self.dbm_proc.start()
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)
182
-
183
- self.filesystem_proc = ForkProcess(target=filesystem_router_starter,
184
- args=(self.resource_msgs, dfk_run_dir, self.router_exit_event),
185
- name="Monitoring-Filesystem-Process",
186
- daemon=True
187
- )
172
+ logger.info("Started UDP router process %s and DBM process %s",
173
+ self.udp_router_proc.pid, self.dbm_proc.pid)
174
+
175
+ self.filesystem_proc = SpawnProcess(target=filesystem_router_starter,
176
+ args=(self.resource_msgs, dfk_run_dir, self.router_exit_event),
177
+ name="Monitoring-Filesystem-Process",
178
+ daemon=True
179
+ )
188
180
  self.filesystem_proc.start()
189
181
  logger.info("Started filesystem radio receiver process %s", self.filesystem_proc.pid)
190
182
 
191
183
  self.radio = MultiprocessingQueueRadioSender(self.resource_msgs)
192
184
 
193
- try:
194
- zmq_comm_q_result = zmq_comm_q.get(block=True, timeout=120)
195
- zmq_comm_q.close()
196
- zmq_comm_q.join_thread()
197
- except queue.Empty:
198
- logger.error("Monitoring ZMQ Router has not reported port in 120s. Aborting")
199
- raise MonitoringHubStartError()
200
-
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
185
  try:
208
186
  udp_comm_q_result = udp_comm_q.get(block=True, timeout=120)
209
187
  udp_comm_q.close()
@@ -232,9 +210,6 @@ class MonitoringHub(RepresentationMixin):
232
210
  logger.info("Setting router termination event")
233
211
  self.router_exit_event.set()
234
212
 
235
- logger.info("Waiting for ZMQ router to terminate")
236
- join_terminate_close_proc(self.zmq_router_proc)
237
-
238
213
  logger.info("Waiting for UDP router to terminate")
239
214
  join_terminate_close_proc(self.udp_router_proc)
240
215
 
@@ -251,38 +226,3 @@ class MonitoringHub(RepresentationMixin):
251
226
  self.resource_msgs.close()
252
227
  self.resource_msgs.join_thread()
253
228
  logger.info("Closed monitoring multiprocessing queues")
254
-
255
-
256
- def join_terminate_close_proc(process: ForkProcessType, *, timeout: int = 30) -> None:
257
- """Increasingly aggressively terminate a process.
258
-
259
- This function assumes that the process is likely to exit before
260
- the join timeout, driven by some other means, such as the
261
- MonitoringHub router_exit_event. If the process does not exit, then
262
- first terminate() and then kill() will be used to end the process.
263
-
264
- In the case of a very mis-behaving process, this function might take
265
- up to 3*timeout to exhaust all termination methods and return.
266
- """
267
- logger.debug("Joining process")
268
- process.join(timeout)
269
-
270
- # run a sequence of increasingly aggressive steps to shut down the process.
271
- if process.is_alive():
272
- logger.error("Process did not join. Terminating.")
273
- process.terminate()
274
- process.join(timeout)
275
- if process.is_alive():
276
- logger.error("Process did not join after terminate. Killing.")
277
- process.kill()
278
- process.join(timeout)
279
- # This kill should not be caught by any signal handlers so it is
280
- # unlikely that this join will timeout. If it does, there isn't
281
- # anything further to do except log an error in the next if-block.
282
-
283
- if process.is_alive():
284
- logger.error("Process failed to end")
285
- # don't call close if the process hasn't ended:
286
- # process.close() doesn't work on a running process.
287
- else:
288
- process.close()
@@ -3,16 +3,27 @@ from __future__ import annotations
3
3
  import logging
4
4
  import multiprocessing.queues as mpq
5
5
  import os
6
+ import queue
6
7
  import time
7
- from multiprocessing.synchronize import Event
8
+ from multiprocessing.context import SpawnProcess as SpawnProcessType
9
+ from multiprocessing.queues import Queue as QueueType
10
+ from multiprocessing.synchronize import Event as EventType
8
11
  from typing import Tuple
9
12
 
10
13
  import typeguard
11
14
  import zmq
12
15
 
16
+ from parsl.addresses import tcp_url
13
17
  from parsl.log_utils import set_file_logger
18
+ from parsl.monitoring.errors import MonitoringRouterStartError
14
19
  from parsl.monitoring.radios.multiprocessing import MultiprocessingQueueRadioSender
15
20
  from parsl.monitoring.types import TaggedMonitoringMessage
21
+ from parsl.multiprocessing import (
22
+ SizedQueue,
23
+ SpawnEvent,
24
+ SpawnProcess,
25
+ join_terminate_close_proc,
26
+ )
16
27
  from parsl.process_loggers import wrap_with_logs
17
28
  from parsl.utils import setproctitle
18
29
 
@@ -23,21 +34,21 @@ class MonitoringRouter:
23
34
 
24
35
  def __init__(self,
25
36
  *,
26
- hub_address: str,
27
- zmq_port_range: Tuple[int, int] = (55050, 56000),
37
+ address: str,
38
+ port_range: Tuple[int, int] = (55050, 56000),
28
39
 
29
40
  run_dir: str = ".",
30
41
  logging_level: int = logging.INFO,
31
42
  resource_msgs: mpq.Queue,
32
- exit_event: Event,
43
+ exit_event: EventType,
33
44
  ):
34
45
  """ Initializes a monitoring configuration class.
35
46
 
36
47
  Parameters
37
48
  ----------
38
- hub_address : str
49
+ address : str
39
50
  The ip address at which the workers will be able to reach the Hub.
40
- zmq_port_range : tuple(int, int)
51
+ port_range : tuple(int, int)
41
52
  The MonitoringHub picks ports at random from the range which will be used by Hub.
42
53
  Default: (55050, 56000)
43
54
  run_dir : str
@@ -51,11 +62,11 @@ class MonitoringRouter:
51
62
  """
52
63
  os.makedirs(run_dir, exist_ok=True)
53
64
  self.logger = set_file_logger(f"{run_dir}/monitoring_zmq_router.log",
54
- name="monitoring_router",
65
+ name="zmq_monitoring_router",
55
66
  level=logging_level)
56
67
  self.logger.debug("Monitoring router starting")
57
68
 
58
- self.hub_address = hub_address
69
+ self.address = address
59
70
 
60
71
  self.loop_freq = 10.0 # milliseconds
61
72
 
@@ -64,15 +75,15 @@ class MonitoringRouter:
64
75
  self.zmq_receiver_channel.setsockopt(zmq.LINGER, 0)
65
76
  self.zmq_receiver_channel.set_hwm(0)
66
77
  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])
78
+ self.logger.debug("address: {}. port_range {}".format(address, port_range))
79
+ self.zmq_receiver_port = self.zmq_receiver_channel.bind_to_random_port(tcp_url(address),
80
+ min_port=port_range[0],
81
+ max_port=port_range[1])
71
82
 
72
83
  self.target_radio = MultiprocessingQueueRadioSender(resource_msgs)
73
84
  self.exit_event = exit_event
74
85
 
75
- @wrap_with_logs(target="monitoring_router")
86
+ @wrap_with_logs(target="zmq_monitoring_router")
76
87
  def start(self) -> None:
77
88
  self.logger.info("Starting ZMQ listener")
78
89
  try:
@@ -108,17 +119,17 @@ class MonitoringRouter:
108
119
  def zmq_router_starter(*,
109
120
  comm_q: mpq.Queue,
110
121
  resource_msgs: mpq.Queue,
111
- exit_event: Event,
122
+ exit_event: EventType,
112
123
 
113
- hub_address: str,
114
- zmq_port_range: Tuple[int, int],
124
+ address: str,
125
+ port_range: Tuple[int, int],
115
126
 
116
127
  run_dir: str,
117
128
  logging_level: int) -> None:
118
129
  setproctitle("parsl: monitoring zmq router")
119
130
  try:
120
- router = MonitoringRouter(hub_address=hub_address,
121
- zmq_port_range=zmq_port_range,
131
+ router = MonitoringRouter(address=address,
132
+ port_range=port_range,
122
133
  run_dir=run_dir,
123
134
  logging_level=logging_level,
124
135
  resource_msgs=resource_msgs,
@@ -129,3 +140,54 @@ def zmq_router_starter(*,
129
140
  else:
130
141
  comm_q.put(router.zmq_receiver_port)
131
142
  router.start()
143
+
144
+
145
+ class ZMQRadioReceiver():
146
+ def __init__(self, *, process: SpawnProcessType, exit_event: EventType, port: int) -> None:
147
+ self.process = process
148
+ self.exit_event = exit_event
149
+ self.port = port
150
+
151
+ def close(self) -> None:
152
+ self.exit_event.set()
153
+ join_terminate_close_proc(self.process)
154
+
155
+
156
+ def start_zmq_receiver(*,
157
+ monitoring_messages: QueueType,
158
+ loopback_address: str,
159
+ port_range: Tuple[int, int],
160
+ logdir: str,
161
+ worker_debug: bool) -> ZMQRadioReceiver:
162
+ comm_q = SizedQueue(maxsize=10)
163
+
164
+ router_exit_event = SpawnEvent()
165
+
166
+ router_proc = SpawnProcess(target=zmq_router_starter,
167
+ kwargs={"comm_q": comm_q,
168
+ "resource_msgs": monitoring_messages,
169
+ "exit_event": router_exit_event,
170
+ "address": loopback_address,
171
+ "port_range": port_range,
172
+ "run_dir": logdir,
173
+ "logging_level": logging.DEBUG if worker_debug else logging.INFO,
174
+ },
175
+ name="Monitoring-ZMQ-Router-Process",
176
+ daemon=True,
177
+ )
178
+ router_proc.start()
179
+
180
+ try:
181
+ logger.debug("Waiting for router process to report port")
182
+ comm_q_result = comm_q.get(block=True, timeout=120)
183
+ comm_q.close()
184
+ comm_q.join_thread()
185
+ except queue.Empty:
186
+ logger.error("Monitoring ZMQ Router has not reported port in 120s")
187
+ raise MonitoringRouterStartError()
188
+
189
+ if isinstance(comm_q_result, str):
190
+ logger.error("MonitoringRouter sent an error message: %s", comm_q_result)
191
+ raise RuntimeError(f"MonitoringRouter failed to start: {comm_q_result}")
192
+
193
+ return ZMQRadioReceiver(process=router_proc, exit_event=router_exit_event, port=comm_q_result)
parsl/multiprocessing.py CHANGED
@@ -6,6 +6,7 @@ import multiprocessing
6
6
  import multiprocessing.queues
7
7
  import platform
8
8
  from multiprocessing.context import ForkProcess as ForkProcessType
9
+ from multiprocessing.context import SpawnProcess as SpawnProcessType
9
10
  from typing import Callable
10
11
 
11
12
  logger = logging.getLogger(__name__)
@@ -14,6 +15,10 @@ ForkContext = multiprocessing.get_context("fork")
14
15
  SpawnContext = multiprocessing.get_context("spawn")
15
16
 
16
17
  ForkProcess: Callable[..., ForkProcessType] = ForkContext.Process
18
+ SpawnProcess: Callable[..., SpawnProcessType] = SpawnContext.Process
19
+
20
+ SpawnEvent = SpawnContext.Event
21
+ SpawnQueue = SpawnContext.Queue
17
22
 
18
23
 
19
24
  class MacSafeQueue(multiprocessing.queues.Queue):
@@ -26,7 +31,7 @@ class MacSafeQueue(multiprocessing.queues.Queue):
26
31
 
27
32
  def __init__(self, *args, **kwargs):
28
33
  if 'ctx' not in kwargs:
29
- kwargs['ctx'] = multiprocessing.get_context()
34
+ kwargs['ctx'] = multiprocessing.get_context('spawn')
30
35
  super().__init__(*args, **kwargs)
31
36
  self._counter = multiprocessing.Value('i', 0)
32
37
 
@@ -59,6 +64,41 @@ SizedQueue: Callable[..., multiprocessing.Queue]
59
64
 
60
65
  if platform.system() != 'Darwin':
61
66
  import multiprocessing
62
- SizedQueue = multiprocessing.Queue
67
+ SizedQueue = SpawnQueue
63
68
  else:
64
69
  SizedQueue = MacSafeQueue
70
+
71
+
72
+ def join_terminate_close_proc(process: SpawnProcessType, *, timeout: int = 30) -> None:
73
+ """Increasingly aggressively terminate a process.
74
+
75
+ This function assumes that the process is likely to exit before
76
+ the join timeout, driven by some other means, such as the
77
+ MonitoringHub router_exit_event. If the process does not exit, then
78
+ first terminate() and then kill() will be used to end the process.
79
+
80
+ In the case of a very mis-behaving process, this function might take
81
+ up to 3*timeout to exhaust all termination methods and return.
82
+ """
83
+ logger.debug("Joining process")
84
+ process.join(timeout)
85
+
86
+ # run a sequence of increasingly aggressive steps to shut down the process.
87
+ if process.is_alive():
88
+ logger.error("Process did not join. Terminating.")
89
+ process.terminate()
90
+ process.join(timeout)
91
+ if process.is_alive():
92
+ logger.error("Process did not join after terminate. Killing.")
93
+ process.kill()
94
+ process.join(timeout)
95
+ # This kill should not be caught by any signal handlers so it is
96
+ # unlikely that this join will timeout. If it does, there isn't
97
+ # anything further to do except log an error in the next if-block.
98
+
99
+ if process.is_alive():
100
+ logger.error("Process failed to end")
101
+ # don't call close if the process hasn't ended:
102
+ # process.close() doesn't work on a running process.
103
+ else:
104
+ process.close()
@@ -4,8 +4,7 @@ import signal
4
4
  import psutil
5
5
  import pytest
6
6
 
7
- from parsl.monitoring.monitoring import join_terminate_close_proc
8
- from parsl.multiprocessing import ForkProcess
7
+ from parsl.multiprocessing import SpawnEvent, SpawnProcess, join_terminate_close_proc
9
8
 
10
9
 
11
10
  def noop():
@@ -14,7 +13,7 @@ def noop():
14
13
 
15
14
  @pytest.mark.local
16
15
  def test_end_process_already_exited():
17
- p = ForkProcess(target=noop)
16
+ p = SpawnProcess(target=noop)
18
17
  p.start()
19
18
  p.join()
20
19
  join_terminate_close_proc(p)
@@ -28,7 +27,7 @@ def hang():
28
27
  @pytest.mark.local
29
28
  def test_end_hung_process():
30
29
  """Test calling against a process that will not exit itself."""
31
- p = ForkProcess(target=hang)
30
+ p = SpawnProcess(target=hang)
32
31
  p.start()
33
32
  pid = p.pid
34
33
  join_terminate_close_proc(p, timeout=1)
@@ -46,10 +45,10 @@ def hang_no_sigint(e):
46
45
  @pytest.mark.local
47
46
  def test_end_hung_process_no_sigint():
48
47
  """Test calling against a process that will not exit itself."""
49
- e = multiprocessing.Event()
50
- p = ForkProcess(target=hang_no_sigint, args=(e,))
48
+ e = SpawnEvent()
49
+ p = SpawnProcess(target=hang_no_sigint, args=(e,))
51
50
  p.start()
52
51
  pid = p.pid
53
- join_terminate_close_proc(p, timeout=1)
52
+ join_terminate_close_proc(p, timeout=2)
54
53
  assert not psutil.pid_exists(pid), "process should not exist any more"
55
54
  assert e.is_set(), "hung process should have set event on signal"
@@ -45,7 +45,7 @@ def test_row_counts():
45
45
 
46
46
  # dig out the interchange port...
47
47
  hub_address = parsl.dfk().monitoring.hub_address
48
- hub_zmq_port = parsl.dfk().monitoring.hub_zmq_port
48
+ hub_zmq_port = parsl.dfk().executors["htex_Local"].hub_zmq_port
49
49
 
50
50
  # this will send a string to a new socket connection
51
51
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
@@ -0,0 +1,27 @@
1
+ import pytest
2
+
3
+ from parsl.monitoring.radios.zmq import ZMQRadioSender
4
+ from parsl.monitoring.radios.zmq_router import start_zmq_receiver
5
+ from parsl.multiprocessing import SpawnQueue
6
+
7
+
8
+ @pytest.mark.local
9
+ def test_send_recv_message(tmpd_cwd, try_assert):
10
+ q = SpawnQueue()
11
+ loopback = "127.0.0.1"
12
+ r = start_zmq_receiver(monitoring_messages=q,
13
+ loopback_address=loopback,
14
+ port_range=(49152, 65535),
15
+ logdir=str(tmpd_cwd),
16
+ worker_debug=False)
17
+
18
+ s = ZMQRadioSender(loopback, r.port)
19
+
20
+ test_msg = ("test", {})
21
+ s.send(test_msg)
22
+
23
+ assert q.get() == test_msg
24
+
25
+ assert r.process.is_alive()
26
+ r.exit_event.set()
27
+ try_assert(lambda: not r.process.is_alive())
@@ -102,6 +102,9 @@ def test_stdstream_to_monitoring(stdx, expected_stdx, stream, tmpd_cwd, caplog):
102
102
  kwargs = {stream: stdx}
103
103
  stdapp(**kwargs).result()
104
104
 
105
+ for record in caplog.records:
106
+ assert record.levelno < logging.ERROR
107
+
105
108
  engine = sqlalchemy.create_engine(c.monitoring.logging_endpoint)
106
109
  with engine.begin() as connection:
107
110
 
@@ -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", ["zmq_router_proc", "udp_router_proc", "dbm_proc", "filesystem_proc"])
33
+ @pytest.mark.parametrize("process_attr", ["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
@@ -8,7 +8,7 @@ import uuid
8
8
 
9
9
  from parsl.dataflow.states import States
10
10
  from parsl.errors import ConfigurationError
11
- from parsl.multiprocessing import ForkProcess
11
+ from parsl.multiprocessing import SpawnProcess
12
12
  from parsl.usage_tracking.api import get_parsl_usage
13
13
  from parsl.usage_tracking.levels import DISABLED as USAGE_TRACKING_DISABLED
14
14
  from parsl.usage_tracking.levels import LEVEL_3 as USAGE_TRACKING_LEVEL_3
@@ -35,7 +35,7 @@ def async_process(fn: Callable[P, None]) -> Callable[P, None]:
35
35
  """ Decorator function to launch a function as a separate process """
36
36
 
37
37
  def run(*args, **kwargs):
38
- proc = ForkProcess(target=fn, args=args, kwargs=kwargs, name="Usage-Tracking")
38
+ proc = SpawnProcess(target=fn, args=args, kwargs=kwargs, name="Usage-Tracking")
39
39
  proc.start()
40
40
  return proc
41
41
 
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 = '2025.03.17'
6
+ VERSION = '2025.03.24'
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: parsl
3
- Version: 2025.3.17
3
+ Version: 2025.3.24
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.03.17.tar.gz
6
+ Download-URL: https://github.com/Parsl/parsl/archive/2025.03.24.tar.gz
7
7
  Author: The Parsl Team
8
8
  Author-email: parsl@googlegroups.com
9
9
  License: Apache 2.0
@@ -4,11 +4,11 @@ parsl/config.py,sha256=p5HQoxLj5aMagUAYfngcXG2kw0s6SJoc6u7vH2sVhPU,9635
4
4
  parsl/curvezmq.py,sha256=6Zi7RqTP_eKWi3DFgapfK2t-Jw8vJS-ZtN1bsrByPeo,7073
5
5
  parsl/errors.py,sha256=SzINzQFZDBDbj9l-DPQznD0TbGkNhHIRAPkcBCogf_A,1019
6
6
  parsl/log_utils.py,sha256=u14Fkl5eDfS4HMpl0JjseNNPdbvPaugWPRQj1_af_Zo,3273
7
- parsl/multiprocessing.py,sha256=MyaEcEq-Qf860u7V98u-PZrPNdtzOZL_NW6EhIJnmfQ,1937
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=_yYxGBkoJMDKADe5yJ2dAkIINmdZgyRTRJnodIasABw,131
11
+ parsl/version.py,sha256=RGffnfvs_gOc_lEWgnZpd1jy_cuz-F39xp5lsIo6OyU,131
12
12
  parsl/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  parsl/app/app.py,sha256=0gbM4AH2OtFOLsv07I5nglpElcwMSOi-FzdZZfrk7So,8532
14
14
  parsl/app/bash.py,sha256=jm2AvePlCT9DZR7H_4ANDWxatp5dN_22FUlT_gWhZ-g,5528
@@ -53,7 +53,7 @@ parsl/data_provider/staging.py,sha256=ZDZuuFg38pjUStegKPcvPsfGp3iMeReMzfU6DSwtJj
53
53
  parsl/data_provider/zip.py,sha256=S4kVuH9lxAegRURYbvIUR7EYYBOccyslaqyCrVWUBhw,4497
54
54
  parsl/dataflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
55
  parsl/dataflow/dependency_resolvers.py,sha256=Om8Dgh7a0ZwgXAc6TlhxLSzvxXHDlNNV1aBNiD3JTNY,3325
56
- parsl/dataflow/dflow.py,sha256=jNxrAd2xmxesS3fR6eZyDN9f6I0BIBQbhL63zv51lkk,61752
56
+ parsl/dataflow/dflow.py,sha256=kAEziQPm9rpGKzqhOuHSnnIXQ3KgrRaeRbYReww7Amw,61620
57
57
  parsl/dataflow/errors.py,sha256=daVfr2BWs1zRsGD6JtosEMttWHvK1df1Npiu_MUvFKg,3998
58
58
  parsl/dataflow/futures.py,sha256=08LuP-HFiHBIZmeKCjlsazw_WpQ5fwevrU2_WbidkYw,6080
59
59
  parsl/dataflow/memoization.py,sha256=QUkTduZ_gdr8i08VWNWrqhfEvoMGsPDZegWUE2_7sGQ,12579
@@ -61,19 +61,19 @@ parsl/dataflow/rundirs.py,sha256=JZdzybVGubY35jL2YiKcDo65ZmRl1WyOApc8ajYxztc,108
61
61
  parsl/dataflow/states.py,sha256=hV6mfv-y4A6xrujeQglcomnfEs7y3Xm2g6JFwC6dvgQ,2612
62
62
  parsl/dataflow/taskrecord.py,sha256=qIW7T6hn9dYTuNPdUura3HQwwUpUJACwPP5REm5COf4,3042
63
63
  parsl/executors/__init__.py,sha256=PEuXYrnVqwlaz_nt82s9D_YNaVsX7ET29DeIZRUR8hw,577
64
- parsl/executors/base.py,sha256=jYEa5nS1_Vn8k3A92TCSYG0n2zeHnN6KiuVWsILl4sE,5205
64
+ parsl/executors/base.py,sha256=_X-huuXKCoQatT_TYx9ApEuXiVVvUYI0S7uKlVMHP-U,4488
65
65
  parsl/executors/errors.py,sha256=ZxL3nK5samPos8Xixo_jpRtPIiRJfZ5D397_qaXj2g0,2515
66
66
  parsl/executors/execute_task.py,sha256=PtqHxk778UQaNah1AN-TJV5emZbOcU5TGtWDxFn3_F4,1079
67
- parsl/executors/globus_compute.py,sha256=giyCyq5KWK_o4nhQJBX2Xm9FatdpdKrgloZuHhN0Zwg,4840
68
- parsl/executors/status_handling.py,sha256=nxbkiGr6f3xDc0nsUeSrMMxlj7UD32K7nOLCLzfthDs,15416
67
+ parsl/executors/globus_compute.py,sha256=818XKRobNRCs5-h30x2NP2XSLkoWlWoNeBZtv9hF-ec,4851
68
+ parsl/executors/status_handling.py,sha256=oiy6SQUQWwVciZd9MxF0sna9miqkMfaKv6ZTrj3h-Pc,15772
69
69
  parsl/executors/threads.py,sha256=_LA5NA3GSvtjDend-1HVpjoDoNHHW13rAD0CET99fjQ,3463
70
70
  parsl/executors/flux/__init__.py,sha256=P9grTTeRPXfqXurFhlSS7XhmE6tTbnCnyQ1f9b-oYHE,136
71
71
  parsl/executors/flux/execute_parsl_task.py,sha256=zHP5M7ILGiwnoalZ8WsfVVdZM7uP4iQo2ThVh4crxpM,1530
72
- parsl/executors/flux/executor.py,sha256=8_xakLUu5zNJAHL0LbeTCFEWqWzRK1eE-3ep4GIIIrY,17017
72
+ parsl/executors/flux/executor.py,sha256=ii1i5V7uQnhf1BDq5nnMscmmpXJkCWtrFCuBbDaPyWI,17041
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=esMYMgPHmgD0wPTb0U61vBX96DaPrWj9sQLzpKvB06k,38752
76
+ parsl/executors/high_throughput/executor.py,sha256=VVKe3gveCqa3rERAqhVNsWifSrLqyMcUwpdyHCsYdz8,39663
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
@@ -85,13 +85,13 @@ parsl/executors/high_throughput/probe.py,sha256=QOEaliO3x5cB6ltMOZMsZQ-ath9AAuFq
85
85
  parsl/executors/high_throughput/process_worker_pool.py,sha256=Q7FN0MdXIAOouxDarim6etYVHEgbXFiaMhBahC2ZtIQ,41137
86
86
  parsl/executors/high_throughput/zmq_pipes.py,sha256=NUK25IEh0UkxzdqQQyM8tMtuZmjSiTeWu1DzkkAIOhA,8980
87
87
  parsl/executors/radical/__init__.py,sha256=CKbtV2numw5QvgIBq1htMUrt9TqDCIC2zifyf2svTNU,186
88
- parsl/executors/radical/executor.py,sha256=en2TKzZnJYU_juojkM_aZUdWhbAgutAYn_EL6HGpfSY,22835
88
+ parsl/executors/radical/executor.py,sha256=cRxEAPLvdYozCHT_zSj79KAOltfZySFYUbMU4rTbGng,22859
89
89
  parsl/executors/radical/rpex_resources.py,sha256=Q7-0u3K447LBCe2y7mVcdw6jqWI7SdPXxCKhkr6FoRQ,5139
90
90
  parsl/executors/radical/rpex_worker.py,sha256=z6r82ZujKb6sdKIdHsQ_5EBMDIQieeGcrlt6kGLFo4M,1830
91
91
  parsl/executors/taskvine/__init__.py,sha256=9rwp3M8B0YyEhZMLO0RHaNw7u1nc01WHbXLqnBTanu0,293
92
92
  parsl/executors/taskvine/errors.py,sha256=euIYkSslrNSI85kyi2s0xzOaO9ik4c1fYHstMIeiBJk,652
93
93
  parsl/executors/taskvine/exec_parsl_function.py,sha256=ftGdJU78lKPPkphSHlEi4rj164mhuMHJjghVqfgeXKk,7085
94
- parsl/executors/taskvine/executor.py,sha256=4c0mt83G-F4ZFMxhdJByvYjG05QdLrLYYHsmpPXY6YE,30906
94
+ parsl/executors/taskvine/executor.py,sha256=PpsMPYx99mgKq_xOqnRdXo20NZOr7JFtOIKETIEK-z0,30930
95
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
@@ -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=QYJ02jt0AG-83XN0-mUa9LSUezroYSbh1OkOGzGLIqo,49693
103
+ parsl/executors/workqueue/executor.py,sha256=aRvEt_BGO2AGNaoF8P109z81XhlFqjyjJnka3yIzy-4,49717
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
@@ -115,9 +115,9 @@ parsl/launchers/errors.py,sha256=8YMV_CHpBNVa4eXkGE4x5DaFQlZkDCRCHmBktYcY6TA,467
115
115
  parsl/launchers/launchers.py,sha256=cQsNsHuCOL_nQTjPXf0--YsgsDoMoJ77bO1Wt4ncLjs,15134
116
116
  parsl/monitoring/__init__.py,sha256=0ywNz6i0lM1xo_7_BIxhETDGeVd2C_0wwD7qgeaMR4c,83
117
117
  parsl/monitoring/db_manager.py,sha256=L0c5S9ockq0UIchT2bjmkSAWXS-t0G-Q_neOIBfLbm0,33444
118
- parsl/monitoring/errors.py,sha256=D6jpYzEzp0d6FmVKGqhvjAxr4ztZfJX2s-aXemH9bBU,148
118
+ parsl/monitoring/errors.py,sha256=GParOWoCTp2w1Hmif0PaF5J6p5dWVOwyhO18bcvr_uo,277
119
119
  parsl/monitoring/message_type.py,sha256=Khn88afNxcOIciKiCK4GLnn90I5BlRTiOL3zK-P07yQ,401
120
- parsl/monitoring/monitoring.py,sha256=PspFFtf3Iaj5tl23ITRRdHrBDAocSOSvP2IVP_pmW-Y,13134
120
+ parsl/monitoring/monitoring.py,sha256=ZA-36DYtPRApJDc6cd8R8xoRGfCG4OMa9mzW4OPGCCs,10065
121
121
  parsl/monitoring/remote.py,sha256=t0qCTUMCzeJ_JOARFpjqlTNrAWdEb20BxhmZh9X7kEM,13728
122
122
  parsl/monitoring/types.py,sha256=oOCrzv-ab-_rv4pb8o58Sdb8G_RGp1aZriRbdf9zBEk,339
123
123
  parsl/monitoring/queries/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -131,7 +131,7 @@ parsl/monitoring/radios/multiprocessing.py,sha256=fsfaaoMDp6VJv1DSAl-P0R2ofO6jp1
131
131
  parsl/monitoring/radios/udp.py,sha256=bTpt7JYp-5hyBBLzgiLj1_BlSTn28UVp39OYgVGLXCw,1613
132
132
  parsl/monitoring/radios/udp_router.py,sha256=LEiHZVhw3lVFhqUK1FAFFtpvNOWbB6RNRBK8FaMvtDw,5771
133
133
  parsl/monitoring/radios/zmq.py,sha256=fhoHp9ylhf-D3eTJb2aSHRsuic8-FJ_oRNGnniGkCAI,592
134
- parsl/monitoring/radios/zmq_router.py,sha256=pYhol8-SV8FThv7YIjqc5tv149E4ktDLb-l7-ot4nfg,5579
134
+ parsl/monitoring/radios/zmq_router.py,sha256=ksaWb9bceyFhGGMDC8Nc16JhQ3qpmg8uXQypbrvvtcg,7984
135
135
  parsl/monitoring/visualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
136
136
  parsl/monitoring/visualization/app.py,sha256=xMeRlAnzl5lHddAOdSBcqY3D5lmOYw3Z3Z2_YyoVwnw,1425
137
137
  parsl/monitoring/visualization/models.py,sha256=C7CcF6w6PhtrdvDX9VgDH-aSrpLfvYU1fJ4-HDUeFVQ,5138
@@ -339,12 +339,13 @@ parsl/tests/test_monitoring/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
339
339
  parsl/tests/test_monitoring/test_app_names.py,sha256=A-mOMCVhZDnUyJp32fsTUkHdcyval8o7WPEWacDkbD4,2208
340
340
  parsl/tests/test_monitoring/test_basic.py,sha256=VdF6JHfqsEOIMg-ysIAREgygZIjHWNDVLNVQ7jhWxmQ,4592
341
341
  parsl/tests/test_monitoring/test_db_locks.py,sha256=3s3c1xhKo230ZZIJ3f1Ca4U7LcEdXnanOGVXQyNlk2U,2895
342
- parsl/tests/test_monitoring/test_exit_helper.py,sha256=FsMcQ1GF70vPXEfexDyo674_c5cglJBrLXKBzAYIfOk,1266
343
- parsl/tests/test_monitoring/test_fuzz_zmq.py,sha256=--3-pQUvXXbkr8v_BEJoPvVvNly1oXvrD2nJh6yl_0M,3436
342
+ parsl/tests/test_monitoring/test_exit_helper.py,sha256=ob8Qd1hlkq_mowygfPetTnYN9LfuqeXHRpPilSfDSog,1232
343
+ parsl/tests/test_monitoring/test_fuzz_zmq.py,sha256=SQNNHhXxHB_LwW4Ujqkgut3lbG0XVW-hliPagQQpiTc,3449
344
344
  parsl/tests/test_monitoring/test_htex_init_blocks_vs_monitoring.py,sha256=_QV8zjBKVF_qBbBnhT0C3X9AmfS7IKLcOnEw_cU6HeM,2622
345
345
  parsl/tests/test_monitoring/test_incomplete_futures.py,sha256=ZnO1sFSwlWUBHX64C_zwfTVRVC_UFNlU4h0POgx6NEo,2005
346
346
  parsl/tests/test_monitoring/test_memoization_representation.py,sha256=dknv2nO7pNZ1jGxWGsC_AW3rs90gjMIeC5d7pIJ75Xc,2645
347
- parsl/tests/test_monitoring/test_stdouterr.py,sha256=AjzD4oumRIe9XMDPU4Cn_9-fwx34Vg8eAq58VfCO_zc,4492
347
+ parsl/tests/test_monitoring/test_radio_zmq.py,sha256=7ARjDbde9kSuP4NCx_z-UtmMf2X5opbkkKM7mZcayuA,783
348
+ parsl/tests/test_monitoring/test_stdouterr.py,sha256=SEMKBk4v5Saoq3QiraFpzUpVe5vS_132GQPSf1Qu0qM,4573
348
349
  parsl/tests/test_monitoring/test_viz_colouring.py,sha256=83Qdmn3gM0j7IL6kPDcuIsp_nl4zj-liPijyIN632SY,592
349
350
  parsl/tests/test_mpi_apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
350
351
  parsl/tests/test_mpi_apps/test_bad_mpi_config.py,sha256=QKvEUSrHIBrvqu2fRj1MAqxsYxDfcrdQ7dzWdOZejuU,1320
@@ -423,7 +424,7 @@ parsl/tests/test_serialization/test_pack_resource_spec.py,sha256=-Vtyh8KyezZw8e7
423
424
  parsl/tests/test_serialization/test_proxystore_configured.py,sha256=lGWOSEWul16enDWhW-s7CK0d3eMDzm1324Fmj0cZMVU,2293
424
425
  parsl/tests/test_serialization/test_proxystore_impl.py,sha256=uGd45sfPm9rJhzqKV0rI3lqdSOAUddQf-diEpcJAlcY,1228
425
426
  parsl/tests/test_shutdown/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
426
- parsl/tests/test_shutdown/test_kill_monitoring.py,sha256=BycTDLwxhHbbV68Qkgrmn8UUzSr55SvbNvydp35UCTM,1948
427
+ parsl/tests/test_shutdown/test_kill_monitoring.py,sha256=UNU_VeorxRq8mRGhjrDmqF_axZMCQjsPfAK0wh6ZN04,1929
427
428
  parsl/tests/test_staging/__init__.py,sha256=WZl9EHSkfYiSoE3Gbulcq2ifmn7IFGUkasJIobL5T5A,208
428
429
  parsl/tests/test_staging/staging_provider.py,sha256=6FDpImkWOLgysqM68NbCAoXZciZokI8dmBWRAxnggBk,3242
429
430
  parsl/tests/test_staging/test_1316.py,sha256=eS0e2BDM2vmPNF60aDr35wcuGgDPfXjTjRV6kyBZOQc,2652
@@ -457,14 +458,14 @@ parsl/tests/unit/test_usage_tracking.py,sha256=xEfUlbBRpsFdUdOrCsk1Kz5AfmMxJT7f0
457
458
  parsl/usage_tracking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
458
459
  parsl/usage_tracking/api.py,sha256=iaCY58Dc5J4UM7_dJzEEs871P1p1HdxBMtNGyVdzc9g,1821
459
460
  parsl/usage_tracking/levels.py,sha256=xbfzYEsd55KiZJ-mzNgPebvOH4rRHum04hROzEf41tU,291
460
- parsl/usage_tracking/usage.py,sha256=f9k6QcpbQxkGyP5WTC9PVyv0CA05s9NDpRe5wwRdBTM,9163
461
- parsl-2025.3.17.data/scripts/exec_parsl_function.py,sha256=YXKVVIa4zXmOtz-0Ca4E_5nQfN_3S2bh2tB75uZZB4w,7774
462
- parsl-2025.3.17.data/scripts/interchange.py,sha256=17MrOc7-FXxKBWTwkzIbUoa8fvvDfPelfjByd3ZD2Wk,29446
463
- parsl-2025.3.17.data/scripts/parsl_coprocess.py,sha256=zrVjEqQvFOHxsLufPi00xzMONagjVwLZbavPM7bbjK4,5722
464
- parsl-2025.3.17.data/scripts/process_worker_pool.py,sha256=__gFeFQJpV5moRofj3WKQCnKp6gmzieXjzkmzVuTmX4,41123
465
- parsl-2025.3.17.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
466
- parsl-2025.3.17.dist-info/METADATA,sha256=d_WFIKY6wmq4VQQcz-BCh0yhu9i3i627EjutSTqSNH4,4023
467
- parsl-2025.3.17.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
468
- parsl-2025.3.17.dist-info/entry_points.txt,sha256=XqnsWDYoEcLbsMcpnYGKLEnSBmaIe1YoM5YsBdJG2tI,176
469
- parsl-2025.3.17.dist-info/top_level.txt,sha256=PIheYoUFQtF2icLsgOykgU-Cjuwr2Oi6On2jo5RYgRM,6
470
- parsl-2025.3.17.dist-info/RECORD,,
461
+ parsl/usage_tracking/usage.py,sha256=8hq1UPdFlVcC0V3aj0ve-MvCyvwK8Xr3CVuSto3dTW4,9165
462
+ parsl-2025.3.24.data/scripts/exec_parsl_function.py,sha256=YXKVVIa4zXmOtz-0Ca4E_5nQfN_3S2bh2tB75uZZB4w,7774
463
+ parsl-2025.3.24.data/scripts/interchange.py,sha256=17MrOc7-FXxKBWTwkzIbUoa8fvvDfPelfjByd3ZD2Wk,29446
464
+ parsl-2025.3.24.data/scripts/parsl_coprocess.py,sha256=zrVjEqQvFOHxsLufPi00xzMONagjVwLZbavPM7bbjK4,5722
465
+ parsl-2025.3.24.data/scripts/process_worker_pool.py,sha256=__gFeFQJpV5moRofj3WKQCnKp6gmzieXjzkmzVuTmX4,41123
466
+ parsl-2025.3.24.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
467
+ parsl-2025.3.24.dist-info/METADATA,sha256=_Wl6Xlf9aCRKSbnQxEquJuYSA3_uNmlLKPgs-2B9nf8,4023
468
+ parsl-2025.3.24.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
469
+ parsl-2025.3.24.dist-info/entry_points.txt,sha256=XqnsWDYoEcLbsMcpnYGKLEnSBmaIe1YoM5YsBdJG2tI,176
470
+ parsl-2025.3.24.dist-info/top_level.txt,sha256=PIheYoUFQtF2icLsgOykgU-Cjuwr2Oi6On2jo5RYgRM,6
471
+ parsl-2025.3.24.dist-info/RECORD,,