parsl 2024.10.21__py3-none-any.whl → 2024.11.4__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 (59) hide show
  1. parsl/channels/base.py +0 -11
  2. parsl/channels/errors.py +0 -17
  3. parsl/channels/local/local.py +3 -16
  4. parsl/channels/ssh/ssh.py +0 -11
  5. parsl/dataflow/dflow.py +6 -6
  6. parsl/executors/high_throughput/executor.py +0 -1
  7. parsl/executors/high_throughput/interchange.py +8 -5
  8. parsl/executors/high_throughput/mpi_resource_management.py +0 -12
  9. parsl/executors/high_throughput/process_worker_pool.py +0 -8
  10. parsl/monitoring/db_manager.py +1 -1
  11. parsl/monitoring/monitoring.py +9 -11
  12. parsl/monitoring/radios.py +5 -16
  13. parsl/monitoring/remote.py +3 -5
  14. parsl/monitoring/router.py +4 -7
  15. parsl/monitoring/types.py +3 -6
  16. parsl/providers/__init__.py +0 -2
  17. parsl/providers/base.py +1 -17
  18. parsl/tests/conftest.py +4 -0
  19. parsl/tests/site_tests/site_config_selector.py +1 -6
  20. parsl/tests/test_bash_apps/test_basic.py +3 -0
  21. parsl/tests/test_bash_apps/test_error_codes.py +4 -0
  22. parsl/tests/test_bash_apps/test_kwarg_storage.py +1 -0
  23. parsl/tests/test_bash_apps/test_memoize.py +2 -6
  24. parsl/tests/test_bash_apps/test_memoize_ignore_args.py +3 -0
  25. parsl/tests/test_bash_apps/test_memoize_ignore_args_regr.py +1 -0
  26. parsl/tests/test_bash_apps/test_multiline.py +1 -0
  27. parsl/tests/test_bash_apps/test_stdout.py +2 -0
  28. parsl/tests/{integration/test_channels → test_channels}/test_local_channel.py +4 -8
  29. parsl/tests/test_docs/test_from_slides.py +3 -0
  30. parsl/tests/test_docs/test_kwargs.py +3 -0
  31. parsl/tests/test_monitoring/test_basic.py +13 -1
  32. parsl/tests/test_python_apps/test_outputs.py +1 -0
  33. parsl/tests/test_regression/test_226.py +1 -0
  34. parsl/tests/test_scaling/test_worker_interchange_bad_messages_3262.py +92 -0
  35. parsl/tests/test_serialization/test_3495_deserialize_managerlost.py +1 -1
  36. parsl/tests/test_staging/test_docs_1.py +1 -0
  37. parsl/tests/test_staging/test_output_chain_filenames.py +3 -0
  38. parsl/tests/test_staging/test_staging_ftp.py +1 -0
  39. parsl/tests/test_staging/test_staging_https.py +3 -0
  40. parsl/tests/test_staging/test_staging_stdout.py +2 -0
  41. parsl/version.py +1 -1
  42. {parsl-2024.10.21.data → parsl-2024.11.4.data}/scripts/interchange.py +8 -5
  43. {parsl-2024.10.21.data → parsl-2024.11.4.data}/scripts/process_worker_pool.py +0 -8
  44. {parsl-2024.10.21.dist-info → parsl-2024.11.4.dist-info}/METADATA +2 -2
  45. {parsl-2024.10.21.dist-info → parsl-2024.11.4.dist-info}/RECORD +51 -58
  46. parsl/providers/cobalt/__init__.py +0 -0
  47. parsl/providers/cobalt/cobalt.py +0 -236
  48. parsl/providers/cobalt/template.py +0 -17
  49. parsl/tests/configs/cooley_htex.py +0 -37
  50. parsl/tests/configs/theta.py +0 -37
  51. parsl/tests/integration/test_channels/test_channels.py +0 -17
  52. parsl/tests/manual_tests/test_fan_in_out_htex_remote.py +0 -88
  53. parsl/tests/test_providers/test_cobalt_deprecation_warning.py +0 -18
  54. {parsl-2024.10.21.data → parsl-2024.11.4.data}/scripts/exec_parsl_function.py +0 -0
  55. {parsl-2024.10.21.data → parsl-2024.11.4.data}/scripts/parsl_coprocess.py +0 -0
  56. {parsl-2024.10.21.dist-info → parsl-2024.11.4.dist-info}/LICENSE +0 -0
  57. {parsl-2024.10.21.dist-info → parsl-2024.11.4.dist-info}/WHEEL +0 -0
  58. {parsl-2024.10.21.dist-info → parsl-2024.11.4.dist-info}/entry_points.txt +0 -0
  59. {parsl-2024.10.21.dist-info → parsl-2024.11.4.dist-info}/top_level.txt +0 -0
