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,41 +1,36 @@
1
1
  import contextlib
2
2
  import logging
3
3
  import os
4
+ import queue
4
5
  import typing
5
-
6
+ import unittest
7
+ from typing import Dict
8
+ from unittest import mock
6
9
 
7
10
  import pytest
8
- import unittest
9
11
 
10
- import parsl
11
12
  from parsl.app.app import python_app
12
- from parsl.tests.configs.htex_local import fresh_config
13
- from typing import Dict
13
+ from parsl.executors.errors import InvalidResourceSpecification
14
+ from parsl.executors.high_throughput.executor import HighThroughputExecutor
15
+ from parsl.executors.high_throughput.mpi_executor import MPIExecutor
14
16
  from parsl.executors.high_throughput.mpi_resource_management import (
17
+ get_nodes_in_batchjob,
15
18
  get_pbs_hosts_list,
16
19
  get_slurm_hosts_list,
17
- get_nodes_in_batchjob,
18
20
  identify_scheduler,
19
21
  )
20
- from parsl.executors.high_throughput.mpi_prefix_composer import (
21
- validate_resource_spec,
22
- InvalidResourceSpecification
23
- )
22
+ from parsl.launchers import SimpleLauncher
23
+ from parsl.providers import LocalProvider
24
+ from parsl.tests.configs.htex_local import fresh_config
24
25
 
25
26
  EXECUTOR_LABEL = "MPI_TEST"
26
27
 
27
28
 
28
- def local_setup():
29
+ def local_config():
29
30
  config = fresh_config()
30
31
  config.executors[0].label = EXECUTOR_LABEL
31
32
  config.executors[0].max_workers_per_node = 1
32
- parsl.load(config)
33
-
34
-
35
- def local_teardown():
36
- logging.warning("Exiting")
37
- parsl.dfk().cleanup()
38
- parsl.clear()
33
+ return config
39
34
 
40
35
 
41
36
  @python_app
@@ -54,23 +49,6 @@ def get_env_vars(parsl_resource_specification: Dict = {}) -> Dict:
54
49
  return parsl_vars
55
50
 
56
51
 
57
- @pytest.mark.local
58
- def test_resource_spec_env_vars():
59
- resource_spec = {
60
- "num_nodes": 4,
61
- "ranks_per_node": 2,
62
- }
63
-
64
- assert double(5).result() == 10
65
-
66
- future = get_env_vars(parsl_resource_specification=resource_spec)
67
-
68
- result = future.result()
69
- assert isinstance(result, Dict)
70
- assert result["PARSL_NUM_NODES"] == str(resource_spec["num_nodes"])
71
- assert result["PARSL_RANKS_PER_NODE"] == str(resource_spec["ranks_per_node"])
72
-
73
-
74
52
  @pytest.mark.local
75
53
  @unittest.mock.patch("subprocess.check_output", return_value=b"c203-031\nc203-032\n")
76
54
  def test_slurm_mocked_mpi_fetch(subprocess_check):
@@ -89,16 +67,6 @@ def add_to_path(path: os.PathLike) -> typing.Generator[None, None, None]:
89
67
  os.environ["PATH"] = old_path
90
68
 
91
69
 
92
- @pytest.mark.local
93
- @pytest.mark.skip
94
- def test_slurm_mpi_fetch():
95
- logging.warning(f"Current pwd : {os.path.dirname(__file__)}")
96
- with add_to_path(os.path.dirname(__file__)):
97
- logging.warning(f"PATH: {os.environ['PATH']}")
98
- nodeinfo = get_slurm_hosts_list()
99
- logging.warning(f"Got : {nodeinfo}")
100
-
101
-
102
70
  @contextlib.contextmanager
103
71
  def mock_pbs_nodefile(nodefile: str = "pbs_nodefile") -> typing.Generator[None, None, None]:
104
72
  cwd = os.path.abspath(os.path.dirname(__file__))
