parsl 2025.6.16__py3-none-any.whl → 2025.6.30__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 (53) hide show
  1. parsl/configs/osg.py +1 -1
  2. parsl/dataflow/dflow.py +14 -4
  3. parsl/executors/base.py +19 -9
  4. parsl/executors/flux/executor.py +2 -0
  5. parsl/executors/globus_compute.py +2 -0
  6. parsl/executors/high_throughput/executor.py +22 -15
  7. parsl/executors/high_throughput/interchange.py +173 -191
  8. parsl/executors/high_throughput/mpi_executor.py +14 -4
  9. parsl/executors/high_throughput/probe.py +4 -4
  10. parsl/executors/high_throughput/process_worker_pool.py +88 -94
  11. parsl/executors/radical/executor.py +3 -0
  12. parsl/executors/taskvine/executor.py +11 -3
  13. parsl/executors/taskvine/manager.py +3 -1
  14. parsl/executors/threads.py +19 -3
  15. parsl/executors/workqueue/executor.py +11 -3
  16. parsl/monitoring/errors.py +4 -4
  17. parsl/monitoring/monitoring.py +26 -88
  18. parsl/monitoring/radios/base.py +63 -2
  19. parsl/monitoring/radios/filesystem.py +19 -4
  20. parsl/monitoring/radios/filesystem_router.py +22 -3
  21. parsl/monitoring/radios/htex.py +22 -13
  22. parsl/monitoring/radios/multiprocessing.py +22 -2
  23. parsl/monitoring/radios/udp.py +57 -19
  24. parsl/monitoring/radios/udp_router.py +119 -25
  25. parsl/monitoring/radios/zmq_router.py +9 -10
  26. parsl/monitoring/remote.py +19 -40
  27. parsl/providers/local/local.py +12 -13
  28. parsl/tests/configs/htex_local_alternate.py +0 -1
  29. parsl/tests/conftest.py +7 -4
  30. parsl/tests/test_htex/test_interchange_exit_bad_registration.py +5 -7
  31. parsl/tests/test_htex/test_zmq_binding.py +5 -6
  32. parsl/tests/test_monitoring/test_basic.py +12 -10
  33. parsl/tests/test_monitoring/{test_fuzz_zmq.py → test_htex_fuzz_zmq.py} +7 -2
  34. parsl/tests/test_monitoring/test_htex_init_blocks_vs_monitoring.py +0 -1
  35. parsl/tests/test_monitoring/test_radio_filesystem.py +48 -0
  36. parsl/tests/test_monitoring/test_radio_multiprocessing.py +44 -0
  37. parsl/tests/test_monitoring/test_radio_udp.py +204 -0
  38. parsl/tests/test_monitoring/test_stdouterr.py +1 -3
  39. parsl/tests/test_scaling/test_worker_interchange_bad_messages_3262.py +3 -7
  40. parsl/tests/test_shutdown/test_kill_monitoring.py +1 -1
  41. parsl/version.py +1 -1
  42. {parsl-2025.6.16.data → parsl-2025.6.30.data}/scripts/interchange.py +173 -191
  43. {parsl-2025.6.16.data → parsl-2025.6.30.data}/scripts/process_worker_pool.py +88 -94
  44. {parsl-2025.6.16.dist-info → parsl-2025.6.30.dist-info}/METADATA +2 -2
  45. {parsl-2025.6.16.dist-info → parsl-2025.6.30.dist-info}/RECORD +51 -50
  46. parsl/tests/configs/local_threads_monitoring.py +0 -10
  47. parsl/tests/manual_tests/test_udp_simple.py +0 -51
  48. {parsl-2025.6.16.data → parsl-2025.6.30.data}/scripts/exec_parsl_function.py +0 -0
  49. {parsl-2025.6.16.data → parsl-2025.6.30.data}/scripts/parsl_coprocess.py +0 -0
  50. {parsl-2025.6.16.dist-info → parsl-2025.6.30.dist-info}/LICENSE +0 -0
  51. {parsl-2025.6.16.dist-info → parsl-2025.6.30.dist-info}/WHEEL +0 -0
  52. {parsl-2025.6.16.dist-info → parsl-2025.6.30.dist-info}/entry_points.txt +0 -0
  53. {parsl-2025.6.16.dist-info → parsl-2025.6.30.dist-info}/top_level.txt +0 -0
@@ -34,7 +34,6 @@ def fresh_config(run_dir, strategy, db_url):
34
34
  strategy=strategy,