@@ -9,9 +9,7 @@ def fail_on_presence(outputs=()):
9
9
  return 'if [ -f {0} ] ; then exit 1 ; else touch {0}; fi'.format(outputs[0])
10
10
 
11
11
 
12
- # This test is an oddity that requires a shared-FS and simply
13
- # won't work if there's a staging provider.
14
- # @pytest.mark.sharedFS_required
12
+ @pytest.mark.shared_fs
15
13
  def test_bash_memoization(tmpd_cwd, n=2):
16
14
  """Testing bash memoization
17
15
  """
@@ -29,9 +27,7 @@ def fail_on_presence_kw(outputs=(), foo=None):
29
27
  return 'if [ -f {0} ] ; then exit 1 ; else touch {0}; fi'.format(outputs[0])
30
28
 
31
29
 
32
- # This test is an oddity that requires a shared-FS and simply
33
- # won't work if there's a staging provider.
34
- # @pytest.mark.sharedFS_required
30
+ @pytest.mark.shared_fs
35
31
  def test_bash_memoization_keywords(tmpd_cwd, n=2):
36
32
  """Testing bash memoization
37
33
  """
@@ -1,5 +1,7 @@
1
1
  import os
2
2
 
3
+ import pytest
4
+
3
5
  import parsl
4
6
  from parsl.app.app import bash_app
5
7
 
@@ -21,6 +23,7 @@ def no_checkpoint_stdout_app_ignore_args(stdout=None):
21
23
  return "echo X"
22
24
 
23
25
 
26
+ @pytest.mark.shared_fs
24
27
  def test_memo_stdout(tmpd_cwd):
25
28
  path_x = tmpd_cwd / "test.memo.stdout.x"
26
29
 
@@ -29,6 +29,7 @@ def no_checkpoint_stdout_app(stdout=None):
29
29
  return "echo X"
30
30
 
31
31
 
32
+ @pytest.mark.shared_fs
32
33
  def test_memo_stdout(tmpd_cwd):
33
34
  assert const_list_x == const_list_x_arg
34
35
 