@@ -130,16 +98,41 @@ def test_top_level():
130
98
  @pytest.mark.parametrize(
131
99
  "resource_spec, exception",
132
100
  (
101
+
133
102
  ({"num_nodes": 2, "ranks_per_node": 1}, None),
134
103
  ({"launcher_options": "--debug_foo"}, None),
135
104
  ({"num_nodes": 2, "BAD_OPT": 1}, InvalidResourceSpecification),
136
- ({}, None),
105
+ ({}, InvalidResourceSpecification),
137
106
  )
138
107
  )
139
- def test_resource_spec(resource_spec: Dict, exception):
108
+ def test_mpi_resource_spec(resource_spec: Dict, exception):
109
+ """Test validation of resource_specification in MPIExecutor"""
110
+
111
+ mpi_ex = MPIExecutor(provider=LocalProvider(launcher=SimpleLauncher()))
112
+ mpi_ex.outgoing_q = mock.Mock(spec=queue.Queue)
113
+
140
114
  if exception:
141
115
  with pytest.raises(exception):
142
- validate_resource_spec(resource_spec)
116
+ mpi_ex.validate_resource_spec(resource_spec)
143
117
  else:
144
- result = validate_resource_spec(resource_spec)
118
+ result = mpi_ex.validate_resource_spec(resource_spec)
145
119
  assert result is None