35
35
  strategy_period=0.1,
36
36
  monitoring=MonitoringHub(
37
- hub_address="localhost",
38
37
  logging_endpoint=db_url
39
38
  )
40
39
  )
@@ -0,0 +1,48 @@
1
+ import pytest
2
+
3
+ from parsl.monitoring.message_type import MessageType
4
+ from parsl.monitoring.radios.filesystem import FilesystemRadio
5
+ from parsl.multiprocessing import SpawnQueue
6
+
7
+
8
+ @pytest.mark.local
9
+ def test_filesystem(tmpd_cwd):
10
+ """Test filesystem radio/receiver pair.
11
+ This test checks that the pair can be started up locally, that a message
12
+ is conveyed from radio to receiver, and that the receiver process goes
13
+ away at shutdown.
14
+ """
15
+
16
+ resource_msgs = SpawnQueue()
17
+
18
+ radio_config = FilesystemRadio()
19
+
20
+ # start receiver
21
+ receiver = radio_config.create_receiver(run_dir=str(tmpd_cwd),
22
+ resource_msgs=resource_msgs)
23
+
24
+ # make radio
25
+
26
+ radio_sender = radio_config.create_sender()
27
+
28
+ # send message into radio
29
+
30
+ message = (MessageType.RESOURCE_INFO, {})
31
+
32
+ radio_sender.send(message)
33
+
34
+ # verify it comes out of the receiver
35
+
36
+ m = resource_msgs.get()
37
+
38
+ assert m == message, "The sent message should appear in the queue"
39
+
40
+ # shut down router
41
+
42
+ receiver.shutdown()
43
+
44
+ # we can't inspect the process if it has been closed properly, but
45
+ # we can verify that it raises the expected ValueError the closed
46
+ # processes raise on access.
47
+ with pytest.raises(ValueError):
48
+ receiver.process.exitcode
@@ -0,0 +1,44 @@
1
+ import pytest
2
+
3
+ from parsl.monitoring.message_type import MessageType
4
+ from parsl.monitoring.radios.multiprocessing import MultiprocessingQueueRadio
5
+ from parsl.multiprocessing import SpawnQueue
6
+
7
+
8
+ @pytest.mark.local
9
+ def test_radio(tmpd_cwd):
10
+ """Test filesystem radio/receiver pair.
11
+ This test checks that the pair can be started up locally, that a message
12
+ is conveyed from radio to receiver, and that the receiver process goes
13
+ away at shutdown.
14
+ """
15
+
16
+ resource_msgs = SpawnQueue()
17
+
18
+ radio_config = MultiprocessingQueueRadio()
19
+
20
+ # start receiver
21
+ receiver = radio_config.create_receiver(run_dir=str(tmpd_cwd),
22
+ resource_msgs=resource_msgs)
23
+
24
+ # make radio
25
+
26
+ radio_sender = radio_config.create_sender()
27
+
28
+ # send message into radio
29
+
30
+ message = (MessageType.RESOURCE_INFO, {})
31
+
32
+ radio_sender.send(message)
33
+
34
+ # verify it comes out of the receiver
35
+
36
+ m = resource_msgs.get()
37
+
38
+ assert m == message, "The sent message should appear in the queue"
39
+
40
+ # Shut down router.
41
+ # In the multiprocessing radio, nothing happens at shutdown, so this
42
+ # validates that the call executes without raising an exception, but
43
+ # not much else.
44
+ receiver.shutdown()
@@ -0,0 +1,204 @@
1
+ import socket
2
+ import time
3
+
4
+ import pytest
5
+
6
+ from parsl.monitoring.message_type import MessageType
7
+ from parsl.monitoring.radios.udp import UDPRadio
8
+ from parsl.multiprocessing import SpawnQueue
9
+
10
+
11
+ @pytest.mark.local
12
+ def test_udp(tmpd_cwd):
13
+ """Test UDP radio/receiver pair.
14
+ This test checks that the pair can be started up locally, that a message
15
+ is conveyed from radio to receiver, and that the receiver process goes
16
+ away at shutdown.
17
+ """
18
+
19
+ resource_msgs = SpawnQueue()
20
+
21
+ radio_config = UDPRadio(address="localhost", atexit_timeout=0)
22
+
23
+ # start receiver
24
+ udp_receiver = radio_config.create_receiver(run_dir=str(tmpd_cwd),
25
+ resource_msgs=resource_msgs)
26
+
27
+ # check hash properties
28
+
29
+ assert len(radio_config.hmac_key) == 64, "With default hash, should expect 64 byte key"
30
+
31
+ # make radio
32
+
33
+ radio_sender = radio_config.create_sender()
34
+
35
+ # send message into radio
36
+
37
+ message = (MessageType.RESOURCE_INFO, {})
38
+
39
+ radio_sender.send(message)
40
+
41
+ # verify it comes out of the receiver
42
+
43
+ m = resource_msgs.get()
44
+
45
+ assert m == message, "The sent message should appear in the queue"
46
+
47
+ # shut down router
48
+
49
+ udp_receiver.shutdown()
50
+
51
+ # we can't inspect the process if it has been closed properly, but
52
+ # we can verify that it raises the expected ValueError the closed
53
+ # processes raise on access.
54
+ with pytest.raises(ValueError):
55
+ udp_receiver.process.exitcode
56
+
57
+
58
+ @pytest.mark.local
59
+ def test_bad_hmac(tmpd_cwd, caplog):
60
+ """Test when HMAC does not match.
61
+ """
62
+
63
+ resource_msgs = SpawnQueue()
64
+
65
+ radio_config = UDPRadio(address="localhost", atexit_timeout=0)
66
+
67
+ # start receiver
68
+ udp_receiver = radio_config.create_receiver(run_dir=str(tmpd_cwd),
69
+ resource_msgs=resource_msgs)
70
+
71
+ # check the hmac is configured in the right place,
72
+ # then change it to something different (by prepending a new byte)
73
+ assert radio_config.hmac_key is not None
74
+ radio_config.hmac_key += b'x'
75
+
76
+ # make radio, after changing the HMAC key
77
+
78
+ radio_sender = radio_config.create_sender()
79
+
80
+ # send message into radio
81
+
82
+ message = (MessageType.RESOURCE_INFO, {})
83
+
84
+ radio_sender.send(message)
85
+
86
+ # We should expect no message from the UDP side. That's hard to
87
+ # state in this scenario because UDP doesn't have any delivery
88
+ # guarantees for the test-failing case.
89
+ # So sleep a while to allow that test to misdeliver and fail.
90
+ time.sleep(1)
91
+
92
+ assert resource_msgs.empty(), "receiving queue should be empty"
93
+ assert udp_receiver.process.is_alive(), "UDP router process should still be alive"
94
+
95
+ with open(f"{tmpd_cwd}/monitoring_udp_router.log", "r") as logfile:
96
+ assert "ERROR" in logfile.read(), "Router log file should contain an error"
97
+
98
+ # shut down router
99
+
100
+ udp_receiver.shutdown()
101
+
102
+ # we can't inspect the process if it has been closed properly, but
103
+ # we can verify that it raises the expected ValueError the closed
104
+ # processes raise on access.
105
+ with pytest.raises(ValueError):
106
+ udp_receiver.process.exitcode
107
+
108
+
109
+ @pytest.mark.local
110
+ def test_wrong_digest(tmpd_cwd, caplog):
111
+ """Test when HMAC does not match.
112
+ """
113
+
114
+ resource_msgs = SpawnQueue()
115
+
116
+ radio_config = UDPRadio(address="localhost", atexit_timeout=0)
117
+
118
+ # start receiver
119
+ udp_receiver = radio_config.create_receiver(run_dir=str(tmpd_cwd),
120
+ resource_msgs=resource_msgs)
121
+
122
+ # check the hmac is configured in the right place,
123
+ # then change it to a different digest. The choice of different
124
+ # digest is arbitrary.
125
+ assert radio_config.hmac_digest is not None
126
+ radio_config.hmac_digest = "sha3_224"
127
+
128
+ # make radio, after changing the HMAC digest
129
+
130
+ radio_sender = radio_config.create_sender()
131
+
132
+ # send message into radio
133
+
134
+ message = (MessageType.RESOURCE_INFO, {})
135
+
136
+ radio_sender.send(message)
137
+
138
+ # We should expect no message from the UDP side. That's hard to
139
+ # state in this scenario because UDP doesn't have any delivery
140
+ # guarantees for the test-failing case.
141
+ # So sleep a while to allow that test to misdeliver and fail.
142
+ time.sleep(1)
143
+
144
+ assert resource_msgs.empty(), "receiving queue should be empty"
145
+ assert udp_receiver.process.is_alive(), "UDP router process should still be alive"
146
+
147
+ with open(f"{tmpd_cwd}/monitoring_udp_router.log", "r") as logfile:
148
+ assert "ERROR" in logfile.read(), "Router log file should contain an error"
149
+
150
+ # shut down router
151
+
152
+ udp_receiver.shutdown()
153
+
154
+ # we can't inspect the process if it has been closed properly, but
155
+ # we can verify that it raises the expected ValueError the closed
156
+ # processes raise on access.
157
+ with pytest.raises(ValueError):
158
+ udp_receiver.process.exitcode
159
+
160
+
161
+ @pytest.mark.local
162
+ def test_short_message(tmpd_cwd, caplog):
163
+ """Test when UDP message is so short it can't even be parsed into
164
+ HMAC + the rest.
165
+ """
166
+
167
+ resource_msgs = SpawnQueue()
168
+
169
+ radio_config = UDPRadio(address="localhost", atexit_timeout=0)
170
+
171
+ # start receiver
172
+ udp_receiver = radio_config.create_receiver(run_dir=str(tmpd_cwd),
173
+ resource_msgs=resource_msgs)
174
+
175
+ # now send a bad UDP message, rather than using the sender mechanism.
176
+
177
+ sock = socket.socket(socket.AF_INET,
178
+ socket.SOCK_DGRAM,
179
+ socket.IPPROTO_UDP)
180
+
181
+ sock.sendto(b'', (radio_config.address, radio_config.port))
182
+ sock.close()
183
+
184
+ # We should expect no message from the UDP side. That's hard to
185
+ # state in this scenario because UDP doesn't have any delivery
186
+ # guarantees for the test-failing case.
187
+ # So sleep a while to allow that test to misdeliver and fail.
188
+ time.sleep(1)
189
+
190
+ assert resource_msgs.empty(), "receiving queue should be empty"
191
+ assert udp_receiver.process.is_alive(), "UDP router process should still be alive"
192
+
193
+ with open(f"{tmpd_cwd}/monitoring_udp_router.log", "r") as logfile:
194
+ assert "ERROR" in logfile.read(), "Router log file should contain an error"
195
+
196
+ # shut down router
197
+
198
+ udp_receiver.shutdown()
199
+
200
+ # we can't inspect the process if it has been closed properly, but
201
+ # we can verify that it raises the expected ValueError the closed
202
+ # processes raise on access.
203
+ with pytest.raises(ValueError):
204
+ udp_receiver.process.exitcode
@@ -35,9 +35,7 @@ def fresh_config(run_dir):
35
35
  ],