@@ -14,6 +14,7 @@ def multiline(inputs=(), outputs=(), stderr=None, stdout=None):
14
14
  """.format(inputs=inputs, outputs=outputs)
15
15
 
16
16
 
17
+ @pytest.mark.shared_fs
17
18
  def test_multiline(tmpd_cwd):
18
19
  so, se = tmpd_cwd / "std.out", tmpd_cwd / "std.err"
19
20
  f = multiline(
@@ -91,6 +91,7 @@ def test_bad_stderr_file():
91
91
 
92
92
 
93
93
  @pytest.mark.executor_supports_std_stream_tuples
94
+ @pytest.mark.shared_fs
94
95
  def test_stdout_truncate(tmpd_cwd, caplog):
95
96
  """Testing truncation of prior content of stdout"""
96
97
 
@@ -110,6 +111,7 @@ def test_stdout_truncate(tmpd_cwd, caplog):
110
111
  assert record.levelno < logging.ERROR
111
112
 
112
113
 
114
+ @pytest.mark.shared_fs
113
115
  def test_stdout_append(tmpd_cwd, caplog):
114
116
  """Testing appending to prior content of stdout (default open() mode)"""
115
117
 
@@ -1,6 +1,9 @@
1
+ import pytest
2
+
1
3
  from parsl.channels.local.local import LocalChannel
2
4
 
3
5
 
6
+ @pytest.mark.local
4
7
  def test_env():
5
8
  ''' Regression testing for issue #27
6
9
  '''
@@ -15,9 +18,8 @@ def test_env():
15
18
  x = [s for s in stdout if s.startswith("HOME=")]
16
19
  assert x, "HOME not found"
17
20
 
18
- print("RC:{} \nSTDOUT:{} \nSTDERR:{}".format(rc, stdout, stderr))
19
-
20
21
 
22
+ @pytest.mark.local
21
23
  def test_env_mod():
22
24
  ''' Testing for env update at execute time.
23
25
  '''
@@ -34,9 +36,3 @@ def test_env_mod():
34
36
 
35
37
  x = [s for s in stdout if s.startswith("TEST_ENV=fooo")]
36
38
  assert x, "User set env missing"
37
-
38
-
39
- if __name__ == "__main__":
40
-
41
- test_env()
42
- test_env_mod()
@@ -1,5 +1,7 @@
1
1
  import os
2
2
 
3
+ import pytest
4
+
3
5
  from parsl.app.app import bash_app, python_app
4
6
  from parsl.data_provider.files import File
5
7
 
@@ -15,6 +17,7 @@ def cat(inputs=[]):
15
17
  return f.readlines()
16
18
 
17
19
 
20
+ @pytest.mark.staging_required
18
21
  def test_slides():
19
22
  """Testing code snippet from slides """
20
23
 
@@ -1,6 +1,8 @@
1
1
  """Functions used to explain kwargs"""
2
2
  from pathlib import Path
3
3
 
4
+ import pytest
5
+
4
6
  from parsl import File, python_app
5
7
 
6
8
 
@@ -19,6 +21,7 @@ def test_inputs():
19
21
  assert reduce_future.result() == 6
20
22
 
21
23
 
24
+ @pytest.mark.shared_fs
22
25
  def test_outputs(tmpd_cwd):
23
26
  @python_app()
24
27
  def write_app(message, outputs=()):
@@ -42,6 +42,18 @@ def htex_udp_config():
42
42
  return c
43
43
 
44
44
 
45
+ def htex_filesystem_config():
46
+ """This config will force filesystem radio"""
47
+ from parsl.tests.configs.htex_local_alternate import fresh_config
48
+ c = fresh_config()
49
+ assert len(c.executors) == 1
50
+
51
+ assert c.executors[0].radio_mode == "htex", "precondition: htex has a radio mode attribute, configured for htex radio"
52
+ c.executors[0].radio_mode = "filesystem"
53
+
54
+ return c
55
+
56
+
45
57
  def workqueue_config():
46
58
  from parsl.tests.configs.workqueue_ex import fresh_config
47
59
  c = fresh_config()
@@ -61,7 +73,7 @@ def taskvine_config():
61
73
 
62
74
 
63
75
  @pytest.mark.local
64
- @pytest.mark.parametrize("fresh_config", [htex_config, htex_udp_config, workqueue_config, taskvine_config])
76
+ @pytest.mark.parametrize("fresh_config", [htex_config, htex_filesystem_config, htex_udp_config, workqueue_config, taskvine_config])
65
77
  def test_row_counts(tmpd_cwd, fresh_config):
66
78
  # this is imported here rather than at module level because
67
79
  # it isn't available in a plain parsl install, so this module
@@ -16,6 +16,7 @@ def double(x, outputs=[]):
16
16
  whitelist = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'configs', '*threads*')
17
17
 
18
18
 
19
+ @pytest.mark.shared_fs
19
20
  def test_launch_apps(tmpd_cwd, n=2):
20
21
  outdir = tmpd_cwd / "outputs"
21
22
  outdir.mkdir()
@@ -53,6 +53,7 @@ def test_get_dataframe():
53
53
  assert res.equals(data), 'Unexpected dataframe'
54
54
 
55
55
 
56
+ @pytest.mark.shared_fs
56
57
  def test_bash_default_arg():
57
58
  if os.path.exists('std.out'):
58
59
  os.remove('std.out')
@@ -0,0 +1,92 @@
1
+ import os
2
+ import signal
3
+ import time
4
+
5
+ import pytest
6
+ import zmq
7
+
8
+ import parsl
9
+ from parsl.channels import LocalChannel
10
+ from parsl.config import Config
11
+ from parsl.executors import HighThroughputExecutor
12
+ from parsl.launchers import SimpleLauncher
13
+ from parsl.providers import LocalProvider
14
+
15
+ T_s = 1
16
+
17
+
18
+ def fresh_config():
19
+ htex = HighThroughputExecutor(
20
+ heartbeat_period=1 * T_s,
21
+ heartbeat_threshold=3 * T_s,
22
+ label="htex_local",
23
+ worker_debug=True,
24
+ cores_per_worker=1,
25
+ encrypted=False,
26
+ provider=LocalProvider(
27
+ channel=LocalChannel(),
28
+ init_blocks=0,
29
+ min_blocks=0,
30
+ max_blocks=0,
31
+ launcher=SimpleLauncher(),
32
+ ),
33
+ )
34
+ c = Config(
35
+ executors=[htex],
36
+ strategy='none',
37
+ strategy_period=0.5,
38
+ )
39
+ return c, htex
40
+
41
+
42
+ @parsl.python_app
43
+ def app():
44
+ return 7
45
+
46
+
47
+ @pytest.mark.local
48
+ @pytest.mark.parametrize("msg",
49
+ (b'FuzzyByte\rSTREAM', # not JSON
50
+ b'{}', # missing fields
51
+ b'{"type":"heartbeat"}', # regression test #3262
52
+ )
53
+ )
54
+ def test_bad_messages(try_assert, msg):
55
+ """This tests that the interchange is resilient to a few different bad
56
+ messages: malformed messages caused by implementation errors, and
57
+ heartbeat messages from managers that are not registered.
58
+
59
+ The heartbeat test is a regression test for issues #3262, #3632
60
+ """
61
+
62
+ c, htex = fresh_config()
63
+
64
+ with parsl.load(c):
65
+
66
+ # send a bad message into the interchange on the task_outgoing worker
67
+ # channel, and then check that the interchange is still alive enough
68
+ # that we can scale out a block and run a task.
69
+
70
+ (task_port, result_port) = htex.command_client.run("WORKER_PORTS")
71
+
72
+ context = zmq.Context()
73
+ channel_timeout = 10000 # in milliseconds
74
+ task_channel = context.socket(zmq.DEALER)
75
+ task_channel.setsockopt(zmq.LINGER, 0)
76
+ task_channel.setsockopt(zmq.IDENTITY, b'testid')
77
+
78
+ task_channel.set_hwm(0)
79
+ task_channel.setsockopt(zmq.SNDTIMEO, channel_timeout)
80
+ task_channel.connect(f"tcp://localhost:{task_port}")
81
+
82
+ task_channel.send(msg)
83
+
84
+ # If the interchange exits, it's likely that this test will hang rather
85
+ # than raise an error, because the interchange interaction code
86
+ # assumes the interchange is always there.
87
+ # In the case of issue #3262, an exception message goes to stderr, and
88
+ # no error goes to the interchange log file.
89
+ htex.scale_out_facade(1)
90
+ try_assert(lambda: len(htex.connected_managers()) == 1, timeout_ms=10000)
91
+
92
+ assert app().result() == 7
@@ -32,7 +32,7 @@ def test_manager_lost_system_failure(tmpd_cwd):
32
32
  cores_per_worker=1,
33
33
  worker_logdir_root=str(tmpd_cwd),
34
34
  heartbeat_period=1,
35
- heartbeat_threshold=1,
35
+ heartbeat_threshold=3,
36
36
  )
37
37
  c = Config(executors=[hte], strategy='simple', strategy_period=0.1)
38
38
 
@@ -12,6 +12,7 @@ def convert(inputs=[], outputs=[]):
12
12
 
13
13
 
14
14
  @pytest.mark.cleannet
15
+ @pytest.mark.staging_required
15
16
  def test():
16
17
  # create an remote Parsl file
17
18
  inp = File('ftp://ftp.iana.org/pub/mirror/rirstats/arin/ARIN-STATS-FORMAT-CHANGE.txt')
@@ -1,5 +1,7 @@
1
1
  from concurrent.futures import Future
2
2
 
3
+ import pytest
4
+
3
5
  from parsl import File
4
6
  from parsl.app.app import bash_app
5
7
 
@@ -14,6 +16,7 @@ def app2(inputs=(), outputs=(), stdout=None, stderr=None, mock=False):
14
16
  return f"echo '{inputs[0]}' > {outputs[0]}"
15
17
 
16
18
 
19
+ @pytest.mark.shared_fs
17
20
  def test_behavior(tmpd_cwd):
18
21
  expected_path = str(tmpd_cwd / "simple-out.txt")
19
22
  app1_future = app1(
@@ -15,6 +15,7 @@ def sort_strings(inputs=[], outputs=[]):
15
15
 
16
16
 
17
17
  @pytest.mark.cleannet
18
+ @pytest.mark.staging_required
18
19
  def test_staging_ftp():
19
20
  """Test staging for an ftp file