120
+
121
+
122
+ @pytest.mark.local
123
+ @pytest.mark.parametrize(
124
+ "resource_spec",
125
+ (
126
+ {"num_nodes": 2, "ranks_per_node": 1},
127
+ {"launcher_options": "--debug_foo"},
128
+ {"BAD_OPT": 1},
129
+ )
130
+ )
131
+ def test_mpi_resource_spec_passed_to_htex(resource_spec: dict):
132
+ """HTEX should reject every resource_spec"""
133
+
134
+ htex = HighThroughputExecutor()
135
+ htex.outgoing_q = mock.Mock(spec=queue.Queue)
136
+
137
+ with pytest.raises(InvalidResourceSpecification):
138
+ htex.validate_resource_spec(resource_spec)
@@ -0,0 +1,102 @@
1
+ import re
2
+ from unittest import mock
3
+
4
+ import pytest
5
+
6
+ from parsl.providers.kubernetes.kube import KubernetesProvider
7
+ from parsl.tests.test_utils.test_sanitize_dns import DNS_SUBDOMAIN_REGEX
8
+
9
+ _MOCK_BASE = "parsl.providers.kubernetes.kube"
10
+
11
+
12
+ @pytest.fixture(autouse=True)
13
+ def mock_kube_config():
14
+ with mock.patch(f"{_MOCK_BASE}.config") as mock_config:
15
+ mock_config.load_kube_config.return_value = None
16
+ yield mock_config
17
+
18
+
19
+ @pytest.fixture
20
+ def mock_kube_client():
21
+ mock_client = mock.MagicMock()
22
+ with mock.patch(f"{_MOCK_BASE}.client.CoreV1Api") as mock_api:
23
+ mock_api.return_value = mock_client
24
+ yield mock_client
25
+
26
+
27
+ @pytest.mark.local
28
+ def test_submit_happy_path(mock_kube_client: mock.MagicMock):
29
+ image = "test-image"
30
+ namespace = "test-namespace"
31
+ cmd_string = "test-command"
32
+ volumes = [("test-volume", "test-mount-path")]
33
+ service_account_name = "test-service-account"
34
+ annotations = {"test-annotation": "test-value"}
35
+ max_cpu = 2
36
+ max_mem = "2Gi"
37
+ init_cpu = 1
38
+ init_mem = "1Gi"
39
+ provider = KubernetesProvider(
40
+ image=image,
41
+ persistent_volumes=volumes,
42
+ namespace=namespace,
43
+ service_account_name=service_account_name,
44
+ annotations=annotations,
45
+ max_cpu=max_cpu,
46
+ max_mem=max_mem,
47
+ init_cpu=init_cpu,
48
+ init_mem=init_mem,
49
+ )
50
+
51
+ job_name = "test.job.name"
52
+ job_id = provider.submit(cmd_string=cmd_string, tasks_per_node=1, job_name=job_name)
53
+
54
+ assert job_id in provider.resources
55
+ assert mock_kube_client.create_namespaced_pod.call_count == 1
56
+
57
+ call_args = mock_kube_client.create_namespaced_pod.call_args[1]
58
+ pod = call_args["body"]
59
+ container = pod.spec.containers[0]
60
+ volume = container.volume_mounts[0]
61
+
62
+ assert image == container.image
63
+ assert namespace == call_args["namespace"]
64
+ assert any(cmd_string in arg for arg in container.args)
65
+ assert volumes[0] == (volume.name, volume.mount_path)
66
+ assert service_account_name == pod.spec.service_account_name
67
+ assert annotations == pod.metadata.annotations
68
+ assert str(max_cpu) == container.resources.limits["cpu"]
69
+ assert max_mem == container.resources.limits["memory"]
70
+ assert str(init_cpu) == container.resources.requests["cpu"]
71
+ assert init_mem == container.resources.requests["memory"]
72
+ assert job_id == pod.metadata.labels["parsl-job-id"]
73
+ assert job_id == container.name
74
+ assert f"{job_name}.{job_id}" == pod.metadata.name
75
+
76
+
77
+ @pytest.mark.local
78
+ @mock.patch(f"{_MOCK_BASE}.KubernetesProvider._create_pod")
79
+ @pytest.mark.parametrize("char", (".", "-"))
80
+ def test_submit_pod_name_includes_job_id(mock_create_pod: mock.MagicMock, char: str):
81
+ provider = KubernetesProvider(image="test-image")
82
+
83
+ job_name = "a." * 121 + f"a{char}" + "a" * 9
84
+ assert len(job_name) == 253 # Max length for pod name
85
+ job_id = provider.submit(cmd_string="test-command", tasks_per_node=1, job_name=job_name)
86
+
87
+ expected_pod_name = job_name[:253 - len(job_id) - 2] + f".{job_id}"
88
+ actual_pod_name = mock_create_pod.call_args[1]["pod_name"]
89
+ assert re.match(DNS_SUBDOMAIN_REGEX, actual_pod_name)
90
+ assert expected_pod_name == actual_pod_name
91
+
92
+
93
+ @pytest.mark.local
94
+ @mock.patch(f"{_MOCK_BASE}.KubernetesProvider._create_pod")
95
+ @mock.patch(f"{_MOCK_BASE}.logger")
96
+ @pytest.mark.parametrize("job_name", ("", ".", "-", "a.-.a", "$$$"))
97
+ def test_submit_invalid_job_name(mock_logger: mock.MagicMock, mock_create_pod: mock.MagicMock, job_name: str):
98
+ provider = KubernetesProvider(image="test-image")
99
+ job_id = provider.submit(cmd_string="test-command", tasks_per_node=1, job_name=job_name)
100
+ assert mock_logger.warning.call_count == 1
101
+ assert f"Invalid pod name '{job_name}' for job '{job_id}'" in mock_logger.warning.call_args[0][0]
102
+ assert f"parsl.kube.{job_id}" == mock_create_pod.call_args[1]["pod_name"]
@@ -1,7 +1,6 @@
1
1
  import logging
2
2
  import os
3
3
  import pathlib
4
- import pytest
5
4
  import random
6
5
  import shutil
7
6
  import socket
@@ -10,7 +9,8 @@ import tempfile
10
9
  import threading
11
10
  import time
12
11
 
13
- from parsl.channels import LocalChannel, SSHChannel
12
+ import pytest
13
+
14
14
  from parsl.jobs.states import JobState
15
15
  from parsl.launchers import SingleNodeLauncher
16
16
  from parsl.providers import LocalProvider
@@ -62,140 +62,11 @@ def _run_tests(p: LocalProvider):
62
62
  def test_local_channel():