36
36
  strategy='simple',
37
37
  strategy_period=0.1,
38
- monitoring=MonitoringHub(
39
- hub_address="localhost",
40
- )
38
+ monitoring=MonitoringHub(),
41
39
  )
42
40
 
43
41
 
@@ -1,7 +1,3 @@
1
- import os
2
- import signal
3
- import time
4
-
5
1
  import pytest
6
2
  import zmq
7
3
 
@@ -61,11 +57,11 @@ def test_bad_messages(try_assert, msg):
61
57
 
62
58
  with parsl.load(c):
63
59
 
64
- # send a bad message into the interchange on the task_outgoing worker
60
+ # send a bad message into the interchange on the worker_sock worker
65
61
  # channel, and then check that the interchange is still alive enough
66
62
  # that we can scale out a block and run a task.
67
63
 
68
- (task_port, result_port) = htex.command_client.run("WORKER_PORTS")
64
+ worker_port = htex.command_client.run("WORKER_BINDS")
69
65
 
70
66
  context = zmq.Context()
71
67
  channel_timeout = 10000 # in milliseconds
@@ -75,7 +71,7 @@ def test_bad_messages(try_assert, msg):
75
71
 
76
72
  task_channel.set_hwm(0)
77
73
  task_channel.setsockopt(zmq.SNDTIMEO, channel_timeout)
78
- task_channel.connect(f"tcp://localhost:{task_port}")
74
+ task_channel.connect(f"tcp://localhost:{worker_port}")
79
75
 
80
76
  task_channel.send(msg)
81
77
 
@@ -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", ["udp_router_proc", "dbm_proc", "filesystem_proc"])
33
+ @pytest.mark.parametrize("process_attr", ["dbm_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/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.06.16'
6
+ VERSION = '2025.06.30'