20
21
 
@@ -48,6 +48,7 @@ def sort_strings_additional_executor(inputs=(), outputs=()):
48
48
 
49
49
 
50
50
  @pytest.mark.cleannet
51
+ @pytest.mark.staging_required
51
52
  def test_staging_https_cleannet(tmpd_cwd):
52
53
  unsorted_file = File(_unsorted_url)
53
54
  sorted_file = File(tmpd_cwd / 'sorted.txt')
@@ -68,6 +69,7 @@ def test_staging_https_local(tmpd_cwd):
68
69
 
69
70
 
70
71
  @pytest.mark.cleannet
72
+ @pytest.mark.staging_required
71
73
  def test_staging_https_kwargs(tmpd_cwd):
72
74
  unsorted_file = File(_unsorted_url)
73
75
  sorted_file = File(tmpd_cwd / 'sorted.txt')
@@ -78,6 +80,7 @@ def test_staging_https_kwargs(tmpd_cwd):
78
80
 
79
81
 
80
82
  @pytest.mark.cleannet
83
+ @pytest.mark.staging_required
81
84
  def test_staging_https_args(tmpd_cwd):
82
85
  unsorted_file = File(_unsorted_url)
83
86
  sorted_file = File(tmpd_cwd / 'sorted.txt')
@@ -15,6 +15,7 @@ def output_to_stds(*, stdout=parsl.AUTO_LOGNAME, stderr=parsl.AUTO_LOGNAME):
15
15
  return "echo hello ; echo goodbye >&2"
