parsl 2024.1.22__py3-none-any.whl → 2024.2.5__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 (69) hide show
  1. parsl/app/errors.py +1 -5
  2. parsl/curvezmq.py +205 -0
  3. parsl/dataflow/dflow.py +1 -1
  4. parsl/executors/high_throughput/executor.py +78 -49
  5. parsl/executors/high_throughput/interchange.py +14 -7
  6. parsl/executors/high_throughput/process_worker_pool.py +44 -9
  7. parsl/executors/high_throughput/zmq_pipes.py +21 -15
  8. parsl/executors/taskvine/manager.py +60 -43
  9. parsl/executors/taskvine/manager_config.py +14 -0
  10. parsl/monitoring/monitoring.py +22 -4
  11. parsl/monitoring/remote.py +1 -1
  12. parsl/providers/errors.py +4 -6
  13. parsl/providers/slurm/slurm.py +7 -6
  14. parsl/tests/configs/ad_hoc_cluster_htex.py +1 -0
  15. parsl/tests/configs/azure_single_node.py +1 -0
  16. parsl/tests/configs/bluewaters.py +1 -0
  17. parsl/tests/configs/bridges.py +1 -0
  18. parsl/tests/configs/cc_in2p3.py +1 -0
  19. parsl/tests/configs/comet.py +1 -0
  20. parsl/tests/configs/cooley_htex.py +1 -0
  21. parsl/tests/configs/ec2_single_node.py +1 -0
  22. parsl/tests/configs/ec2_spot.py +1 -0
  23. parsl/tests/configs/frontera.py +1 -0
  24. parsl/tests/configs/htex_ad_hoc_cluster.py +1 -0
  25. parsl/tests/configs/htex_local.py +1 -0
  26. parsl/tests/configs/htex_local_alternate.py +1 -0
  27. parsl/tests/configs/htex_local_intask_staging.py +1 -0
  28. parsl/tests/configs/htex_local_rsync_staging.py +1 -0
  29. parsl/tests/configs/local_adhoc.py +1 -0
  30. parsl/tests/configs/midway.py +1 -0
  31. parsl/tests/configs/nscc_singapore.py +1 -0
  32. parsl/tests/configs/osg_htex.py +1 -0
  33. parsl/tests/configs/petrelkube.py +1 -0
  34. parsl/tests/configs/summit.py +1 -0
  35. parsl/tests/configs/swan_htex.py +1 -0
  36. parsl/tests/configs/theta.py +1 -0
  37. parsl/tests/conftest.py +12 -2
  38. parsl/tests/manual_tests/htex_local.py +1 -0
  39. parsl/tests/manual_tests/test_ad_hoc_htex.py +1 -0
  40. parsl/tests/manual_tests/test_fan_in_out_htex_remote.py +1 -0
  41. parsl/tests/manual_tests/test_memory_limits.py +1 -0
  42. parsl/tests/scaling_tests/htex_local.py +1 -0
  43. parsl/tests/sites/test_affinity.py +1 -0
  44. parsl/tests/sites/test_concurrent.py +2 -1
  45. parsl/tests/sites/test_dynamic_executor.py +1 -0
  46. parsl/tests/sites/test_worker_info.py +1 -0
  47. parsl/tests/test_bash_apps/test_stdout.py +6 -1
  48. parsl/tests/test_curvezmq.py +455 -0
  49. parsl/tests/test_data/test_file_apps.py +5 -5
  50. parsl/tests/test_data/test_file_staging.py +3 -3
  51. parsl/tests/test_docs/test_kwargs.py +3 -3
  52. parsl/tests/test_htex/test_cpu_affinity_explicit.py +52 -0
  53. parsl/tests/test_htex/test_htex.py +46 -0
  54. parsl/tests/test_htex/test_htex_zmq_binding.py +53 -13
  55. parsl/tests/test_python_apps/test_futures.py +5 -5
  56. parsl/tests/test_regression/test_97_parallelism_0.py +1 -0
  57. parsl/tests/test_scaling/test_block_error_handler.py +6 -5
  58. parsl/tests/test_scaling/test_regression_1621.py +1 -0
  59. parsl/tests/test_scaling/test_scale_down.py +1 -0
  60. parsl/version.py +1 -1
  61. {parsl-2024.1.22.data → parsl-2024.2.5.data}/scripts/process_worker_pool.py +44 -9
  62. {parsl-2024.1.22.dist-info → parsl-2024.2.5.dist-info}/METADATA +5 -6
  63. {parsl-2024.1.22.dist-info → parsl-2024.2.5.dist-info}/RECORD +69 -65
  64. {parsl-2024.1.22.data → parsl-2024.2.5.data}/scripts/exec_parsl_function.py +0 -0
  65. {parsl-2024.1.22.data → parsl-2024.2.5.data}/scripts/parsl_coprocess.py +0 -0
  66. {parsl-2024.1.22.dist-info → parsl-2024.2.5.dist-info}/LICENSE +0 -0
  67. {parsl-2024.1.22.dist-info → parsl-2024.2.5.dist-info}/WHEEL +0 -0
  68. {parsl-2024.1.22.dist-info → parsl-2024.2.5.dist-info}/entry_points.txt +0 -0
  69. {parsl-2024.1.22.dist-info → parsl-2024.2.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,52 @@
1
+ import logging
2
+ import os
3
+ import parsl
4
+ import pytest
5
+ import random
6
+ from parsl.tests.configs.htex_local import fresh_config
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ @parsl.python_app
12
+ def my_affinity():
13
+ """an app that returns the affinity of the unix process it is currently in.
14
+ """
15
+ import os
16
+ return os.sched_getaffinity(0)
17
+
18
+
19
+ @pytest.mark.local
20
+ @pytest.mark.multiple_cores_required
21
+ def test_cpu_affinity_explicit():
22
+ available_cores = os.sched_getaffinity(0)
23
+
24
+ logger.debug(f"Got these cores: {available_cores}")
25
+
26
+ assert len(available_cores) >= 2, "This test requires multiple cores. Run with '-k not multiple_cores' to skip"
27
+
28
+ cores_as_list = list(available_cores)
29
+
30
+ single_core = random.choice(cores_as_list)
31
+ affinity = f"list:{single_core}"
32
+
33
+ logger.debug(f"Will test with affinity for one worker, one core: {affinity}")
34
+
35
+ config = fresh_config()
36
+ config.executors[0].cpu_affinity = affinity
37
+ config.executors[0].max_workers = 1
38
+
39
+ logger.debug(f"config: {config}")
40
+ # TODO: is there a `with` style for this, to properly deal with exceptions?
41
+
42
+ parsl.load(config)
43
+ try:
44
+
45
+ worker_affinity = my_affinity().result()
46
+ logger.debug(f"worker reported this affinity: {worker_affinity}")
47
+ assert len(worker_affinity) == 1
48
+ assert worker_affinity == set((single_core,))
49
+
50
+ finally:
51
+ parsl.dfk().cleanup()
52
+ parsl.clear()
@@ -0,0 +1,46 @@
1
+ import pathlib
2
+
3
+ import pytest
4
+
5
+ from parsl import curvezmq
6
+ from parsl import HighThroughputExecutor
7
+
8
+
9
+ @pytest.mark.local
10
+ @pytest.mark.parametrize("encrypted", (True, False))
11
+ @pytest.mark.parametrize("cert_dir_provided", (True, False))
12
+ def test_htex_start_encrypted(
13
+ encrypted: bool, cert_dir_provided: bool, tmpd_cwd: pathlib.Path
14
+ ):
15
+ htex = HighThroughputExecutor(encrypted=encrypted)
16
+ htex.run_dir = str(tmpd_cwd)
17
+ if cert_dir_provided:
18
+ provided_base_dir = tmpd_cwd / "provided"
19
+ provided_base_dir.mkdir()
20
+ cert_dir = curvezmq.create_certificates(provided_base_dir)
21
+ htex.cert_dir = cert_dir
22
+ else:
23
+ cert_dir = curvezmq.create_certificates(htex.logdir)
24
+
25
+ if not encrypted and cert_dir_provided:
26
+ with pytest.raises(AttributeError) as pyt_e:
27
+ htex.start()
28
+ assert "change cert_dir to None" in str(pyt_e.value)
29
+ return
30
+
31
+ htex.start()
32
+
33
+ assert htex.encrypted is encrypted
34
+ if encrypted:
35
+ assert htex.cert_dir == cert_dir
36
+ assert htex.outgoing_q.zmq_context.cert_dir == cert_dir
37
+ assert htex.incoming_q.zmq_context.cert_dir == cert_dir
38
+ assert htex.command_client.zmq_context.cert_dir == cert_dir
39
+ assert isinstance(htex.outgoing_q.zmq_context, curvezmq.ClientContext)
40
+ assert isinstance(htex.incoming_q.zmq_context, curvezmq.ClientContext)
41
+ assert isinstance(htex.command_client.zmq_context, curvezmq.ClientContext)
42
+ else:
43
+ assert htex.cert_dir is None
44
+ assert htex.outgoing_q.zmq_context.cert_dir is None
45
+ assert htex.incoming_q.zmq_context.cert_dir is None
46
+ assert htex.command_client.zmq_context.cert_dir is None
@@ -1,42 +1,82 @@
1
- import logging
1
+ import pathlib
2
+ from typing import Optional
3
+ from unittest import mock
2
4
 
3
5
  import psutil
4
6
  import pytest
5
7
  import zmq
6
8
 
9
+ from parsl import curvezmq
7
10
  from parsl.executors.high_throughput.interchange import Interchange
8
11
 
9
12
 
10
- def test_interchange_binding_no_address():
11
- ix = Interchange()
13
+ @pytest.fixture
14
+ def encrypted(request: pytest.FixtureRequest):
15
+ if hasattr(request, "param"):
16
+ return request.param
17
+ return True
18
+
19
+
20
+ @pytest.fixture
21
+ def cert_dir(encrypted: bool, tmpd_cwd: pathlib.Path):
22
+ if not encrypted:
23
+ return None
24
+ return curvezmq.create_certificates(tmpd_cwd)
25
+
26
+
27
+ @pytest.mark.local
28
+ @pytest.mark.parametrize("encrypted", (True, False), indirect=True)
29
+ @mock.patch.object(curvezmq.ServerContext, "socket", return_value=mock.MagicMock())
30
+ def test_interchange_curvezmq_sockets(
31
+ mock_socket: mock.MagicMock, cert_dir: Optional[str], encrypted: bool
32
+ ):
33
+ address = "127.0.0.1"
34
+ ix = Interchange(interchange_address=address, cert_dir=cert_dir)
35
+ assert isinstance(ix.zmq_context, curvezmq.ServerContext)
36
+ assert ix.zmq_context.encrypted is encrypted
37
+ assert mock_socket.call_count == 5
38
+
39
+
40
+ @pytest.mark.local
41
+ @pytest.mark.parametrize("encrypted", (True, False), indirect=True)
42
+ def test_interchange_binding_no_address(cert_dir: Optional[str]):
43
+ ix = Interchange(cert_dir=cert_dir)
12
44
  assert ix.interchange_address == "*"
13
45
 
14
46
 
15
- def test_interchange_binding_with_address():
47
+ @pytest.mark.local
48
+ @pytest.mark.parametrize("encrypted", (True, False), indirect=True)
49
+ def test_interchange_binding_with_address(cert_dir: Optional[str]):
16
50
  # Using loopback address
17
51
  address = "127.0.0.1"
18
- ix = Interchange(interchange_address=address)
52
+ ix = Interchange(interchange_address=address, cert_dir=cert_dir)
19
53
  assert ix.interchange_address == address
20
54
 
21
55
 
22
- def test_interchange_binding_with_non_ipv4_address():
56
+ @pytest.mark.local
57
+ @pytest.mark.parametrize("encrypted", (True, False), indirect=True)
58
+ def test_interchange_binding_with_non_ipv4_address(cert_dir: Optional[str]):
23
59
  # Confirm that a ipv4 address is required
24
60
  address = "localhost"
25
61
  with pytest.raises(zmq.error.ZMQError):
26
- Interchange(interchange_address=address)
62
+ Interchange(interchange_address=address, cert_dir=cert_dir)
27
63
 
28
64
 
29
- def test_interchange_binding_bad_address():
30
- """ Confirm that we raise a ZMQError when a bad address is supplied"""
65
+ @pytest.mark.local
66
+ @pytest.mark.parametrize("encrypted", (True, False), indirect=True)
67
+ def test_interchange_binding_bad_address(cert_dir: Optional[str]):
68
+ """Confirm that we raise a ZMQError when a bad address is supplied"""
31
69
  address = "550.0.0.0"
32
70
  with pytest.raises(zmq.error.ZMQError):
33
- Interchange(interchange_address=address)
71
+ Interchange(interchange_address=address, cert_dir=cert_dir)
34
72
 
35
73
 
36
- def test_limited_interface_binding():
37
- """ When address is specified the worker_port would be bound to it rather than to 0.0.0.0"""
74
+ @pytest.mark.local
75
+ @pytest.mark.parametrize("encrypted", (True, False), indirect=True)
76
+ def test_limited_interface_binding(cert_dir: Optional[str]):
77
+ """When address is specified the worker_port would be bound to it rather than to 0.0.0.0"""
38
78
  address = "127.0.0.1"
39
- ix = Interchange(interchange_address=address)
79
+ ix = Interchange(interchange_address=address, cert_dir=cert_dir)
40
80
  ix.worker_result_port
41
81
  proc = psutil.Process()
42
82
  conns = proc.connections(kind="tcp")
@@ -42,10 +42,10 @@ def test_fut_case_1():
42
42
 
43
43
 
44
44
  @pytest.mark.staging_required
45
- def test_fut_case_2(tmp_path):
45
+ def test_fut_case_2(tmpd_cwd):
46
46
  """Testing the behavior of DataFutures where there are no dependencies
47
47
  """
48
- output_f = tmp_path / 'test_fut_case_2.txt'
48
+ output_f = tmpd_cwd / 'test_fut_case_2.txt'
49
49
  app_fu = delay_incr(1, delay=0.01, outputs=[File(str(output_f))])
50
50
  data_fu = app_fu.outputs[0]
51
51
 
@@ -69,13 +69,13 @@ def test_fut_case_3():
69
69
 
70
70
 
71
71
  @pytest.mark.staging_required
72
- def test_fut_case_4(tmp_path):
72
+ def test_fut_case_4(tmpd_cwd):
73
73
  """Testing the behavior of DataFutures where there are dependencies
74
74
 
75
75
  The first call has a delay, and the second call depends on the first
76
76
  """
77
- output_f1 = tmp_path / 'test_fut_case_4_f1.txt'
78
- output_f2 = tmp_path / 'test_fut_case_4_f2.txt'
77
+ output_f1 = tmpd_cwd / 'test_fut_case_4_f1.txt'
78
+ output_f2 = tmpd_cwd / 'test_fut_case_4_f2.txt'
79
79
  app_1 = delay_incr(1, delay=0.01, outputs=[File(str(output_f1))])
80
80
  app_2 = delay_incr(app_1, delay=0.01, outputs=[File(str(output_f2))])
81
81
  data_2 = app_2.outputs[0]
@@ -15,6 +15,7 @@ def local_config() -> Config:
15
15
  label="htex_local",
16
16
  worker_debug=True,
17
17
  cores_per_worker=1,
18
+ encrypted=True,
18
19
  provider=LocalProvider(
19
20
  init_blocks=0,
20
21
  min_blocks=0,
@@ -11,7 +11,7 @@ from functools import partial
11
11
  @pytest.mark.local
12
12
  def test_block_error_handler_false():
13
13
  mock = Mock()
14
- htex = HighThroughputExecutor(block_error_handler=False)
14
+ htex = HighThroughputExecutor(block_error_handler=False, encrypted=True)
15
15
  assert htex.block_error_handler is noop_error_handler
16
16
  htex.set_bad_state_and_fail_all = mock
17
17
 
@@ -27,7 +27,7 @@ def test_block_error_handler_false():
27
27
  @pytest.mark.local
28
28
  def test_block_error_handler_mock():
29
29
  handler_mock = Mock()
30
- htex = HighThroughputExecutor(block_error_handler=handler_mock)
30
+ htex = HighThroughputExecutor(block_error_handler=handler_mock, encrypted=True)
31
31
  assert htex.block_error_handler is handler_mock
32
32
 
33
33
  bad_jobs = {'1': JobStatus(JobState.FAILED),
@@ -43,6 +43,7 @@ def test_block_error_handler_mock():
43
43
  @pytest.mark.local
44
44
  def test_simple_error_handler():
45
45
  htex = HighThroughputExecutor(block_error_handler=simple_error_handler,
46
+ encrypted=True,
46
47
  provider=LocalProvider(init_blocks=3))
47
48
 
48
49
  assert htex.block_error_handler is simple_error_handler
@@ -76,7 +77,7 @@ def test_simple_error_handler():
76
77
 
77
78
  @pytest.mark.local
78
79
  def test_windowed_error_handler():
79
- htex = HighThroughputExecutor(block_error_handler=windowed_error_handler)
80
+ htex = HighThroughputExecutor(block_error_handler=windowed_error_handler, encrypted=True)
80
81
  assert htex.block_error_handler is windowed_error_handler
81
82
 
82
83
  bad_state_mock = Mock()
@@ -110,7 +111,7 @@ def test_windowed_error_handler():
110
111
 
111
112
  @pytest.mark.local
112
113
  def test_windowed_error_handler_sorting():
113
- htex = HighThroughputExecutor(block_error_handler=windowed_error_handler)
114
+ htex = HighThroughputExecutor(block_error_handler=windowed_error_handler, encrypted=True)
114
115
  assert htex.block_error_handler is windowed_error_handler
115
116
 
116
117
  bad_state_mock = Mock()
@@ -136,7 +137,7 @@ def test_windowed_error_handler_sorting():
136
137
  @pytest.mark.local
137
138
  def test_windowed_error_handler_with_threshold():
138
139
  error_handler = partial(windowed_error_handler, threshold=2)
139
- htex = HighThroughputExecutor(block_error_handler=error_handler)
140
+ htex = HighThroughputExecutor(block_error_handler=error_handler, encrypted=True)
140
141
  assert htex.block_error_handler is error_handler
141
142
 
142
143
  bad_state_mock = Mock()
@@ -49,6 +49,7 @@ def test_one_block(tmpd_cwd):
49
49
  address="127.0.0.1",
50
50
  worker_debug=True,
51
51
  cores_per_worker=1,
52
+ encrypted=True,
52
53
  provider=oneshot_provider,
53
54
  worker_logdir_root=str(tmpd_cwd)
54
55
  )
@@ -28,6 +28,7 @@ def local_config():
28
28
  label="htex_local",
29
29
  address="127.0.0.1",
30
30
  max_workers=1,
31
+ encrypted=True,
31
32
  provider=LocalProvider(
32
33
  channel=LocalChannel(),
33
34
  init_blocks=0,
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.01.22'
6
+ VERSION = '2024.02.05'
@@ -20,8 +20,8 @@ import multiprocessing
20
20
  from multiprocessing.managers import DictProxy
21
21
  from multiprocessing.sharedctypes import Synchronized
22
22
 
23
+ from parsl import curvezmq
23
24
  from parsl.process_loggers import wrap_with_logs
24
-
25
25
  from parsl.version import VERSION as PARSL_VERSION
26
26
  from parsl.app.errors import RemoteExceptionWrapper
27
27
  from parsl.executors.high_throughput.errors import WorkerLost
@@ -63,7 +63,8 @@ class Manager:
63
63
  heartbeat_period,
64
64
  poll_period,
65
65
  cpu_affinity,
66
- available_accelerators: Sequence[str]):
66
+ available_accelerators: Sequence[str],
67
+ cert_dir: Optional[str]):
67
68
  """
68
69
  Parameters
69
70
  ----------
@@ -118,6 +119,8 @@ class Manager:
118
119
  available_accelerators: list of str
119
120
  List of accelerators available to the workers.
120
121
 
122
+ cert_dir : str | None
123
+ Path to the certificate directory.
121
124
  """
122
125
 
123
126
  logger.info("Manager started")
@@ -137,15 +140,16 @@ class Manager:
137
140
  print("Failed to find a viable address to connect to interchange. Exiting")
138
141
  exit(5)
139
142
 
140
- self.context = zmq.Context()
141
- self.task_incoming = self.context.socket(zmq.DEALER)
143
+ self.cert_dir = cert_dir
144
+ self.zmq_context = curvezmq.ClientContext(self.cert_dir)
145
+ self.task_incoming = self.zmq_context.socket(zmq.DEALER)
142
146
  self.task_incoming.setsockopt(zmq.IDENTITY, uid.encode('utf-8'))
143
147
  # Linger is set to 0, so that the manager can exit even when there might be
144
148
  # messages in the pipe
145
149
  self.task_incoming.setsockopt(zmq.LINGER, 0)
146
150
  self.task_incoming.connect(task_q_url)
147
151
 
148
- self.result_outgoing = self.context.socket(zmq.DEALER)
152
+ self.result_outgoing = self.zmq_context.socket(zmq.DEALER)
149
153
  self.result_outgoing.setsockopt(zmq.IDENTITY, uid.encode('utf-8'))
150
154
  self.result_outgoing.setsockopt(zmq.LINGER, 0)
151
155
  self.result_outgoing.connect(result_q_url)
@@ -468,7 +472,7 @@ class Manager:
468
472
 
469
473
  self.task_incoming.close()
470
474
  self.result_outgoing.close()
471
- self.context.term()
475
+ self.zmq_context.term()
472
476
  delta = time.time() - start
473
477
  logger.info("process_worker_pool ran for {} seconds".format(delta))
474
478
  return
@@ -578,7 +582,7 @@ def worker(
578
582
  # If desired, set process affinity
579
583
  if cpu_affinity != "none":
580
584
  # Count the number of cores per worker
581
- avail_cores = sorted(os.sched_getaffinity(0)) # Get the available processors
585
+ avail_cores = sorted(os.sched_getaffinity(0)) # Get the available threads
582
586
  cores_per_worker = len(avail_cores) // pool_size
583
587
  assert cores_per_worker > 0, "Affinity does not work if there are more workers than cores"
584
588
 
@@ -590,6 +594,23 @@ def worker(
590
594
  my_cores = avail_cores[cores_per_worker * cpu_worker_id:cores_per_worker * (cpu_worker_id + 1)]
591
595
  elif cpu_affinity == "alternating":
592
596
  my_cores = avail_cores[worker_id::pool_size]
597
+ elif cpu_affinity[0:4] == "list":
598
+ thread_ranks = cpu_affinity.split(":")[1:]
599
+ if len(thread_ranks) != pool_size:
600
+ raise ValueError("Affinity list {} has wrong number of thread ranks".format(cpu_affinity))
601
+ threads = thread_ranks[worker_id]
602
+ thread_list = threads.split(",")
603
+ my_cores = []
604
+ for tl in thread_list:
605
+ thread_range = tl.split("-")
606
+ if len(thread_range) == 1:
607
+ my_cores.append(int(thread_range[0]))
608
+ elif len(thread_range) == 2:
609
+ start_thread = int(thread_range[0])
610
+ end_thread = int(thread_range[1]) + 1
611
+ my_cores += list(range(start_thread, end_thread))
612
+ else:
613
+ raise ValueError("Affinity list formatting is not expected {}".format(cpu_affinity))
593
614
  else:
594
615
  raise ValueError("Affinity strategy {} is not supported".format(cpu_affinity))
595
616
 
@@ -703,6 +724,8 @@ if __name__ == "__main__":
703
724
  help="Enable logging at DEBUG level")
704
725
  parser.add_argument("-a", "--addresses", default='',
705
726
  help="Comma separated list of addresses at which the interchange could be reached")
727
+ parser.add_argument("--cert_dir", required=True,
728
+ help="Path to certificate directory.")
706
729
  parser.add_argument("-l", "--logdir", default="process_worker_pool_logs",
707
730
  help="Process worker pool log directory")
708
731
  parser.add_argument("-u", "--uid", default=str(uuid.uuid4()).split('-')[-1],
@@ -729,7 +752,17 @@ if __name__ == "__main__":
729
752
  help="Poll period used in milliseconds")
730
753
  parser.add_argument("-r", "--result_port", required=True,
731
754
  help="REQUIRED: Result port for posting results to the interchange")
732
- parser.add_argument("--cpu-affinity", type=str, choices=["none", "block", "alternating", "block-reverse"],
755
+
756
+ def strategyorlist(s: str):
757
+ allowed_strategies = ["none", "block", "alternating", "block-reverse"]
758
+ if s in allowed_strategies:
759
+ return s
760
+ elif s[0:4] == "list":
761
+ return s
762
+ else:
763
+ raise argparse.ArgumentTypeError("cpu-affinity must be one of {} or a list format".format(allowed_strategies))
764
+
765
+ parser.add_argument("--cpu-affinity", type=strategyorlist,
733
766
  required=True,
734
767
  help="Whether/how workers should control CPU affinity.")
735
768
  parser.add_argument("--available-accelerators", type=str, nargs="*",
@@ -746,6 +779,7 @@ if __name__ == "__main__":
746
779
 
747
780
  logger.info("Python version: {}".format(sys.version))
748
781
  logger.info("Debug logging: {}".format(args.debug))
782
+ logger.info("Certificates dir: {}".format(args.cert_dir))
749
783
  logger.info("Log dir: {}".format(args.logdir))
750
784
  logger.info("Manager ID: {}".format(args.uid))
751
785
  logger.info("Block ID: {}".format(args.block_id))
@@ -777,7 +811,8 @@ if __name__ == "__main__":
777
811
  heartbeat_period=int(args.hb_period),
778
812
  poll_period=int(args.poll),
779
813
  cpu_affinity=args.cpu_affinity,
780
- available_accelerators=args.available_accelerators)
814
+ available_accelerators=args.available_accelerators,
815
+ cert_dir=None if args.cert_dir == "None" else args.cert_dir)
781
816
  manager.start()
782
817
 
783
818
  except Exception:
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: parsl
3
- Version: 2024.1.22
3
+ Version: 2024.2.5
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.01.22.tar.gz
6
+ Download-URL: https://github.com/Parsl/parsl/archive/2024.02.05.tar.gz
7
7
  Author: The Parsl Team
8
8
  Author-email: parsl@googlegroups.com
9
9
  License: Apache 2.0
@@ -19,9 +19,8 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Requires-Python: >=3.8.0
20
20
  License-File: LICENSE
21
21
  Requires-Dist: pyzmq >=17.1.2
22
- Requires-Dist: typeguard <3,>=2.10
22
+ Requires-Dist: typeguard !=3.*,<5,>=2.10
23
23
  Requires-Dist: typing-extensions <5,>=4.6
24
- Requires-Dist: six
25
24
  Requires-Dist: globus-sdk
26
25
  Requires-Dist: dill
27
26
  Requires-Dist: tblib
@@ -35,7 +34,7 @@ Requires-Dist: pydot ; extra == 'all'
35
34
  Requires-Dist: networkx <2.6,>=2.5 ; extra == 'all'
36
35
  Requires-Dist: Flask >=1.0.2 ; extra == 'all'
37
36
  Requires-Dist: flask-sqlalchemy ; extra == 'all'
38
- Requires-Dist: pandas <3 ; extra == 'all'
37
+ Requires-Dist: pandas <2.2 ; extra == 'all'
39
38
  Requires-Dist: plotly ; extra == 'all'
40
39
  Requires-Dist: python-daemon ; extra == 'all'
41
40
  Requires-Dist: boto3 ; extra == 'all'
@@ -90,7 +89,7 @@ Requires-Dist: pydot ; extra == 'visualization'
90
89
  Requires-Dist: networkx <2.6,>=2.5 ; extra == 'visualization'
91
90
  Requires-Dist: Flask >=1.0.2 ; extra == 'visualization'
92
91
  Requires-Dist: flask-sqlalchemy ; extra == 'visualization'
93
- Requires-Dist: pandas <3 ; extra == 'visualization'
92
+ Requires-Dist: pandas <2.2 ; extra == 'visualization'
94
93
  Requires-Dist: plotly ; extra == 'visualization'
95
94
  Requires-Dist: python-daemon ; extra == 'visualization'
96
95
  Provides-Extra: workqueue