parsl 2024.3.18__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 +26 -6
  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 +53 -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 +259 -223
  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 +307 -285
  57. parsl/executors/high_throughput/interchange.py +137 -168
  58. parsl/executors/high_throughput/manager_record.py +4 -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 +77 -75
  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 +38 -55
  75. parsl/executors/taskvine/factory.py +1 -1
  76. parsl/executors/taskvine/factory_config.py +1 -1
  77. parsl/executors/taskvine/manager.py +17 -13
  78. parsl/executors/taskvine/manager_config.py +7 -2
  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 +28 -112
  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 +0 -6
  91. parsl/log_utils.py +1 -2
  92. parsl/monitoring/db_manager.py +55 -93
  93. parsl/monitoring/errors.py +6 -0
  94. parsl/monitoring/monitoring.py +85 -311
  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 +8 -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 -9
  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 +3 -9
  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 +0 -7
  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 +11 -10
  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 +5 -5
  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 +5 -8
  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 +137 -4
  313. parsl/version.py +1 -1
  314. {parsl-2024.3.18.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.18.data → parsl-2025.1.13.data}/scripts/process_worker_pool.py +77 -75
  317. parsl-2025.1.13.dist-info/METADATA +96 -0
  318. parsl-2025.1.13.dist-info/RECORD +462 -0
  319. {parsl-2024.3.18.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.18.dist-info/METADATA +0 -98
  360. parsl-2024.3.18.dist-info/RECORD +0 -449
  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.18.data → parsl-2025.1.13.data}/scripts/parsl_coprocess.py +1 -1
  367. {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/LICENSE +0 -0
  368. {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/entry_points.txt +0 -0
  369. {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,33 @@
1
- import uuid
2
- import time
3
- import os
4
1
  import json
5
2
  import logging
3
+ import platform
6
4
  import socket
7
5
  import sys
8
- import platform
6
+ import time
7
+ import uuid
9
8
 
10
- from parsl.utils import setproctitle
11
- from parsl.multiprocessing import ForkProcess
12
9
  from parsl.dataflow.states import States
10
+ from parsl.errors import ConfigurationError
11
+ from parsl.multiprocessing import ForkProcess
12
+ from parsl.usage_tracking.api import get_parsl_usage
13
+ from parsl.usage_tracking.levels import DISABLED as USAGE_TRACKING_DISABLED
14
+ from parsl.usage_tracking.levels import LEVEL_3 as USAGE_TRACKING_LEVEL_3
15
+ from parsl.utils import setproctitle
13
16
  from parsl.version import VERSION as PARSL_VERSION
14
17
 
15
18
  logger = logging.getLogger(__name__)
16
19
 
17
20
  from typing import Callable
21
+
18
22
  from typing_extensions import ParamSpec
19
23
 
24
+ # protocol version byte: when (for example) compression parameters are changed
25
+ # that cannot be inferred from the compressed message itself, this version
26
+ # ID needs to imply those parameters.
27
+
28
+ # Earlier protocol versions: b'{' - the original pure-JSON protocol pre-March 2024
29
+ PROTOCOL_VERSION = b'1'
30
+
20
31
  P = ParamSpec("P")
21
32
 
22
33
 
@@ -32,7 +43,7 @@ def async_process(fn: Callable[P, None]) -> Callable[P, None]:
32
43
 
33
44
 
34
45
  @async_process
35
- def udp_messenger(domain_name: str, UDP_PORT: int, sock_timeout: int, message: str) -> None:
46
+ def udp_messenger(domain_name: str, UDP_PORT: int, sock_timeout: int, message: bytes) -> None:
36
47
  """Send UDP messages to usage tracker asynchronously
37
48
 
38
49
  This multiprocessing based messenger was written to overcome the limitations
@@ -46,16 +57,11 @@ def udp_messenger(domain_name: str, UDP_PORT: int, sock_timeout: int, message: s
46
57
  setproctitle("parsl: Usage tracking")
47
58
 
48
59
  try:
49
- encoded_message = bytes(message, "utf-8")
50
-
51
60
  UDP_IP = socket.gethostbyname(domain_name)
52
61
 
53
- if UDP_PORT is None:
54
- raise Exception("UDP_PORT is None")
55
-
56
62
  sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
57
63
  sock.settimeout(sock_timeout)
58
- sock.sendto(encoded_message, (UDP_IP, UDP_PORT))
64
+ sock.sendto(message, (UDP_IP, UDP_PORT))
59
65
  sock.close()
60
66
 
61
67
  except socket.timeout:
@@ -102,86 +108,119 @@ class UsageTracker:
102
108
  self.procs = []
103
109
  self.dfk = dfk
104
110
  self.config = self.dfk.config
105
- self.uuid = str(uuid.uuid4())
111
+ self.correlator_uuid = str(uuid.uuid4())
106
112
  self.parsl_version = PARSL_VERSION
107
113
  self.python_version = "{}.{}.{}".format(sys.version_info.major,
108
114
  sys.version_info.minor,
109
115
  sys.version_info.micro)
110
- self.tracking_enabled = self.check_tracking_enabled()
111
- logger.debug("Tracking status: {}".format(self.tracking_enabled))
112
-
113
- def check_tracking_enabled(self):
114
- """Check if tracking is enabled.
115
-
116
- Tracking will be enabled unless either of these is true:
117
-
118
- 1. dfk.config.usage_tracking is set to False
119
- 2. Environment variable PARSL_TRACKING is set to false (case insensitive)
120
-
116
+ self.tracking_level = self.check_tracking_level()
117
+ self.project_name = self.config.project_name
118
+ self.start_time = None
119
+ logger.debug("Tracking level: {}".format(self.tracking_level))
120
+
121
+ def check_tracking_level(self) -> int:
122
+ """Check if tracking is enabled and return level.
123
+
124
+ Checks usage_tracking in Config
125
+ - Possible values: [True, False, 0, 1, 2, 3]
126
+
127
+ True/False values are treated as Level 1/Level 0 respectively.
128
+
129
+ Returns: int
130
+ - 0 : Tracking is disabled
131
+ - 1 : Tracking is enabled with level 1
132
+ Share info about Parsl version, Python version, platform
133
+ - 2 : Tracking is enabled with level 2
134
+ Share info about config + level 1
135
+ - 3 : Tracking is enabled with level 3
136
+ Share info about app count, app fails, execution time + level 2
121
137
  """
122
- track = True
123
-
124
- if not self.config.usage_tracking:
125
- track = False
138
+ if not USAGE_TRACKING_DISABLED <= self.config.usage_tracking <= USAGE_TRACKING_LEVEL_3:
139
+ raise ConfigurationError(
140
+ f"Usage Tracking values must be 0, 1, 2, or 3 and not {self.config.usage_tracking}"
141
+ )
126
142
 
127
- envvar = str(os.environ.get("PARSL_TRACKING", True)).lower()
128
- if envvar == "false":
129
- track = False
143
+ return self.config.usage_tracking
130
144
 
131
- return track
132
-
133
- def construct_start_message(self) -> str:
145
+ def construct_start_message(self) -> bytes:
134
146
  """Collect preliminary run info at the start of the DFK.
135
147
 
136
148
  Returns :
137
149
  - Message dict dumped as json string, ready for UDP
138
150
  """
139
- message = {'uuid': self.uuid,
151
+ message = {'correlator': self.correlator_uuid,
140
152
  'parsl_v': self.parsl_version,
141
153
  'python_v': self.python_version,
142
- 'os': platform.system(),
143
- 'os_v': platform.release(),
144
- 'start': time.time()}
154
+ 'platform.system': platform.system(),
155
+ 'tracking_level': int(self.tracking_level)}
156
+
157
+ if self.project_name:
158
+ message['project_name'] = self.project_name
159
+
160
+ if self.tracking_level >= 2:
161
+ message['components'] = get_parsl_usage(self.dfk._config)
162
+
163
+ if self.tracking_level == 3:
164
+ self.start_time = int(time.time())
165
+ message['start'] = self.start_time
145
166
 
146
- return json.dumps(message)
167
+ logger.debug(f"Usage tracking start message: {message}")
147
168
 
148
- def construct_end_message(self) -> str:
169
+ return self.encode_message(message)
170
+
171
+ def construct_end_message(self) -> bytes:
149
172
  """Collect the final run information at the time of DFK cleanup.
173
+ This is only called if tracking level is 3.
150
174
 
151
175
  Returns:
152
176
  - Message dict dumped as json string, ready for UDP
153
177
  """
154
- app_count = self.dfk.task_count
178
+ end_time = int(time.time())
155
179
 
156
- site_count = len(self.dfk.config.executors)
180
+ app_count = self.dfk.task_count
157
181
 
158
182
  app_fails = self.dfk.task_state_counts[States.failed] + self.dfk.task_state_counts[States.dep_fail]
159
183
 
160
- message = {'uuid': self.uuid,
161
- 'end': time.time(),
162
- 't_apps': app_count,
163
- 'sites': site_count,
164
- 'failed': app_fails
165
- }
184
+ # the DFK is tangled into this code as a god-object, so it is
185
+ # handled separately from the usual traversal code, but presenting
186
+ # the same protocol-level report.
187
+ dfk_component = {'c': type(self.dfk).__module__ + "." + type(self.dfk).__name__,
188
+ 'app_count': app_count,
189
+ 'app_fails': app_fails}
190
+
191
+ message = {'correlator': self.correlator_uuid,
192
+ 'end': end_time,
193
+ 'execution_time': end_time - self.start_time,
194
+ 'components': [dfk_component] + get_parsl_usage(self.dfk._config)}
166
195
 
167
- return json.dumps(message)
196
+ if self.project_name:
197
+ message['project_name'] = self.project_name
168
198
 
169
- def send_UDP_message(self, message: str) -> None:
199
+ logger.debug(f"Usage tracking end message (unencoded): {message}")
200
+
201
+ return self.encode_message(message)
202
+
203
+ def encode_message(self, obj):
204
+ return PROTOCOL_VERSION + json.dumps(obj).encode()
205
+
206
+ def send_UDP_message(self, message: bytes) -> None:
170
207
  """Send UDP message."""
171
- if self.tracking_enabled:
172
- try:
173
- proc = udp_messenger(self.domain_name, self.UDP_PORT, self.sock_timeout, message)
174
- self.procs.append(proc)
175
- except Exception as e:
176
- logger.debug("Usage tracking failed: {}".format(e))
208
+ try:
209
+ proc = udp_messenger(self.domain_name, self.UDP_PORT, self.sock_timeout, message)
210
+ self.procs.append(proc)
211
+ except Exception as e:
212
+ logger.debug("Usage tracking failed: {}".format(e))
177
213
 
178
214
  def send_start_message(self) -> None:
179
- message = self.construct_start_message()
180
- self.send_UDP_message(message)
215
+ if self.tracking_level:
216
+ self.start_time = time.time()
217
+ message = self.construct_start_message()
218
+ self.send_UDP_message(message)
181
219
 
182
220
  def send_end_message(self) -> None:
183
- message = self.construct_end_message()
184
- self.send_UDP_message(message)
221
+ if self.tracking_level == 3:
222
+ message = self.construct_end_message()
223
+ self.send_UDP_message(message)
185
224
 
186
225
  def close(self, timeout: float = 10.0) -> None:
187
226
  """First give each process one timeout period to finish what it is
@@ -191,7 +230,10 @@ class UsageTracker:
191
230
  or won't respond to SIGTERM.
192
231
  """
193
232
  for proc in self.procs:
233
+ logger.debug("Joining usage tracking process %s", proc)
194
234
  proc.join(timeout=timeout)
195
235
  if proc.is_alive():
196
- logger.info("Usage tracking process did not end itself; sending SIGKILL")
236
+ logger.warning("Usage tracking process did not end itself; sending SIGKILL")
197
237
  proc.kill()
238
+
239
+ proc.close()
parsl/utils.py CHANGED
@@ -1,21 +1,34 @@
1
1
  import inspect
2
2
  import logging
3
3
  import os
4
+ import re
4
5
  import shlex
5
6
  import subprocess
6
7
  import threading
7
8
  import time
8
9
  from contextlib import contextmanager
9
10
  from types import TracebackType
10
- from typing import Any, Callable, List, Sequence, Tuple, Union, Generator, IO, AnyStr, Dict, Optional
11
+ from typing import (
12
+ IO,
13
+ Any,
14
+ AnyStr,
15
+ Callable,
16
+ Dict,
17
+ Generator,
18
+ List,
19
+ Optional,
20
+ Sequence,
21
+ Tuple,
22
+ Union,
23
+ )
11
24
 
12
25
  import typeguard
13
26
  from typing_extensions import Type
14
27
 
15
28
  import parsl
29
+ from parsl.app.errors import BadStdStreamFile
16
30
  from parsl.version import VERSION
17
31
 
18
-
19
32
  try:
20
33
  import setproctitle as setproctitle_module
21
34
  except ImportError:
@@ -121,9 +134,17 @@ def get_std_fname_mode(
121
134
  if len(stdfspec) != 2:
122
135
  msg = (f"std descriptor {fdname} has incorrect tuple length "
123
136
  f"{len(stdfspec)}")
124
- raise pe.BadStdStreamFile(msg, TypeError('Bad Tuple Length'))
137
+ raise pe.BadStdStreamFile(msg)
125
138
  fname, mode = stdfspec
126
- return str(fname), mode
139
+
140
+ path = os.fspath(fname)
141
+
142
+ if isinstance(path, str):
143
+ return path, mode
144
+ elif isinstance(path, bytes):
145
+ return path.decode(), mode
146
+ else:
147
+ raise BadStdStreamFile(f"fname has invalid type {type(path)}")
127
148
 
128
149
 
129
150
  @contextmanager
@@ -360,3 +381,115 @@ class AutoCancelTimer(threading.Timer):
360
381
  exc_tb: Optional[TracebackType]
361
382
  ) -> None:
362
383
  self.cancel()
384
+
385
+
386
+ def sanitize_dns_label_rfc1123(raw_string: str) -> str:
387
+ """Convert input string to a valid RFC 1123 DNS label.
388
+
389
+ Parameters
390
+ ----------
391
+ raw_string : str
392
+ String to sanitize.
393
+
394
+ Returns
395
+ -------
396
+ str
397
+ Sanitized string.
398
+
399
+ Raises
400
+ ------
401
+ ValueError
402
+ If the string is empty after sanitization.
403
+ """
404
+ # Convert to lowercase and replace non-alphanumeric characters with hyphen
405
+ sanitized = re.sub(r'[^a-z0-9]', '-', raw_string.lower())
406
+
407
+ # Remove consecutive hyphens
408
+ sanitized = re.sub(r'-+', '-', sanitized)
409
+
410
+ # DNS label cannot exceed 63 characters
411
+ sanitized = sanitized[:63]
412
+
413
+ # Strip after trimming to avoid trailing hyphens
414
+ sanitized = sanitized.strip("-")
415
+
416
+ if not sanitized:
417
+ raise ValueError(f"Sanitized DNS label is empty for input '{raw_string}'")
418
+
419
+ return sanitized
420
+
421
+
422
+ def sanitize_dns_subdomain_rfc1123(raw_string: str) -> str:
423
+ """Convert input string to a valid RFC 1123 DNS subdomain.
424
+
425
+ Parameters
426
+ ----------
427
+ raw_string : str
428
+ String to sanitize.
429
+
430
+ Returns
431
+ -------
432
+ str
433
+ Sanitized string.
434
+
435
+ Raises
436
+ ------
437
+ ValueError
438
+ If the string is empty after sanitization.
439
+ """
440
+ segments = raw_string.split('.')
441
+
442
+ sanitized_segments = []
443
+ for segment in segments:
444
+ if not segment:
445
+ continue
446
+ sanitized_segment = sanitize_dns_label_rfc1123(segment)
447
+ sanitized_segments.append(sanitized_segment)
448
+
449
+ sanitized = '.'.join(sanitized_segments)
450
+
451
+ # DNS subdomain cannot exceed 253 characters
452
+ sanitized = sanitized[:253]
453
+
454
+ # Strip after trimming to avoid trailing dots or hyphens
455
+ sanitized = sanitized.strip(".-")
456
+
457
+ if not sanitized:
458
+ raise ValueError(f"Sanitized DNS subdomain is empty for input '{raw_string}'")
459
+
460
+ return sanitized
461
+
462
+
463
+ def execute_wait(cmd: str, walltime: Optional[int] = None) -> Tuple[int, str, str]:
464
+ ''' Synchronously execute a commandline string on the shell.
465
+
466
+ Args:
467
+ - cmd (string) : Commandline string to execute
468
+ - walltime (int) : walltime in seconds
469
+
470
+ Returns:
471
+ - retcode : Return code from the execution
472
+ - stdout : stdout string
473
+ - stderr : stderr string
474
+ '''
475
+ try:
476
+ logger.debug("Creating process with command '%s'", cmd)
477
+ proc = subprocess.Popen(
478
+ cmd,
479
+ stdout=subprocess.PIPE,
480
+ stderr=subprocess.PIPE,
481
+ shell=True,
482
+ preexec_fn=os.setpgrp
483
+ )
484
+ logger.debug("Created process with pid %s. Performing communicate", proc.pid)
485
+ (stdout, stderr) = proc.communicate(timeout=walltime)
486
+ retcode = proc.returncode
487
+ logger.debug("Process %s returned %s", proc.pid, proc.returncode)
488
+
489
+ except Exception:
490
+ logger.exception(f"Execution of command failed:\n{cmd}")
491
+ raise
492
+ else:
493
+ logger.debug("Execution of command in process %s completed normally", proc.pid)
494
+
495
+ return (retcode, stdout.decode("utf-8"), stderr.decode("utf-8"))
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.03.18'
6
+ VERSION = '2025.01.13'
@@ -1,10 +1,11 @@
1
+ import pickle
2
+ import sys
3
+ import traceback
4
+
1
5
  from parsl.app.errors import RemoteExceptionWrapper
2
6
  from parsl.data_provider.files import File
3
- from parsl.utils import get_std_fname_mode
4
- import traceback
5
- import sys
6
- import pickle
7
7
  from parsl.serialize import serialize
8
+ from parsl.utils import get_std_fname_mode
8
9
 
9
10
  # This scripts executes a parsl function which is pickled in a file:
10
11
  #
@@ -93,7 +94,7 @@ def unpack_source_code_function(function_info, user_namespace):
93
94
 
94
95
  def unpack_byte_code_function(function_info, user_namespace):
95
96
  from parsl.serialize import unpack_apply_message
96
- func, args, kwargs = unpack_apply_message(function_info["byte code"], user_namespace, copy=False)
97
+ func, args, kwargs = unpack_apply_message(function_info["byte code"])
97
98
  return (func, 'parsl_function_name', args, kwargs)
98
99
 
99
100