16
16
 
17
17
 
18
+ @pytest.mark.staging_required
18
19
  def test_stdout_staging_file(tmpd_cwd, caplog):
19
20
  basename = str(tmpd_cwd) + "/stdout.txt"
20
21
  stdout_file = File("file://" + basename)
@@ -30,6 +31,7 @@ def test_stdout_staging_file(tmpd_cwd, caplog):
30
31
  assert record.levelno < logging.ERROR
31
32
 
32
33
 
34
+ @pytest.mark.staging_required
33
35
  def test_stdout_stderr_staging_zip(tmpd_cwd, caplog):
34
36
  zipfile_name = str(tmpd_cwd) + "/staging.zip"
35
37
  stdout_relative_path = "somewhere/test-out.txt"
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 = '2024.10.21'
6
+ VERSION = '2024.11.04'
@@ -66,7 +66,7 @@ class Interchange:
66
66
  If specified the interchange will only listen on this address for connections from workers
67
67
  else, it binds to all addresses.
68
68
 
69
- client_ports : triple(int, int, int)
69
+ client_ports : tuple(int, int, int)
70
70
  The ports at which the client can be reached
71
71
 
72
72
  worker_ports : tuple(int, int)
@@ -104,7 +104,6 @@ class Interchange:
104
104
  os.makedirs(self.logdir, exist_ok=True)
105
105
 
106
106
  start_file_logger("{}/interchange.log".format(self.logdir), level=logging_level)
107
- logger.propagate = False
108
107
  logger.debug("Initializing Interchange process")
109
108
 
110
109
  self.client_address = client_address
@@ -437,9 +436,13 @@ class Interchange:
437
436
  logger.info(f"Manager {manager_id!r} has compatible Parsl version {msg['parsl_v']}")
438
437
  logger.info(f"Manager {manager_id!r} has compatible Python version {msg['python_v'].rsplit('.', 1)[0]}")
439
438
  elif msg['type'] == 'heartbeat':
440
- self._ready_managers[manager_id]['last_heartbeat'] = time.time()
441
- logger.debug("Manager %r sent heartbeat via tasks connection", manager_id)
442
- self.task_outgoing.send_multipart([manager_id, b'', PKL_HEARTBEAT_CODE])
439
+ manager = self._ready_managers.get(manager_id)
440
+ if manager:
441
+ manager['last_heartbeat'] = time.time()
442
+ logger.debug("Manager %r sent heartbeat via tasks connection", manager_id)
443
+ self.task_outgoing.send_multipart([manager_id, b'', PKL_HEARTBEAT_CODE])
444
+ else:
445
+ logger.warning("Received heartbeat via tasks connection for not-registered manager %r", manager_id)
443
446
  elif msg['type'] == 'drain':
444
447
  self._ready_managers[manager_id]['draining'] = True
445
448
  logger.debug("Manager %r requested drain", manager_id)
@@ -650,14 +650,6 @@ def worker(
650
650
  debug: bool,
651
651
  mpi_launcher: str,
652
652
  ):
653
- """
654
-
655
- Put request token into queue
656
- Get task from task_queue
657
- Pop request from queue
658
- Put result into result_queue
659
- """
660
-
661
653
  # override the global logger inherited from the __main__ process (which
662
654
  # usually logs to manager.log) with one specific to this worker.
663
655
  global logger
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: parsl
3
- Version: 2024.10.21
3
+ Version: 2024.11.4
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/2024.10.21.tar.gz
6
+ Download-URL: https://github.com/Parsl/parsl/archive/2024.11.04.tar.gz
7
7
  Author: The Parsl Team
8
8
  Author-email: parsl@googlegroups.com
9
9
  License: Apache 2.0