parsl 2024.3.11__py3-none-any.whl → 2025.1.13__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 (369) hide show
  1. parsl/__init__.py +9 -10
  2. parsl/addresses.py +29 -7
  3. parsl/app/app.py +7 -8
  4. parsl/app/bash.py +15 -8
  5. parsl/app/errors.py +10 -13
  6. parsl/app/futures.py +8 -10
  7. parsl/app/python.py +2 -1
  8. parsl/benchmark/perf.py +2 -1
  9. parsl/concurrent/__init__.py +2 -2
  10. parsl/config.py +57 -10
  11. parsl/configs/ASPIRE1.py +6 -5
  12. parsl/configs/Azure.py +9 -8
  13. parsl/configs/bridges.py +6 -4
  14. parsl/configs/cc_in2p3.py +3 -3
  15. parsl/configs/ec2.py +3 -1
  16. parsl/configs/expanse.py +4 -3
  17. parsl/configs/frontera.py +3 -4
  18. parsl/configs/htex_local.py +3 -4
  19. parsl/configs/illinoiscluster.py +3 -1
  20. parsl/configs/improv.py +34 -0
  21. parsl/configs/kubernetes.py +4 -3
  22. parsl/configs/local_threads.py +5 -1
  23. parsl/configs/midway.py +5 -3
  24. parsl/configs/osg.py +4 -2
  25. parsl/configs/polaris.py +4 -2
  26. parsl/configs/stampede2.py +6 -5
  27. parsl/configs/summit.py +3 -3
  28. parsl/configs/toss3_llnl.py +4 -3
  29. parsl/configs/vineex_local.py +6 -4
  30. parsl/configs/wqex_local.py +5 -3
  31. parsl/curvezmq.py +4 -0
  32. parsl/data_provider/data_manager.py +4 -3
  33. parsl/data_provider/file_noop.py +1 -2
  34. parsl/data_provider/files.py +3 -3
  35. parsl/data_provider/ftp.py +1 -3
  36. parsl/data_provider/globus.py +7 -6
  37. parsl/data_provider/http.py +2 -2
  38. parsl/data_provider/rsync.py +1 -1
  39. parsl/data_provider/staging.py +2 -2
  40. parsl/data_provider/zip.py +135 -0
  41. parsl/dataflow/dependency_resolvers.py +115 -0
  42. parsl/dataflow/dflow.py +262 -224
  43. parsl/dataflow/errors.py +3 -5
  44. parsl/dataflow/futures.py +27 -14
  45. parsl/dataflow/memoization.py +5 -5
  46. parsl/dataflow/rundirs.py +5 -6
  47. parsl/dataflow/taskrecord.py +4 -5
  48. parsl/executors/__init__.py +4 -2
  49. parsl/executors/base.py +45 -15
  50. parsl/executors/errors.py +13 -0
  51. parsl/executors/execute_task.py +37 -0
  52. parsl/executors/flux/execute_parsl_task.py +3 -3
  53. parsl/executors/flux/executor.py +18 -19
  54. parsl/executors/flux/flux_instance_manager.py +26 -27
  55. parsl/executors/high_throughput/errors.py +43 -3
  56. parsl/executors/high_throughput/executor.py +316 -282
  57. parsl/executors/high_throughput/interchange.py +158 -167
  58. parsl/executors/high_throughput/manager_record.py +5 -0
  59. parsl/executors/high_throughput/manager_selector.py +55 -0
  60. parsl/executors/high_throughput/monitoring_info.py +2 -1
  61. parsl/executors/high_throughput/mpi_executor.py +113 -0
  62. parsl/executors/high_throughput/mpi_prefix_composer.py +10 -11
  63. parsl/executors/high_throughput/mpi_resource_management.py +6 -17
  64. parsl/executors/high_throughput/probe.py +9 -7
  65. parsl/executors/high_throughput/process_worker_pool.py +115 -77
  66. parsl/executors/high_throughput/zmq_pipes.py +81 -23
  67. parsl/executors/radical/executor.py +130 -79
  68. parsl/executors/radical/rpex_resources.py +17 -15
  69. parsl/executors/radical/rpex_worker.py +4 -3
  70. parsl/executors/status_handling.py +157 -51
  71. parsl/executors/taskvine/__init__.py +1 -1
  72. parsl/executors/taskvine/errors.py +1 -1
  73. parsl/executors/taskvine/exec_parsl_function.py +2 -2
  74. parsl/executors/taskvine/executor.py +41 -57
  75. parsl/executors/taskvine/factory.py +1 -1
  76. parsl/executors/taskvine/factory_config.py +1 -1
  77. parsl/executors/taskvine/manager.py +18 -13
  78. parsl/executors/taskvine/manager_config.py +9 -5
  79. parsl/executors/threads.py +6 -6
  80. parsl/executors/workqueue/errors.py +1 -1
  81. parsl/executors/workqueue/exec_parsl_function.py +6 -5
  82. parsl/executors/workqueue/executor.py +64 -63
  83. parsl/executors/workqueue/parsl_coprocess.py +1 -1
  84. parsl/jobs/error_handlers.py +2 -2
  85. parsl/jobs/job_status_poller.py +30 -113
  86. parsl/jobs/states.py +7 -2
  87. parsl/jobs/strategy.py +43 -31
  88. parsl/launchers/__init__.py +12 -3
  89. parsl/launchers/errors.py +1 -1
  90. parsl/launchers/launchers.py +6 -12
  91. parsl/log_utils.py +9 -6
  92. parsl/monitoring/db_manager.py +59 -95
  93. parsl/monitoring/errors.py +6 -0
  94. parsl/monitoring/monitoring.py +87 -356
  95. parsl/monitoring/queries/pandas.py +1 -2
  96. parsl/monitoring/radios/base.py +13 -0
  97. parsl/monitoring/radios/filesystem.py +52 -0
  98. parsl/monitoring/radios/htex.py +57 -0
  99. parsl/monitoring/radios/multiprocessing.py +17 -0
  100. parsl/monitoring/radios/udp.py +56 -0
  101. parsl/monitoring/radios/zmq.py +17 -0
  102. parsl/monitoring/remote.py +33 -37
  103. parsl/monitoring/router.py +212 -0
  104. parsl/monitoring/types.py +5 -6
  105. parsl/monitoring/visualization/app.py +4 -2
  106. parsl/monitoring/visualization/models.py +0 -1
  107. parsl/monitoring/visualization/plots/default/workflow_plots.py +11 -4
  108. parsl/monitoring/visualization/plots/default/workflow_resource_plots.py +1 -0
  109. parsl/monitoring/visualization/utils.py +0 -1
  110. parsl/monitoring/visualization/views.py +16 -8
  111. parsl/multiprocessing.py +0 -1
  112. parsl/process_loggers.py +1 -2
  113. parsl/providers/__init__.py +8 -17
  114. parsl/providers/aws/aws.py +2 -3
  115. parsl/providers/azure/azure.py +4 -5
  116. parsl/providers/base.py +2 -18
  117. parsl/providers/cluster_provider.py +4 -12
  118. parsl/providers/condor/condor.py +7 -17
  119. parsl/providers/errors.py +2 -2
  120. parsl/providers/googlecloud/googlecloud.py +2 -1
  121. parsl/providers/grid_engine/grid_engine.py +5 -14
  122. parsl/providers/kubernetes/kube.py +80 -40
  123. parsl/providers/local/local.py +13 -26
  124. parsl/providers/lsf/lsf.py +5 -23
  125. parsl/providers/pbspro/pbspro.py +5 -17
  126. parsl/providers/slurm/slurm.py +81 -39
  127. parsl/providers/torque/torque.py +3 -14
  128. parsl/serialize/__init__.py +8 -3
  129. parsl/serialize/base.py +1 -2
  130. parsl/serialize/concretes.py +5 -4
  131. parsl/serialize/facade.py +3 -3
  132. parsl/serialize/proxystore.py +3 -2
  133. parsl/tests/__init__.py +1 -1
  134. parsl/tests/configs/azure_single_node.py +4 -5
  135. parsl/tests/configs/bridges.py +3 -2
  136. parsl/tests/configs/cc_in2p3.py +1 -3
  137. parsl/tests/configs/comet.py +2 -1
  138. parsl/tests/configs/ec2_single_node.py +1 -2
  139. parsl/tests/configs/ec2_spot.py +1 -2
  140. parsl/tests/configs/flux_local.py +11 -0
  141. parsl/tests/configs/frontera.py +2 -3
  142. parsl/tests/configs/htex_local.py +3 -5
  143. parsl/tests/configs/htex_local_alternate.py +11 -15
  144. parsl/tests/configs/htex_local_intask_staging.py +5 -9
  145. parsl/tests/configs/htex_local_rsync_staging.py +4 -8
  146. parsl/tests/configs/local_radical.py +1 -3
  147. parsl/tests/configs/local_radical_mpi.py +2 -2
  148. parsl/tests/configs/local_threads_checkpoint_periodic.py +8 -10
  149. parsl/tests/configs/local_threads_monitoring.py +0 -1
  150. parsl/tests/configs/midway.py +2 -2
  151. parsl/tests/configs/nscc_singapore.py +3 -3
  152. parsl/tests/configs/osg_htex.py +1 -1
  153. parsl/tests/configs/petrelkube.py +3 -2
  154. parsl/tests/configs/slurm_local.py +24 -0
  155. parsl/tests/configs/summit.py +1 -0
  156. parsl/tests/configs/taskvine_ex.py +4 -7
  157. parsl/tests/configs/user_opts.py +2 -8
  158. parsl/tests/configs/workqueue_ex.py +4 -6
  159. parsl/tests/conftest.py +27 -13
  160. parsl/tests/integration/test_stress/test_python_simple.py +3 -4
  161. parsl/tests/integration/test_stress/test_python_threads.py +3 -5
  162. parsl/tests/manual_tests/htex_local.py +4 -6
  163. parsl/tests/manual_tests/test_basic.py +1 -0
  164. parsl/tests/manual_tests/test_log_filter.py +3 -1
  165. parsl/tests/manual_tests/test_memory_limits.py +6 -8
  166. parsl/tests/manual_tests/test_regression_220.py +2 -1
  167. parsl/tests/manual_tests/test_udp_simple.py +4 -4
  168. parsl/tests/manual_tests/test_worker_count.py +3 -2
  169. parsl/tests/scaling_tests/htex_local.py +2 -4
  170. parsl/tests/scaling_tests/test_scale.py +0 -9
  171. parsl/tests/scaling_tests/vineex_condor.py +1 -2
  172. parsl/tests/scaling_tests/vineex_local.py +1 -2
  173. parsl/tests/site_tests/site_config_selector.py +1 -6
  174. parsl/tests/site_tests/test_provider.py +4 -2
  175. parsl/tests/site_tests/test_site.py +2 -0
  176. parsl/tests/sites/test_affinity.py +7 -7
  177. parsl/tests/sites/test_dynamic_executor.py +3 -4
  178. parsl/tests/sites/test_ec2.py +3 -2
  179. parsl/tests/sites/test_worker_info.py +4 -5
  180. parsl/tests/test_aalst_patterns.py +0 -1
  181. parsl/tests/test_bash_apps/test_apptimeout.py +2 -2
  182. parsl/tests/test_bash_apps/test_basic.py +10 -4
  183. parsl/tests/test_bash_apps/test_error_codes.py +5 -7
  184. parsl/tests/test_bash_apps/test_inputs_default.py +25 -0
  185. parsl/tests/test_bash_apps/test_kwarg_storage.py +1 -1
  186. parsl/tests/test_bash_apps/test_memoize.py +2 -8
  187. parsl/tests/test_bash_apps/test_memoize_ignore_args.py +9 -14
  188. parsl/tests/test_bash_apps/test_memoize_ignore_args_regr.py +9 -14
  189. parsl/tests/test_bash_apps/test_multiline.py +1 -1
  190. parsl/tests/test_bash_apps/test_pipeline.py +1 -1
  191. parsl/tests/test_bash_apps/test_std_uri.py +123 -0
  192. parsl/tests/test_bash_apps/test_stdout.py +33 -8
  193. parsl/tests/test_callables.py +2 -2
  194. parsl/tests/test_checkpointing/test_periodic.py +21 -39
  195. parsl/tests/test_checkpointing/test_python_checkpoint_1.py +1 -0
  196. parsl/tests/test_checkpointing/test_python_checkpoint_2.py +2 -2
  197. parsl/tests/test_checkpointing/test_python_checkpoint_3.py +0 -1
  198. parsl/tests/test_checkpointing/test_regression_239.py +1 -1
  199. parsl/tests/test_checkpointing/test_task_exit.py +2 -3
  200. parsl/tests/test_docs/test_from_slides.py +5 -2
  201. parsl/tests/test_docs/test_kwargs.py +4 -1
  202. parsl/tests/test_docs/test_tutorial_1.py +1 -2
  203. parsl/tests/test_docs/test_workflow1.py +2 -2
  204. parsl/tests/test_docs/test_workflow2.py +0 -1
  205. parsl/tests/test_error_handling/test_rand_fail.py +2 -2
  206. parsl/tests/test_error_handling/test_resource_spec.py +10 -12
  207. parsl/tests/test_error_handling/test_retries.py +6 -16
  208. parsl/tests/test_error_handling/test_retry_handler.py +1 -0
  209. parsl/tests/test_error_handling/test_retry_handler_failure.py +2 -1
  210. parsl/tests/test_error_handling/test_serialization_fail.py +1 -1
  211. parsl/tests/test_error_handling/test_wrap_with_logs.py +1 -0
  212. parsl/tests/test_execute_task.py +29 -0
  213. parsl/tests/test_flux.py +1 -1
  214. parsl/tests/test_htex/test_basic.py +2 -3
  215. parsl/tests/test_htex/test_block_manager_selector_unit.py +20 -0
  216. parsl/tests/test_htex/test_command_client_timeout.py +66 -0
  217. parsl/tests/test_htex/test_connected_blocks.py +3 -2
  218. parsl/tests/test_htex/test_cpu_affinity_explicit.py +6 -10
  219. parsl/tests/test_htex/test_disconnected_blocks.py +6 -5
  220. parsl/tests/test_htex/test_disconnected_blocks_failing_provider.py +71 -0
  221. parsl/tests/test_htex/test_drain.py +79 -0
  222. parsl/tests/test_htex/test_htex.py +51 -25
  223. parsl/tests/test_htex/test_manager_failure.py +0 -1
  224. parsl/tests/test_htex/test_manager_selector_by_block.py +51 -0
  225. parsl/tests/test_htex/test_managers_command.py +36 -0
  226. parsl/tests/test_htex/test_missing_worker.py +2 -12
  227. parsl/tests/test_htex/test_multiple_disconnected_blocks.py +9 -9
  228. parsl/tests/test_htex/test_resource_spec_validation.py +45 -0
  229. parsl/tests/test_htex/test_zmq_binding.py +29 -8
  230. parsl/tests/test_monitoring/test_app_names.py +86 -0
  231. parsl/tests/test_monitoring/test_basic.py +73 -25
  232. parsl/tests/test_monitoring/test_db_locks.py +6 -4
  233. parsl/tests/test_monitoring/test_fuzz_zmq.py +19 -8
  234. parsl/tests/test_monitoring/test_htex_init_blocks_vs_monitoring.py +80 -0
  235. parsl/tests/test_monitoring/test_incomplete_futures.py +5 -4
  236. parsl/tests/test_monitoring/test_memoization_representation.py +4 -2
  237. parsl/tests/test_monitoring/test_stdouterr.py +134 -0
  238. parsl/tests/test_monitoring/test_viz_colouring.py +1 -0
  239. parsl/tests/test_mpi_apps/test_bad_mpi_config.py +33 -26
  240. parsl/tests/test_mpi_apps/test_mpi_mode_enabled.py +28 -11
  241. parsl/tests/test_mpi_apps/test_mpi_prefix.py +4 -4
  242. parsl/tests/test_mpi_apps/test_mpi_scheduler.py +7 -2
  243. parsl/tests/test_mpi_apps/test_mpiex.py +64 -0
  244. parsl/tests/test_mpi_apps/test_resource_spec.py +42 -49
  245. parsl/tests/test_providers/test_kubernetes_provider.py +102 -0
  246. parsl/tests/test_providers/test_local_provider.py +3 -132
  247. parsl/tests/test_providers/test_pbspro_template.py +2 -3
  248. parsl/tests/test_providers/test_slurm_template.py +2 -3
  249. parsl/tests/test_providers/test_submiterror_deprecation.py +2 -1
  250. parsl/tests/test_python_apps/test_context_manager.py +128 -0
  251. parsl/tests/test_python_apps/test_dep_standard_futures.py +2 -1
  252. parsl/tests/test_python_apps/test_dependencies_deep.py +59 -0
  253. parsl/tests/test_python_apps/test_fail.py +0 -25
  254. parsl/tests/test_python_apps/test_futures.py +2 -1
  255. parsl/tests/test_python_apps/test_inputs_default.py +22 -0
  256. parsl/tests/test_python_apps/test_join.py +0 -1
  257. parsl/tests/test_python_apps/test_lifted.py +11 -7
  258. parsl/tests/test_python_apps/test_memoize_bad_id_for_memo.py +1 -0
  259. parsl/tests/test_python_apps/test_outputs.py +1 -1
  260. parsl/tests/test_python_apps/test_pluggable_future_resolution.py +161 -0
  261. parsl/tests/test_radical/test_mpi_funcs.py +1 -2
  262. parsl/tests/test_regression/test_1480.py +2 -1
  263. parsl/tests/test_regression/test_1653.py +2 -1
  264. parsl/tests/test_regression/test_226.py +1 -0
  265. parsl/tests/test_regression/test_2652.py +1 -0
  266. parsl/tests/test_regression/test_69a.py +0 -1
  267. parsl/tests/test_regression/test_854.py +4 -2
  268. parsl/tests/test_regression/test_97_parallelism_0.py +1 -2
  269. parsl/tests/test_regression/test_98.py +0 -1
  270. parsl/tests/test_scaling/test_block_error_handler.py +9 -4
  271. parsl/tests/test_scaling/test_regression_1621.py +11 -15
  272. parsl/tests/test_scaling/test_regression_3568_scaledown_vs_MISSING.py +84 -0
  273. parsl/tests/test_scaling/test_regression_3696_oscillation.py +103 -0
  274. parsl/tests/test_scaling/test_scale_down.py +2 -5
  275. parsl/tests/test_scaling/test_scale_down_htex_auto_scale.py +6 -18
  276. parsl/tests/test_scaling/test_scale_down_htex_unregistered.py +71 -0
  277. parsl/tests/test_scaling/test_shutdown_scalein.py +73 -0
  278. parsl/tests/test_scaling/test_worker_interchange_bad_messages_3262.py +90 -0
  279. parsl/tests/test_serialization/test_2555_caching_deserializer.py +1 -1
  280. parsl/tests/test_serialization/test_3495_deserialize_managerlost.py +47 -0
  281. parsl/tests/test_serialization/test_basic.py +2 -1
  282. parsl/tests/test_serialization/test_htex_code_cache.py +3 -4
  283. parsl/tests/test_serialization/test_pack_resource_spec.py +2 -1
  284. parsl/tests/test_serialization/test_proxystore_configured.py +10 -6
  285. parsl/tests/test_serialization/test_proxystore_impl.py +5 -3
  286. parsl/tests/test_shutdown/test_kill_monitoring.py +64 -0
  287. parsl/tests/test_staging/staging_provider.py +2 -2
  288. parsl/tests/test_staging/test_1316.py +3 -4
  289. parsl/tests/test_staging/test_docs_1.py +2 -1
  290. parsl/tests/test_staging/test_docs_2.py +2 -1
  291. parsl/tests/test_staging/test_elaborate_noop_file.py +2 -3
  292. parsl/tests/{test_data → test_staging}/test_file.py +6 -6
  293. parsl/tests/{test_data → test_staging}/test_output_chain_filenames.py +3 -0
  294. parsl/tests/test_staging/test_staging_ftp.py +1 -0
  295. parsl/tests/test_staging/test_staging_https.py +5 -2
  296. parsl/tests/test_staging/test_staging_stdout.py +64 -0
  297. parsl/tests/test_staging/test_zip_in.py +39 -0
  298. parsl/tests/test_staging/test_zip_out.py +110 -0
  299. parsl/tests/test_staging/test_zip_to_zip.py +41 -0
  300. parsl/tests/test_summary.py +2 -2
  301. parsl/tests/test_thread_parallelism.py +0 -1
  302. parsl/tests/test_threads/test_configs.py +1 -2
  303. parsl/tests/test_threads/test_lazy_errors.py +2 -2
  304. parsl/tests/test_utils/test_execute_wait.py +35 -0
  305. parsl/tests/test_utils/test_sanitize_dns.py +76 -0
  306. parsl/tests/unit/test_address.py +20 -0
  307. parsl/tests/unit/test_file.py +99 -0
  308. parsl/tests/unit/test_usage_tracking.py +66 -0
  309. parsl/usage_tracking/api.py +65 -0
  310. parsl/usage_tracking/levels.py +6 -0
  311. parsl/usage_tracking/usage.py +104 -62
  312. parsl/utils.py +139 -6
  313. parsl/version.py +1 -1
  314. {parsl-2024.3.11.data → parsl-2025.1.13.data}/scripts/exec_parsl_function.py +6 -5
  315. parsl-2025.1.13.data/scripts/interchange.py +649 -0
  316. {parsl-2024.3.11.data → parsl-2025.1.13.data}/scripts/process_worker_pool.py +115 -77
  317. parsl-2025.1.13.dist-info/METADATA +96 -0
  318. parsl-2025.1.13.dist-info/RECORD +462 -0
  319. {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/WHEEL +1 -1
  320. parsl/channels/__init__.py +0 -7
  321. parsl/channels/base.py +0 -141
  322. parsl/channels/errors.py +0 -113
  323. parsl/channels/local/local.py +0 -164
  324. parsl/channels/oauth_ssh/oauth_ssh.py +0 -110
  325. parsl/channels/ssh/ssh.py +0 -276
  326. parsl/channels/ssh_il/__init__.py +0 -0
  327. parsl/channels/ssh_il/ssh_il.py +0 -74
  328. parsl/configs/ad_hoc.py +0 -35
  329. parsl/executors/radical/rpex_master.py +0 -42
  330. parsl/monitoring/radios.py +0 -175
  331. parsl/providers/ad_hoc/__init__.py +0 -0
  332. parsl/providers/ad_hoc/ad_hoc.py +0 -248
  333. parsl/providers/cobalt/__init__.py +0 -0
  334. parsl/providers/cobalt/cobalt.py +0 -236
  335. parsl/providers/cobalt/template.py +0 -17
  336. parsl/tests/configs/ad_hoc_cluster_htex.py +0 -35
  337. parsl/tests/configs/cooley_htex.py +0 -37
  338. parsl/tests/configs/htex_ad_hoc_cluster.py +0 -28
  339. parsl/tests/configs/local_adhoc.py +0 -18
  340. parsl/tests/configs/swan_htex.py +0 -43
  341. parsl/tests/configs/theta.py +0 -37
  342. parsl/tests/integration/test_channels/__init__.py +0 -0
  343. parsl/tests/integration/test_channels/test_channels.py +0 -17
  344. parsl/tests/integration/test_channels/test_local_channel.py +0 -42
  345. parsl/tests/integration/test_channels/test_scp_1.py +0 -45
  346. parsl/tests/integration/test_channels/test_ssh_1.py +0 -40
  347. parsl/tests/integration/test_channels/test_ssh_errors.py +0 -46
  348. parsl/tests/integration/test_channels/test_ssh_file_transport.py +0 -41
  349. parsl/tests/integration/test_channels/test_ssh_interactive.py +0 -24
  350. parsl/tests/manual_tests/test_ad_hoc_htex.py +0 -48
  351. parsl/tests/manual_tests/test_fan_in_out_htex_remote.py +0 -88
  352. parsl/tests/manual_tests/test_oauth_ssh.py +0 -13
  353. parsl/tests/sites/test_local_adhoc.py +0 -61
  354. parsl/tests/test_channels/__init__.py +0 -0
  355. parsl/tests/test_channels/test_large_output.py +0 -22
  356. parsl/tests/test_data/__init__.py +0 -0
  357. parsl/tests/test_mpi_apps/test_mpi_mode_disabled.py +0 -51
  358. parsl/tests/test_providers/test_cobalt_deprecation_warning.py +0 -16
  359. parsl-2024.3.11.dist-info/METADATA +0 -98
  360. parsl-2024.3.11.dist-info/RECORD +0 -447
  361. parsl/{channels/local → monitoring/radios}/__init__.py +0 -0
  362. parsl/{channels/oauth_ssh → tests/test_shutdown}/__init__.py +0 -0
  363. parsl/tests/{test_data → test_staging}/test_file_apps.py +0 -0
  364. parsl/tests/{test_data → test_staging}/test_file_staging.py +0 -0
  365. parsl/{channels/ssh → tests/unit}/__init__.py +0 -0
  366. {parsl-2024.3.11.data → parsl-2025.1.13.data}/scripts/parsl_coprocess.py +1 -1
  367. {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/LICENSE +0 -0
  368. {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/entry_points.txt +0 -0
  369. {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,79 @@
1
+ import time
2
+
3
+ import pytest
4
+
5
+ import parsl
6
+ from parsl.config import Config
7
+ from parsl.executors import HighThroughputExecutor
8
+ from parsl.launchers import SimpleLauncher
9
+ from parsl.providers import LocalProvider
10
+
11
+ # this constant is used to scale some durations that happen
12
+ # based around the expected drain period: the drain period
13
+ # is TIME_CONST seconds, and the single executed task will
14
+ # last twice that many number of seconds.
15
+ TIME_CONST = 4
16
+
17
+ CONNECTED_MANAGERS_POLL_MS = 100
18
+
19
+
20
+ def local_config():
21
+ return Config(
22
+ executors=[
23
+ HighThroughputExecutor(
24
+ label="htex_local",
25
+ drain_period=TIME_CONST,
26
+ worker_debug=True,
27
+ cores_per_worker=1,
28
+ encrypted=True,
29
+ provider=LocalProvider(
30
+ init_blocks=1,
31
+ min_blocks=0,
32
+ max_blocks=0,
33
+ launcher=SimpleLauncher(),
34
+ ),
35
+ )
36
+ ],
37
+ strategy='none',
38
+ strategy_period=0.1
39
+ )
40
+
41
+
42
+ @parsl.python_app
43
+ def f(n):
44
+ import time
45
+ time.sleep(n)
46
+
47
+
48
+ @pytest.mark.local
49
+ def test_drain(try_assert):
50
+
51
+ htex = parsl.dfk().executors['htex_local']
52
+
53
+ # wait till we have a block running...
54
+
55
+ try_assert(lambda: len(htex.connected_managers()) == 1, check_period_ms=CONNECTED_MANAGERS_POLL_MS)
56
+
57
+ managers = htex.connected_managers()
58
+ assert managers[0]['active'], "The manager should be active"
59
+ assert not managers[0]['draining'], "The manager should not be draining"
60
+
61
+ fut = f(TIME_CONST * 2)
62
+
63
+ time.sleep(TIME_CONST)
64
+
65
+ # this assert should happen *very fast* after the above delay...
66
+ try_assert(lambda: htex.connected_managers()[0]['draining'], timeout_ms=500, check_period_ms=CONNECTED_MANAGERS_POLL_MS)
67
+
68
+ # and the test task should still be running...
69
+ assert not fut.done(), "The test task should still be running"
70
+
71
+ fut.result()
72
+
73
+ # and now we should see the manager disappear...
74
+ # ... with strategy='none', this should be coming from draining but
75
+ # that information isn't immediately obvious from the absence in
76
+ # connected managers.
77
+ # As with the above draining assert, this should happen very fast after
78
+ # the task ends.
79
+ try_assert(lambda: len(htex.connected_managers()) == 0, timeout_ms=500, check_period_ms=CONNECTED_MANAGERS_POLL_MS)
@@ -1,12 +1,12 @@
1
+ import logging
1
2
  import pathlib
2
- import warnings
3
+ from subprocess import Popen, TimeoutExpired
4
+ from typing import Optional, Sequence
3
5
  from unittest import mock
4
6
 
5
7
  import pytest
6
8
 
7
- from parsl import curvezmq
8
- from parsl import HighThroughputExecutor
9
- from parsl.multiprocessing import ForkProcess
9
+ from parsl import HighThroughputExecutor, curvezmq
10
10
 
11
11
  _MOCK_BASE = "parsl.executors.high_throughput.executor"
12
12
 
@@ -72,51 +72,77 @@ def test_htex_start_encrypted(
72
72
  @pytest.mark.local
73
73
  @pytest.mark.parametrize("started", (True, False))
74
74
  @pytest.mark.parametrize("timeout_expires", (True, False))
75
- @mock.patch(f"{_MOCK_BASE}.logger")
76
75
  def test_htex_shutdown(
77
- mock_logger: mock.MagicMock,
78
76
  started: bool,
79
77
  timeout_expires: bool,
80
78
  htex: HighThroughputExecutor,
79
+ caplog
81
80
  ):
82
- mock_ix_proc = mock.Mock(spec=ForkProcess)
81
+ mock_ix_proc = mock.Mock(spec=Popen)
83
82
 
84
83
  if started:
85
84
  htex.interchange_proc = mock_ix_proc
86
- mock_ix_proc.is_alive.return_value = True
85
+
86
+ # This will, in the absence of any exit trigger, block forever if
87
+ # no timeout is given and if the interchange does not terminate.
88
+ # Raise an exception to report that, rather than actually block,
89
+ # and hope that nothing is catching that exception.
90
+
91
+ # this function implements the behaviour if the interchange has
92
+ # not received a termination call
93
+ def proc_wait_alive(timeout):
94
+ if timeout:
95
+ raise TimeoutExpired(cmd="mock-interchange", timeout=timeout)
96
+ else:
97
+ raise RuntimeError("This wait call would hang forever")
98
+
99
+ def proc_wait_terminated(timeout):
100
+ return 0
101
+
102
+ mock_ix_proc.wait.side_effect = proc_wait_alive
87
103
 
88
104
  if not timeout_expires:
89
105
  # Simulate termination of the Interchange process
90
106
  def kill_interchange(*args, **kwargs):
91
- mock_ix_proc.is_alive.return_value = False
107
+ mock_ix_proc.wait.side_effect = proc_wait_terminated
92
108
 
93
109
  mock_ix_proc.terminate.side_effect = kill_interchange
94
110
 
95
- htex.shutdown()
111
+ with caplog.at_level(logging.INFO):
112
+ htex.shutdown()
96
113
 
97
- mock_logs = mock_logger.info.call_args_list
98
114
  if started:
99
115
  assert mock_ix_proc.terminate.called
100
- assert mock_ix_proc.join.called
101
- assert {"timeout": 10} == mock_ix_proc.join.call_args[1]
116
+ assert mock_ix_proc.wait.called
117
+ assert {"timeout": 10} == mock_ix_proc.wait.call_args[1]
102
118
  if timeout_expires:
103
- assert "Unable to terminate Interchange" in mock_logs[1][0][0]
119
+ assert "Unable to terminate Interchange" in caplog.text
104
120
  assert mock_ix_proc.kill.called
105
- assert "Attempting" in mock_logs[0][0][0]
106
- assert "Finished" in mock_logs[-1][0][0]
121
+ assert "Attempting HighThroughputExecutor shutdown" in caplog.text
122
+ assert "Finished HighThroughputExecutor shutdown" in caplog.text
107
123
  else:
108
124
  assert not mock_ix_proc.terminate.called
109
- assert not mock_ix_proc.join.called
110
- assert "has not started" in mock_logs[0][0][0]
125
+ assert not mock_ix_proc.wait.called
126
+ assert "HighThroughputExecutor has not started" in caplog.text
111
127
 
112
128
 
113
129
  @pytest.mark.local
114
- def test_max_workers_per_node():
115
- with pytest.warns(DeprecationWarning) as record:
116
- htex = HighThroughputExecutor(max_workers_per_node=1, max_workers=2)
130
+ @pytest.mark.parametrize("cmd", (None, "custom-launch-cmd"))
131
+ def test_htex_worker_pool_launch_cmd(cmd: Optional[str]):
132
+ if cmd:
133
+ htex = HighThroughputExecutor(launch_cmd=cmd)
134
+ assert htex.launch_cmd == cmd
135
+ else:
136
+ htex = HighThroughputExecutor()
137
+ assert htex.launch_cmd.startswith("process_worker_pool.py")
117
138
 
118
- warning_msg = "max_workers is deprecated"
119
- assert any(warning_msg in str(warning.message) for warning in record)
120
139
 
121
- # Ensure max_workers_per_node takes precedence
122
- assert htex.max_workers_per_node == htex.max_workers == 1
140
+ @pytest.mark.local
141
+ @pytest.mark.parametrize("cmd", (None, ["custom", "launch", "cmd"]))
142
+ def test_htex_interchange_launch_cmd(cmd: Optional[Sequence[str]]):
143
+ if cmd:
144
+ htex = HighThroughputExecutor(interchange_launch_cmd=cmd)
145
+ assert htex.interchange_launch_cmd == cmd
146
+ else:
147
+ htex = HighThroughputExecutor()
148
+ assert htex.interchange_launch_cmd == ["interchange.py"]
@@ -20,7 +20,6 @@ def load_config():
20
20
  yield
21
21
 
22
22
  parsl.dfk().cleanup()
23
- parsl.clear()
24
23
 
25
24
 
26
25
  @python_app
@@ -0,0 +1,51 @@
1
+ import time
2
+
3
+ import pytest
4
+
5
+ import parsl
6
+ from parsl.app.app import bash_app, python_app
7
+ from parsl.config import Config
8
+ from parsl.executors import HighThroughputExecutor
9
+ from parsl.executors.high_throughput.manager_selector import (
10
+ BlockIdManagerSelector,
11
+ ManagerSelector,
12
+ )
13
+ from parsl.launchers import WrappedLauncher
14
+ from parsl.providers import LocalProvider
15
+ from parsl.usage_tracking.levels import LEVEL_1
16
+
17
+ BLOCK_COUNT = 2
18
+
19
+
20
+ @parsl.python_app
21
+ def get_worker_pid():
22
+ import os
23
+ return os.environ.get('PARSL_WORKER_BLOCK_ID')
24
+
25
+
26
+ @pytest.mark.local
27
+ def test_block_id_selection(try_assert):
28
+ htex = HighThroughputExecutor(
29
+ label="htex_local",
30
+ max_workers_per_node=1,
31
+ manager_selector=BlockIdManagerSelector(),
32
+ provider=LocalProvider(
33
+ init_blocks=BLOCK_COUNT,
34
+ max_blocks=BLOCK_COUNT,
35
+ min_blocks=BLOCK_COUNT,
36
+ ),
37
+ )
38
+
39
+ config = Config(
40
+ executors=[htex],
41
+ usage_tracking=LEVEL_1,
42
+ )
43
+
44
+ with parsl.load(config):
45
+ blockids = []
46
+ try_assert(lambda: len(htex.connected_managers()) == BLOCK_COUNT, timeout_ms=20000)
47
+ for i in range(10):
48
+ future = get_worker_pid()
49
+ blockids.append(future.result())
50
+
51
+ assert all(blockid == "1" for blockid in blockids)
@@ -0,0 +1,36 @@
1
+ import logging
2
+ import sys
3
+
4
+ import pytest
5
+
6
+ import parsl
7
+ from parsl.app.app import python_app
8
+ from parsl.tests.configs.htex_local import fresh_config
9
+
10
+
11
+ def local_config():
12
+ config = fresh_config()
13
+ config.executors[0].poll_period = 1
14
+ config.executors[0].max_workers_per_node = 1
15
+ return config
16
+
17
+
18
+ @python_app
19
+ def dummy():
20
+ pass
21
+
22
+
23
+ @pytest.mark.local
24
+ def test_connected_managers():
25
+
26
+ # Run dummy function to ensure a manager is online
27
+ x = dummy()
28
+ assert x.result() is None
29
+ executor = parsl.dfk().executors['htex_local']
30
+ manager_info_list = executor.connected_managers()
31
+ assert len(manager_info_list) == 1
32
+ manager_info = manager_info_list[0]
33
+ assert 'python_version' in manager_info
34
+ assert 'parsl_version' in manager_info
35
+ assert manager_info['parsl_version'] == parsl.__version__
36
+ assert manager_info['python_version'] == f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
@@ -5,18 +5,12 @@ from parsl.app.app import python_app
5
5
  from parsl.tests.configs.htex_local import fresh_config
6
6
 
7
7
 
8
- def local_setup():
8
+ def local_config():
9
9
  config = fresh_config()
10
10
  config.executors[0].poll_period = 1
11
11
  config.executors[0].max_workers_per_node = 1
12
12
  config.executors[0].launch_cmd = "executable_that_hopefully_does_not_exist_1030509.py"
13
- parsl.load(config)
14
-
15
-
16
- def local_teardown():
17
-
18
- parsl.dfk().cleanup()
19
- parsl.clear()
13
+ return config
20
14
 
21
15
 
22
16
  @python_app
@@ -37,7 +31,3 @@ def test_that_it_fails():
37
31
  raise Exception("The app somehow ran without a valid worker")
38
32
 
39
33
  assert parsl.dfk().config.executors[0]._executor_bad_state.is_set()
40
-
41
- # htex needs shutting down explicitly because dfk.cleanup() will not
42
- # do that, as it is in bad state
43
- parsl.dfk().config.executors[0].shutdown()
@@ -1,11 +1,13 @@
1
1
  import logging
2
- import parsl
2
+
3
3
  import pytest
4
- from parsl.executors import HighThroughputExecutor
4
+
5
+ import parsl
5
6
  from parsl import Config
6
- from parsl.providers import LocalProvider
7
+ from parsl.executors import HighThroughputExecutor
7
8
  from parsl.executors.errors import BadStateException
8
- from parsl.jobs.states import JobStatus, JobState
9
+ from parsl.jobs.states import JobState, JobStatus
10
+ from parsl.providers import LocalProvider
9
11
 
10
12
 
11
13
  def local_config():
@@ -19,16 +21,14 @@ def local_config():
19
21
  poll_period=100,
20
22
  max_workers_per_node=1,
21
23
  provider=LocalProvider(
22
- worker_init="conda deactivate; export PATH=''; which python; exit 0",
23
- init_blocks=2,
24
- max_blocks=4,
25
- min_blocks=0,
24
+ worker_init="exit 0",
25
+ init_blocks=2
26
26
  ),
27
27
  )
28
28
  ],
29
29
  run_dir="/tmp/test_htex",
30
30
  max_idletime=0.5,
31
- strategy='htex_auto_scale',
31
+ strategy='none',
32
32
  )
33
33
 
34
34
 
@@ -0,0 +1,45 @@
1
+ import queue
2
+ from unittest import mock
3
+
4
+ import pytest
5
+
6
+ from parsl.executors import HighThroughputExecutor
7
+ from parsl.executors.errors import InvalidResourceSpecification
8
+
9
+
10
+ def double(x):
11
+ return x * 2
12
+
13
+
14
+ @pytest.mark.local
15
+ def test_submit_calls_validate():
16
+
17
+ htex = HighThroughputExecutor()
18
+ htex.outgoing_q = mock.Mock(spec=queue.Queue)
19
+ htex.validate_resource_spec = mock.Mock(spec=htex.validate_resource_spec)
20
+
21
+ res_spec = {}
22
+ htex.submit(double, res_spec, (5,), {})
23
+ htex.validate_resource_spec.assert_called()
24
+
25
+
26
+ @pytest.mark.local
27
+ def test_resource_spec_validation():
28
+ htex = HighThroughputExecutor()
29
+ ret_val = htex.validate_resource_spec({})
30
+ assert ret_val is None
31
+
32
+
33
+ @pytest.mark.local
34
+ def test_resource_spec_validation_one_key():
35
+ htex = HighThroughputExecutor()
36
+ ret_val = htex.validate_resource_spec({"priority": 2})
37
+ assert ret_val is None
38
+
39
+
40
+ @pytest.mark.local
41
+ def test_resource_spec_validation_bad_keys():
42
+ htex = HighThroughputExecutor()
43
+
44
+ with pytest.raises(InvalidResourceSpecification):
45
+ htex.validate_resource_spec({"num_nodes": 2})
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import pathlib
2
3
  from typing import Optional
3
4
  from unittest import mock
@@ -8,6 +9,24 @@ import zmq
8
9
 
9
10
  from parsl import curvezmq
10
11
  from parsl.executors.high_throughput.interchange import Interchange
12
+ from parsl.executors.high_throughput.manager_selector import RandomManagerSelector
13
+
14
+
15
+ def make_interchange(*, interchange_address: Optional[str], cert_dir: Optional[str]) -> Interchange:
16
+ return Interchange(interchange_address=interchange_address,
17
+ cert_dir=cert_dir,
18
+ client_address="127.0.0.1",
19
+ client_ports=(50055, 50056, 50057),
20
+ worker_ports=None,
21
+ worker_port_range=(54000, 55000),
22
+ hub_address=None,
23
+ hub_zmq_port=None,
24
+ heartbeat_threshold=60,
25
+ logdir=".",
26
+ logging_level=logging.INFO,
27
+ manager_selector=RandomManagerSelector(),
28
+ poll_period=10,
29
+ run_id="test_run_id")
11
30
 
12
31
 
13
32
  @pytest.fixture
@@ -31,7 +50,7 @@ def test_interchange_curvezmq_sockets(
31
50
  mock_socket: mock.MagicMock, cert_dir: Optional[str], encrypted: bool
32
51
  ):
33
52
  address = "127.0.0.1"
34
- ix = Interchange(interchange_address=address, cert_dir=cert_dir)
53
+ ix = make_interchange(interchange_address=address, cert_dir=cert_dir)
35
54
  assert isinstance(ix.zmq_context, curvezmq.ServerContext)
36
55
  assert ix.zmq_context.encrypted is encrypted
37
56
  assert mock_socket.call_count == 5
@@ -40,7 +59,7 @@ def test_interchange_curvezmq_sockets(
40
59
  @pytest.mark.local
41
60
  @pytest.mark.parametrize("encrypted", (True, False), indirect=True)
42
61
  def test_interchange_binding_no_address(cert_dir: Optional[str]):
43
- ix = Interchange(cert_dir=cert_dir)
62
+ ix = make_interchange(interchange_address=None, cert_dir=cert_dir)
44
63
  assert ix.interchange_address == "*"
45
64
 
46
65
 
@@ -49,17 +68,18 @@ def test_interchange_binding_no_address(cert_dir: Optional[str]):
49
68
  def test_interchange_binding_with_address(cert_dir: Optional[str]):
50
69
  # Using loopback address
51
70
  address = "127.0.0.1"
52
- ix = Interchange(interchange_address=address, cert_dir=cert_dir)
71
+ ix = make_interchange(interchange_address=address, cert_dir=cert_dir)
53
72
  assert ix.interchange_address == address
54
73
 
55
74
 
75
+ @pytest.mark.skip("This behaviour is possibly unexpected. See issue #3037")
56
76
  @pytest.mark.local
57
77
  @pytest.mark.parametrize("encrypted", (True, False), indirect=True)
58
78
  def test_interchange_binding_with_non_ipv4_address(cert_dir: Optional[str]):
59
79
  # Confirm that a ipv4 address is required
60
80
  address = "localhost"
61
81
  with pytest.raises(zmq.error.ZMQError):
62
- Interchange(interchange_address=address, cert_dir=cert_dir)
82
+ make_interchange(interchange_address=address, cert_dir=cert_dir)
63
83
 
64
84
 
65
85
  @pytest.mark.local
@@ -67,8 +87,8 @@ def test_interchange_binding_with_non_ipv4_address(cert_dir: Optional[str]):
67
87
  def test_interchange_binding_bad_address(cert_dir: Optional[str]):
68
88
  """Confirm that we raise a ZMQError when a bad address is supplied"""
69
89
  address = "550.0.0.0"
70
- with pytest.raises(zmq.error.ZMQError):
71
- Interchange(interchange_address=address, cert_dir=cert_dir)
90
+ with pytest.raises(ValueError):
91
+ make_interchange(interchange_address=address, cert_dir=cert_dir)
72
92
 
73
93
 
74
94
  @pytest.mark.local
@@ -76,11 +96,12 @@ def test_interchange_binding_bad_address(cert_dir: Optional[str]):
76
96
  def test_limited_interface_binding(cert_dir: Optional[str]):
77
97
  """When address is specified the worker_port would be bound to it rather than to 0.0.0.0"""
78
98
  address = "127.0.0.1"
79
- ix = Interchange(interchange_address=address, cert_dir=cert_dir)
99
+ ix = make_interchange(interchange_address=address, cert_dir=cert_dir)
80
100
  ix.worker_result_port
81
101
  proc = psutil.Process()
82
102
  conns = proc.connections(kind="tcp")
83
103
 
84
104
  matched_conns = [conn for conn in conns if conn.laddr.port == ix.worker_result_port]
85
105
  assert len(matched_conns) == 1
86
- assert matched_conns[0].laddr.ip == address
106
+ # laddr.ip can return ::ffff:127.0.0.1 when using IPv6
107
+ assert address in matched_conns[0].laddr.ip
@@ -0,0 +1,86 @@
1
+ """Tests monitoring records app name under various decoration patterns.
2
+ """
3
+
4
+ import os
5
+ import time
6
+
7
+ import pytest
8
+
9
+ import parsl
10
+ from parsl.tests.configs.htex_local_alternate import fresh_config
11
+
12
+
13
+ @parsl.python_app
14
+ def regular_decorated_app():
15
+ return 5
16
+
17
+
18
+ @pytest.mark.local
19
+ def get_regular_decorated_app():
20
+ return regular_decorated_app
21
+
22
+
23
+ def for_decoration_later():
24
+ return 77
25
+
26
+
27
+ def get_for_decoration_later():
28
+ return parsl.python_app(for_decoration_later)
29
+
30
+
31
+ def get_decorated_closure():
32
+
33
+ r = 53
34
+
35
+ @parsl.python_app
36
+ def decorated_closure():
37
+ return r
38
+
39
+ return decorated_closure
40
+
41
+
42
+ @pytest.mark.local
43
+ @pytest.mark.parametrize("get_app,expected_name,expected_result",
44
+ [(get_regular_decorated_app, "regular_decorated_app", 5),
45
+ (get_for_decoration_later, "for_decoration_later", 77),
46
+ (get_decorated_closure, "decorated_closure", 53)
47
+ ])
48
+ def test_app_name(get_app, expected_name, expected_result, tmpd_cwd):
49
+
50
+ # this is imported here rather than at module level because
51
+ # it isn't available in a plain parsl install, so this module
52
+ # would otherwise fail to import and break even a basic test
53
+ # run.
54
+ import sqlalchemy
55
+
56
+ c = fresh_config()
57
+ c.run_dir = tmpd_cwd
58
+ c.monitoring.logging_endpoint = f"sqlite:///{tmpd_cwd}/monitoring.db"
59
+ parsl.load(c)
60
+
61
+ app = get_app()
62
+ assert app().result() == expected_result
63
+
64
+ parsl.dfk().cleanup()
65
+
66
+ engine = sqlalchemy.create_engine(c.monitoring.logging_endpoint)
67
+ with engine.begin() as connection:
68
+
69
+ def count_rows(table: str):
70
+ result = connection.execute(sqlalchemy.text(f"SELECT COUNT(*) FROM {table}"))
71
+ (c, ) = result.first()
72
+ return c
73
+
74
+ # one workflow...
75
+ assert count_rows("workflow") == 1
76
+
77
+ # ... with one task ...
78
+ assert count_rows("task") == 1
79
+
80
+ # ... that was tried once ...
81
+ assert count_rows("try") == 1
82
+
83
+ # ... and has the expected name.
84
+ result = connection.execute(sqlalchemy.text("SELECT task_func_name FROM task"))
85
+ (c, ) = result.first()
86
+ assert c == expected_name