63
63
  with tempfile.TemporaryDirectory() as script_dir:
64
64
  script_dir = tempfile.mkdtemp()
65
- p = LocalProvider(channel=LocalChannel(), launcher=SingleNodeLauncher(debug=False))
65
+ p = LocalProvider(launcher=SingleNodeLauncher(debug=False))
66
66
  p.script_dir = script_dir
67
67
  _run_tests(p)
68
68
 
69
69
 
70
- SSHD_CONFIG = """
71
- Port {port}
72
- ListenAddress 127.0.0.1
73
- HostKey {hostkey}
74
- AuthorizedKeysFile {connpubkey}
75
- AuthenticationMethods publickey
76
- StrictModes no
77
- Subsystem sftp {sftp_path}
78
- """
79
-
80
-
81
- # It would probably be better, when more formalized site testing comes into existence, to
82
- # use a site-testing provided server/configuration instead of the current scheme
83
- @pytest.mark.local
84
- @pytest.mark.sshd_required
85
- def test_ssh_channel():
86
- with tempfile.TemporaryDirectory() as config_dir:
87
- sshd_thread, priv_key, server_port = _start_sshd(config_dir)
88
- try:
89
- with tempfile.TemporaryDirectory() as remote_script_dir:
90
- # The SSH library fails to add the new host key to the file if the file does not
91
- # already exist, so create it here.
92
- pathlib.Path('{}/known.hosts'.format(config_dir)).touch(mode=0o600)
93
- script_dir = tempfile.mkdtemp()
94
- p = LocalProvider(channel=SSHChannel('127.0.0.1', port=server_port,
95
- script_dir=remote_script_dir,
96
- host_keys_filename='{}/known.hosts'.format(config_dir),
97
- key_filename=priv_key),
98
- launcher=SingleNodeLauncher(debug=False))
99
- p.script_dir = script_dir
100
- _run_tests(p)
101
- finally:
102
- _stop_sshd(sshd_thread)
103
-
104
-
105
- def _stop_sshd(sshd_thread):
106
- sshd_thread.stop()
107
-
108
-
109
- class SSHDThread(threading.Thread):
110
- def __init__(self, config_file):
111
- threading.Thread.__init__(self, daemon=True)
112
- self.config_file = config_file
113
- self.stop_flag = False
114
- self.error = None
115
-
116
- def run(self):
117
- try:
118
- # sshd needs to be run with an absolute path, hence the call to which()
119
- sshpath = shutil.which('sshd')
120
- assert sshpath is not None, "can find sshd executable"
121
- p = subprocess.Popen([sshpath, '-D', '-f', self.config_file],
122
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
123
- while True:
124
- ec = p.poll()
125
- if self.stop_flag:
126
- p.terminate()
127
- break
128
- elif ec is None:
129
- time.sleep(0.1)
130
- elif ec == 0:
131
- self.error = Exception('sshd exited prematurely: {}{}'.format(p.stdout.read(),
132
- p.stderr.read()))
133
- break
134
- else:
135
- self.error = Exception('sshd failed: {}{}'.format(p.stdout.read(),
136
- p.stderr.read()))
137
- break
138
- except Exception as ex:
139
- logger.exception("SSHDThread exception from run loop")
140
- self.error = ex
141
-
142
- def stop(self):
143
- self.stop_flag = True
144
-
145
-
146
- def _start_sshd(config_dir: str):
147
- server_config, priv_key, port = _init_sshd(config_dir)
148
- sshd_thread = SSHDThread(server_config)
149
- sshd_thread.start()
150
- time.sleep(1.0)
151
- if not sshd_thread.is_alive():
152
- raise Exception('Failed to start sshd: {}'.format(sshd_thread.error))
153
- return sshd_thread, priv_key, port
154
-
155
-
156
- def _init_sshd(config_dir):
157
- hostkey = '{}/hostkey'.format(config_dir)
158
- connkey = '{}/connkey'.format(config_dir)
159
- os.system('ssh-keygen -b 2048 -t rsa -q -N "" -f {}'.format(hostkey))
160
- os.system('ssh-keygen -b 2048 -t rsa -q -N "" -f {}'.format(connkey))
161
- port = _find_free_port(22222)
162
- server_config_str = SSHD_CONFIG.format(port=port, hostkey=hostkey,
163
- connpubkey='{}.pub'.format(connkey),
164
- sftp_path=_get_system_sftp_path())
165
- server_config = '{}/sshd_config'.format(config_dir)
166
- with open(server_config, 'w') as f:
167
- f.write(server_config_str)
168
- return server_config, connkey, port
169
-
170
-
171
- def _get_system_sftp_path():
172
- try:
173
- with open('/etc/ssh/sshd_config') as f:
174
- line = f.readline()
175
- while line:
176
- tokens = line.split()
177
- if tokens[0] == 'Subsystem' and tokens[1] == 'sftp':
178
- return tokens[2]
179
- line = f.readline()
180
- except Exception:
181
- pass
182
- return '/usr/lib/openssh/sftp-server'
183
-
184
-
185
- def _find_free_port(start: int):
186
- port = start
187
- while port < 65535:
188
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
189
- try:
190
- s.bind(('127.0.0.1', port))
191
- s.close()
192
- return port
193
- except Exception:
194
- pass
195
- port += random.randint(1, 20)
196
- raise Exception('Could not find free port')
197
-
198
-
199
70
  def _run(p: LocalProvider, command: str, np: int = 1):
200
71
  id = p.submit(command, np)
201
72
  return _wait(p, id)
@@ -1,9 +1,8 @@
1
1
  import random
2
-
3
2
  from unittest import mock
3
+
4
4
  import pytest
5
5
 
6
- from parsl.channels import LocalChannel
7
6
  from parsl.providers import PBSProProvider
8
7
 
9
8
 
@@ -12,7 +11,7 @@ def test_submit_script_basic(tmp_path):
12
11
  """Test slurm resources table"""
13
12
 
14
13
  provider = PBSProProvider(
15
- queue="debug", channel=LocalChannel(script_dir=tmp_path)
14
+ queue="debug"
16
15
  )
17
16
  provider.script_dir = tmp_path
18
17
  job_id = str(random.randint(55000, 59000))
@@ -1,10 +1,9 @@
1
1
  import logging
2
2
  import random
3
-
4
3
  from unittest import mock
4
+
5
5
  import pytest
6
6
 
7
- from parsl.channels import LocalChannel
8
7
  from parsl.providers import SlurmProvider
9
8
 
10
9
 
@@ -13,7 +12,7 @@ def test_submit_script_basic(tmp_path):
13
12
  """Test slurm resources table"""
14
13
 
15
14
  provider = SlurmProvider(
16
- partition="debug", channel=LocalChannel(script_dir=tmp_path)
15
+ partition="debug"
17
16
  )
18
17
  provider.script_dir = tmp_path
19
18
  job_id = str(random.randint(55000, 59000))
@@ -1,7 +1,8 @@
1
- import pytest
2
1
  import random
3
2
  import string
4
3
 
4
+ import pytest
5
+
5
6
  from parsl.providers.errors import SubmitException
6
7
 
7
8
 
@@ -0,0 +1,128 @@
1
+ from concurrent.futures import Future
2
+ from threading import Event
3
+
4
+ import pytest
5
+
6
+ import parsl
7
+ from parsl.config import Config
8
+ from parsl.dataflow.dflow import DataFlowKernel, DataFlowKernelLoader
9
+ from parsl.errors import NoDataFlowKernelError
10
+ from parsl.tests.configs.local_threads import fresh_config
11
+
12
+
13
+ @parsl.python_app
14
+ def square(x):
15
+ return x * x
16
+
17
+
18
+ @parsl.bash_app
19
+ def foo(x, stdout='foo.stdout'):
20
+ return f"echo {x + 1}"
21
+
22
+
23
+ @parsl.python_app
24
+ def wait_for_event(ev: Event):
25
+ ev.wait()
26
+
27
+
28
+ @parsl.python_app
29
+ def raise_app():
30
+ raise RuntimeError("raise_app deliberate failure")
31
+
32
+
33
+ @pytest.mark.local
34
+ def test_within_context_manger(tmpd_cwd):
35
+ config = fresh_config()
36
+ with parsl.load(config=config) as dfk:
37
+ assert isinstance(dfk, DataFlowKernel)
38
+
39
+ bash_future = foo(1, stdout=tmpd_cwd / 'foo.stdout')
40
+ assert bash_future.result() == 0
41
+
42
+ with open(tmpd_cwd / 'foo.stdout', 'r') as f:
43
+ assert f.read() == "2\n"
44
+
45
+ with pytest.raises(NoDataFlowKernelError) as excinfo:
46
+ square(2).result()
47
+ assert str(excinfo.value) == "Must first load config"
48
+
49
+
50
+ @pytest.mark.local
51
+ def test_exit_skip():
52
+ config = fresh_config()
53
+ config.exit_mode = "skip"
54
+
55
+ with parsl.load(config) as dfk:
56
+ ev = Event()
57
+ fut = wait_for_event(ev)
58
+ # deliberately don't wait for this to finish, so that the context
59
+ # manager can exit
60
+
61
+ assert parsl.dfk() is dfk, "global dfk should be left in place by skip mode"
62
+
63
+ assert not fut.done(), "wait_for_event should not be done yet"
64
+ ev.set()
65
+
66
+ # now we can wait for that result...
67
+ fut.result()
68
+ assert fut.done(), "wait_for_event should complete outside of context manager in 'skip' mode"
69
+
70
+ # now cleanup the DFK that the above `with` block
71
+ # deliberately avoided doing...
72
+ dfk.cleanup()
73
+
74
+
75
+ # 'wait' mode has two cases to test:
76
+ # 1. that we wait when there is no exception
77
+ # 2. that we do not wait when there is an exception
78
+ @pytest.mark.local
79
+ def test_exit_wait_no_exception():
80
+ config = fresh_config()
81
+ config.exit_mode = "wait"
82
+
83
+ with parsl.load(config) as dfk:
84
+ fut = square(1)
85
+ # deliberately don't wait for this to finish, so that the context
86
+ # manager can exit
87
+
88
+ assert fut.done(), "This future should be marked as done before the context manager exits"
89
+
90
+ assert dfk.cleanup_called, "The DFK should have been cleaned up by the context manager"
91
+ assert DataFlowKernelLoader._dfk is None, "The global DFK should have been removed"
92
+
93
+
94
+ @pytest.mark.local
95
+ def test_exit_wait_exception():
96
+ config = fresh_config()
97
+ config.exit_mode = "wait"
98
+
99
+ with pytest.raises(RuntimeError):
100
+ with parsl.load(config) as dfk:
101
+ # we'll never fire this future
102
+ fut_never = Future()
103
+
104
+ fut_raise = raise_app()
105
+
106
+ fut_depend = square(fut_never)
107
+
108
+ # this should cause an exception, which should cause the context
109
+ # manager to exit, without waiting for fut_depend to finish.
110
+ fut_raise.result()
111
+
112
+ assert dfk.cleanup_called, "The DFK should have been cleaned up by the context manager"
113
+ assert DataFlowKernelLoader._dfk is None, "The global DFK should have been removed"
114
+ assert fut_raise.exception() is not None, "fut_raise should contain an exception"
115
+ assert not fut_depend.done(), "fut_depend should have been left un-done (due to dependency failure)"
116
+
117
+
118
+ @pytest.mark.local
119
+ def test_exit_wrong_mode():
120
+
121
+ with pytest.raises(Exception) as ex:
122
+ Config(exit_mode="wrongmode")
123
+
124
+ # with typeguard 4.x this is TypeCheckError,
125
+ # with typeguard 2.x this is TypeError
126
+ # we can't instantiate TypeCheckError if we're in typeguard 2.x environment
127
+ # because it does not exist... so check name using strings.
128
+ assert ex.type.__name__ == "TypeCheckError" or ex.type.__name__ == "TypeError"
@@ -1,6 +1,7 @@
1
+ from concurrent.futures import Future
2
+
1
3
  import parsl
2
4
  from parsl.dataflow.errors import DependencyError
3
- from concurrent.futures import Future
4
5
 
5
6
 
6
7
  @parsl.python_app
@@ -0,0 +1,59 @@
1
+ import inspect
2
+ from concurrent.futures import Future
3
+ from typing import Any, Callable, Dict
4
+
5
+ import pytest
6
+
7
+ import parsl
8
+ from parsl.executors.base import ParslExecutor
9
+
10
+ # N is the number of tasks to chain
11
+ # With mid-2024 Parsl, N>140 causes Parsl to hang
12
+ N = 100
13
+
14
+ # MAX_STACK is the maximum Python stack depth allowed for either
15
+ # task submission to an executor or execution of a task.
16
+ # With mid-2024 Parsl, 2-3 stack entries will be used per
17
+ # recursively launched parsl task. So this should be smaller than
18
+ # 2*N, but big enough to allow regular pytest+parsl stuff to
19
+ # happen.
20
+ MAX_STACK = 50
21
+
22
+
23
+ def local_config():
24
+ return parsl.Config(executors=[ImmediateExecutor()])
25
+
26
+
27
+ class ImmediateExecutor(ParslExecutor):
28
+ def start(self):
29
+ pass
30
+
31
+ def shutdown(self):
32
+ pass
33
+
34
+ def submit(self, func: Callable, resource_specification: Dict[str, Any], *args: Any, **kwargs: Any) -> Future:
35
+ stack_depth = len(inspect.stack())
36
+ assert stack_depth < MAX_STACK, "tasks should not be launched deep in the Python stack"
37
+ fut: Future[None] = Future()
38
+ res = func(*args, **kwargs)
39
+ fut.set_result(res)
40
+ return fut
41
+
42
+
43
+ @parsl.python_app
44
+ def chain(upstream):
45
+ stack_depth = len(inspect.stack())
46
+ assert stack_depth < MAX_STACK, "chained dependencies should not be launched deep in the Python stack"
47
+
48
+
49
+ @pytest.mark.local
50
+ def test_deep_dependency_stack_depth():
51
+
52
+ fut = Future()
53
+ here = fut
54
+
55
+ for _ in range(N):
56
+ here = chain(here)
57
+
58
+ fut.set_result(None)
59
+ here.result()
@@ -41,28 +41,3 @@ def test_fail_sequence(fail_probs):
41
41
 
42
42
  with pytest.raises(DependencyError):
43
43
  t_final.result()
44
-
45
-
46
- def test_deps(width=3):
47
- """Random failures in branches of Map -> Map -> reduce"""
48
- # App1 App2 ... AppN
49
- futs = [random_fail(fail_prob=0.4) for _ in range(width)]
50
-
51
- # App1 App2 ... AppN
52
- # | | |
53
- # V V V
54
- # App1 App2 ... AppN
55
-
56
- futs = [random_fail(fail_prob=0.8, inputs=[f]) for f in futs]
57
-
58
- # App1 App2 ... AppN
59
- # | | |
60
- # V V V
61
- # App1 App2 ... AppN
62
- # \ | /
63
- # \ | /
64
- # App_Final
65
- try:
66
- random_fail(fail_prob=0, inputs=futs).result()
67
- except DependencyError:
68
- pass
@@ -11,9 +11,10 @@ Same applies to datafutures, and we need to know the behavior wrt.
11
11
  2. done() called on 1, vs 2
12
12
 
13
13
  """
14
- import pytest
15
14
  from os.path import basename
16
15
 
16
+ import pytest
17
+
17
18
  from parsl.app.app import python_app
18
19
  from parsl.data_provider.files import File
19
20