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.
- parsl/__init__.py +9 -10
- parsl/addresses.py +26 -6
- parsl/app/app.py +7 -8
- parsl/app/bash.py +15 -8
- parsl/app/errors.py +10 -13
- parsl/app/futures.py +8 -10
- parsl/app/python.py +2 -1
- parsl/benchmark/perf.py +2 -1
- parsl/concurrent/__init__.py +2 -2
- parsl/config.py +53 -10
- parsl/configs/ASPIRE1.py +6 -5
- parsl/configs/Azure.py +9 -8
- parsl/configs/bridges.py +6 -4
- parsl/configs/cc_in2p3.py +3 -3
- parsl/configs/ec2.py +3 -1
- parsl/configs/expanse.py +4 -3
- parsl/configs/frontera.py +3 -4
- parsl/configs/htex_local.py +3 -4
- parsl/configs/illinoiscluster.py +3 -1
- parsl/configs/improv.py +34 -0
- parsl/configs/kubernetes.py +4 -3
- parsl/configs/local_threads.py +5 -1
- parsl/configs/midway.py +5 -3
- parsl/configs/osg.py +4 -2
- parsl/configs/polaris.py +4 -2
- parsl/configs/stampede2.py +6 -5
- parsl/configs/summit.py +3 -3
- parsl/configs/toss3_llnl.py +4 -3
- parsl/configs/vineex_local.py +6 -4
- parsl/configs/wqex_local.py +5 -3
- parsl/curvezmq.py +4 -0
- parsl/data_provider/data_manager.py +4 -3
- parsl/data_provider/file_noop.py +1 -2
- parsl/data_provider/files.py +3 -3
- parsl/data_provider/ftp.py +1 -3
- parsl/data_provider/globus.py +7 -6
- parsl/data_provider/http.py +2 -2
- parsl/data_provider/rsync.py +1 -1
- parsl/data_provider/staging.py +2 -2
- parsl/data_provider/zip.py +135 -0
- parsl/dataflow/dependency_resolvers.py +115 -0
- parsl/dataflow/dflow.py +259 -223
- parsl/dataflow/errors.py +3 -5
- parsl/dataflow/futures.py +27 -14
- parsl/dataflow/memoization.py +5 -5
- parsl/dataflow/rundirs.py +5 -6
- parsl/dataflow/taskrecord.py +4 -5
- parsl/executors/__init__.py +4 -2
- parsl/executors/base.py +45 -15
- parsl/executors/errors.py +13 -0
- parsl/executors/execute_task.py +37 -0
- parsl/executors/flux/execute_parsl_task.py +3 -3
- parsl/executors/flux/executor.py +18 -19
- parsl/executors/flux/flux_instance_manager.py +26 -27
- parsl/executors/high_throughput/errors.py +43 -3
- parsl/executors/high_throughput/executor.py +307 -285
- parsl/executors/high_throughput/interchange.py +137 -168
- parsl/executors/high_throughput/manager_record.py +4 -0
- parsl/executors/high_throughput/manager_selector.py +55 -0
- parsl/executors/high_throughput/monitoring_info.py +2 -1
- parsl/executors/high_throughput/mpi_executor.py +113 -0
- parsl/executors/high_throughput/mpi_prefix_composer.py +10 -11
- parsl/executors/high_throughput/mpi_resource_management.py +6 -17
- parsl/executors/high_throughput/probe.py +9 -7
- parsl/executors/high_throughput/process_worker_pool.py +77 -75
- parsl/executors/high_throughput/zmq_pipes.py +81 -23
- parsl/executors/radical/executor.py +130 -79
- parsl/executors/radical/rpex_resources.py +17 -15
- parsl/executors/radical/rpex_worker.py +4 -3
- parsl/executors/status_handling.py +157 -51
- parsl/executors/taskvine/__init__.py +1 -1
- parsl/executors/taskvine/errors.py +1 -1
- parsl/executors/taskvine/exec_parsl_function.py +2 -2
- parsl/executors/taskvine/executor.py +38 -55
- parsl/executors/taskvine/factory.py +1 -1
- parsl/executors/taskvine/factory_config.py +1 -1
- parsl/executors/taskvine/manager.py +17 -13
- parsl/executors/taskvine/manager_config.py +7 -2
- parsl/executors/threads.py +6 -6
- parsl/executors/workqueue/errors.py +1 -1
- parsl/executors/workqueue/exec_parsl_function.py +6 -5
- parsl/executors/workqueue/executor.py +64 -63
- parsl/executors/workqueue/parsl_coprocess.py +1 -1
- parsl/jobs/error_handlers.py +2 -2
- parsl/jobs/job_status_poller.py +28 -112
- parsl/jobs/states.py +7 -2
- parsl/jobs/strategy.py +43 -31
- parsl/launchers/__init__.py +12 -3
- parsl/launchers/errors.py +1 -1
- parsl/launchers/launchers.py +0 -6
- parsl/log_utils.py +1 -2
- parsl/monitoring/db_manager.py +55 -93
- parsl/monitoring/errors.py +6 -0
- parsl/monitoring/monitoring.py +85 -311
- parsl/monitoring/queries/pandas.py +1 -2
- parsl/monitoring/radios/base.py +13 -0
- parsl/monitoring/radios/filesystem.py +52 -0
- parsl/monitoring/radios/htex.py +57 -0
- parsl/monitoring/radios/multiprocessing.py +17 -0
- parsl/monitoring/radios/udp.py +56 -0
- parsl/monitoring/radios/zmq.py +17 -0
- parsl/monitoring/remote.py +33 -37
- parsl/monitoring/router.py +212 -0
- parsl/monitoring/types.py +5 -6
- parsl/monitoring/visualization/app.py +4 -2
- parsl/monitoring/visualization/models.py +0 -1
- parsl/monitoring/visualization/plots/default/workflow_plots.py +8 -4
- parsl/monitoring/visualization/plots/default/workflow_resource_plots.py +1 -0
- parsl/monitoring/visualization/utils.py +0 -1
- parsl/monitoring/visualization/views.py +16 -9
- parsl/multiprocessing.py +0 -1
- parsl/process_loggers.py +1 -2
- parsl/providers/__init__.py +8 -17
- parsl/providers/aws/aws.py +2 -3
- parsl/providers/azure/azure.py +4 -5
- parsl/providers/base.py +2 -18
- parsl/providers/cluster_provider.py +3 -9
- parsl/providers/condor/condor.py +7 -17
- parsl/providers/errors.py +2 -2
- parsl/providers/googlecloud/googlecloud.py +2 -1
- parsl/providers/grid_engine/grid_engine.py +5 -14
- parsl/providers/kubernetes/kube.py +80 -40
- parsl/providers/local/local.py +13 -26
- parsl/providers/lsf/lsf.py +5 -23
- parsl/providers/pbspro/pbspro.py +5 -17
- parsl/providers/slurm/slurm.py +81 -39
- parsl/providers/torque/torque.py +3 -14
- parsl/serialize/__init__.py +8 -3
- parsl/serialize/base.py +1 -2
- parsl/serialize/concretes.py +5 -4
- parsl/serialize/facade.py +3 -3
- parsl/serialize/proxystore.py +3 -2
- parsl/tests/__init__.py +1 -1
- parsl/tests/configs/azure_single_node.py +4 -5
- parsl/tests/configs/bridges.py +3 -2
- parsl/tests/configs/cc_in2p3.py +1 -3
- parsl/tests/configs/comet.py +2 -1
- parsl/tests/configs/ec2_single_node.py +1 -2
- parsl/tests/configs/ec2_spot.py +1 -2
- parsl/tests/configs/flux_local.py +11 -0
- parsl/tests/configs/frontera.py +2 -3
- parsl/tests/configs/htex_local.py +3 -5
- parsl/tests/configs/htex_local_alternate.py +11 -15
- parsl/tests/configs/htex_local_intask_staging.py +5 -9
- parsl/tests/configs/htex_local_rsync_staging.py +4 -8
- parsl/tests/configs/local_radical.py +1 -3
- parsl/tests/configs/local_radical_mpi.py +2 -2
- parsl/tests/configs/local_threads_checkpoint_periodic.py +8 -10
- parsl/tests/configs/local_threads_monitoring.py +0 -1
- parsl/tests/configs/midway.py +2 -2
- parsl/tests/configs/nscc_singapore.py +3 -3
- parsl/tests/configs/osg_htex.py +1 -1
- parsl/tests/configs/petrelkube.py +3 -2
- parsl/tests/configs/slurm_local.py +24 -0
- parsl/tests/configs/summit.py +1 -0
- parsl/tests/configs/taskvine_ex.py +4 -7
- parsl/tests/configs/user_opts.py +0 -7
- parsl/tests/configs/workqueue_ex.py +4 -6
- parsl/tests/conftest.py +27 -13
- parsl/tests/integration/test_stress/test_python_simple.py +3 -4
- parsl/tests/integration/test_stress/test_python_threads.py +3 -5
- parsl/tests/manual_tests/htex_local.py +4 -6
- parsl/tests/manual_tests/test_basic.py +1 -0
- parsl/tests/manual_tests/test_log_filter.py +3 -1
- parsl/tests/manual_tests/test_memory_limits.py +6 -8
- parsl/tests/manual_tests/test_regression_220.py +2 -1
- parsl/tests/manual_tests/test_udp_simple.py +4 -4
- parsl/tests/manual_tests/test_worker_count.py +3 -2
- parsl/tests/scaling_tests/htex_local.py +2 -4
- parsl/tests/scaling_tests/test_scale.py +0 -9
- parsl/tests/scaling_tests/vineex_condor.py +1 -2
- parsl/tests/scaling_tests/vineex_local.py +1 -2
- parsl/tests/site_tests/site_config_selector.py +1 -6
- parsl/tests/site_tests/test_provider.py +4 -2
- parsl/tests/site_tests/test_site.py +2 -0
- parsl/tests/sites/test_affinity.py +7 -7
- parsl/tests/sites/test_dynamic_executor.py +3 -4
- parsl/tests/sites/test_ec2.py +3 -2
- parsl/tests/sites/test_worker_info.py +4 -5
- parsl/tests/test_aalst_patterns.py +0 -1
- parsl/tests/test_bash_apps/test_apptimeout.py +2 -2
- parsl/tests/test_bash_apps/test_basic.py +10 -4
- parsl/tests/test_bash_apps/test_error_codes.py +5 -7
- parsl/tests/test_bash_apps/test_inputs_default.py +25 -0
- parsl/tests/test_bash_apps/test_kwarg_storage.py +1 -1
- parsl/tests/test_bash_apps/test_memoize.py +2 -8
- parsl/tests/test_bash_apps/test_memoize_ignore_args.py +9 -14
- parsl/tests/test_bash_apps/test_memoize_ignore_args_regr.py +9 -14
- parsl/tests/test_bash_apps/test_multiline.py +1 -1
- parsl/tests/test_bash_apps/test_pipeline.py +1 -1
- parsl/tests/test_bash_apps/test_std_uri.py +123 -0
- parsl/tests/test_bash_apps/test_stdout.py +33 -8
- parsl/tests/test_callables.py +2 -2
- parsl/tests/test_checkpointing/test_periodic.py +21 -39
- parsl/tests/test_checkpointing/test_python_checkpoint_1.py +1 -0
- parsl/tests/test_checkpointing/test_python_checkpoint_2.py +2 -2
- parsl/tests/test_checkpointing/test_python_checkpoint_3.py +0 -1
- parsl/tests/test_checkpointing/test_regression_239.py +1 -1
- parsl/tests/test_checkpointing/test_task_exit.py +2 -3
- parsl/tests/test_docs/test_from_slides.py +5 -2
- parsl/tests/test_docs/test_kwargs.py +4 -1
- parsl/tests/test_docs/test_tutorial_1.py +1 -2
- parsl/tests/test_docs/test_workflow1.py +2 -2
- parsl/tests/test_docs/test_workflow2.py +0 -1
- parsl/tests/test_error_handling/test_rand_fail.py +2 -2
- parsl/tests/test_error_handling/test_resource_spec.py +10 -12
- parsl/tests/test_error_handling/test_retries.py +6 -16
- parsl/tests/test_error_handling/test_retry_handler.py +1 -0
- parsl/tests/test_error_handling/test_retry_handler_failure.py +2 -1
- parsl/tests/test_error_handling/test_serialization_fail.py +1 -1
- parsl/tests/test_error_handling/test_wrap_with_logs.py +1 -0
- parsl/tests/test_execute_task.py +29 -0
- parsl/tests/test_flux.py +1 -1
- parsl/tests/test_htex/test_basic.py +2 -3
- parsl/tests/test_htex/test_block_manager_selector_unit.py +20 -0
- parsl/tests/test_htex/test_command_client_timeout.py +66 -0
- parsl/tests/test_htex/test_connected_blocks.py +3 -2
- parsl/tests/test_htex/test_cpu_affinity_explicit.py +6 -10
- parsl/tests/test_htex/test_disconnected_blocks.py +6 -5
- parsl/tests/test_htex/test_disconnected_blocks_failing_provider.py +71 -0
- parsl/tests/test_htex/test_drain.py +11 -10
- parsl/tests/test_htex/test_htex.py +51 -25
- parsl/tests/test_htex/test_manager_failure.py +0 -1
- parsl/tests/test_htex/test_manager_selector_by_block.py +51 -0
- parsl/tests/test_htex/test_managers_command.py +36 -0
- parsl/tests/test_htex/test_missing_worker.py +2 -12
- parsl/tests/test_htex/test_multiple_disconnected_blocks.py +9 -9
- parsl/tests/test_htex/test_resource_spec_validation.py +45 -0
- parsl/tests/test_htex/test_zmq_binding.py +29 -8
- parsl/tests/test_monitoring/test_app_names.py +5 -5
- parsl/tests/test_monitoring/test_basic.py +73 -25
- parsl/tests/test_monitoring/test_db_locks.py +6 -4
- parsl/tests/test_monitoring/test_fuzz_zmq.py +19 -8
- parsl/tests/test_monitoring/test_htex_init_blocks_vs_monitoring.py +80 -0
- parsl/tests/test_monitoring/test_incomplete_futures.py +5 -4
- parsl/tests/test_monitoring/test_memoization_representation.py +4 -2
- parsl/tests/test_monitoring/test_stdouterr.py +134 -0
- parsl/tests/test_monitoring/test_viz_colouring.py +1 -0
- parsl/tests/test_mpi_apps/test_bad_mpi_config.py +33 -26
- parsl/tests/test_mpi_apps/test_mpi_mode_enabled.py +28 -11
- parsl/tests/test_mpi_apps/test_mpi_prefix.py +4 -4
- parsl/tests/test_mpi_apps/test_mpi_scheduler.py +7 -2
- parsl/tests/test_mpi_apps/test_mpiex.py +64 -0
- parsl/tests/test_mpi_apps/test_resource_spec.py +42 -49
- parsl/tests/test_providers/test_kubernetes_provider.py +102 -0
- parsl/tests/test_providers/test_local_provider.py +3 -132
- parsl/tests/test_providers/test_pbspro_template.py +2 -3
- parsl/tests/test_providers/test_slurm_template.py +2 -3
- parsl/tests/test_providers/test_submiterror_deprecation.py +2 -1
- parsl/tests/test_python_apps/test_context_manager.py +128 -0
- parsl/tests/test_python_apps/test_dep_standard_futures.py +2 -1
- parsl/tests/test_python_apps/test_dependencies_deep.py +59 -0
- parsl/tests/test_python_apps/test_fail.py +0 -25
- parsl/tests/test_python_apps/test_futures.py +2 -1
- parsl/tests/test_python_apps/test_inputs_default.py +22 -0
- parsl/tests/test_python_apps/test_join.py +0 -1
- parsl/tests/test_python_apps/test_lifted.py +11 -7
- parsl/tests/test_python_apps/test_memoize_bad_id_for_memo.py +1 -0
- parsl/tests/test_python_apps/test_outputs.py +1 -1
- parsl/tests/test_python_apps/test_pluggable_future_resolution.py +161 -0
- parsl/tests/test_radical/test_mpi_funcs.py +1 -2
- parsl/tests/test_regression/test_1480.py +2 -1
- parsl/tests/test_regression/test_1653.py +2 -1
- parsl/tests/test_regression/test_226.py +1 -0
- parsl/tests/test_regression/test_2652.py +1 -0
- parsl/tests/test_regression/test_69a.py +0 -1
- parsl/tests/test_regression/test_854.py +4 -2
- parsl/tests/test_regression/test_97_parallelism_0.py +1 -2
- parsl/tests/test_regression/test_98.py +0 -1
- parsl/tests/test_scaling/test_block_error_handler.py +9 -4
- parsl/tests/test_scaling/test_regression_1621.py +11 -15
- parsl/tests/test_scaling/test_regression_3568_scaledown_vs_MISSING.py +84 -0
- parsl/tests/test_scaling/test_regression_3696_oscillation.py +103 -0
- parsl/tests/test_scaling/test_scale_down.py +2 -5
- parsl/tests/test_scaling/test_scale_down_htex_auto_scale.py +5 -8
- parsl/tests/test_scaling/test_scale_down_htex_unregistered.py +71 -0
- parsl/tests/test_scaling/test_shutdown_scalein.py +73 -0
- parsl/tests/test_scaling/test_worker_interchange_bad_messages_3262.py +90 -0
- parsl/tests/test_serialization/test_2555_caching_deserializer.py +1 -1
- parsl/tests/test_serialization/test_3495_deserialize_managerlost.py +47 -0
- parsl/tests/test_serialization/test_basic.py +2 -1
- parsl/tests/test_serialization/test_htex_code_cache.py +3 -4
- parsl/tests/test_serialization/test_pack_resource_spec.py +2 -1
- parsl/tests/test_serialization/test_proxystore_configured.py +10 -6
- parsl/tests/test_serialization/test_proxystore_impl.py +5 -3
- parsl/tests/test_shutdown/test_kill_monitoring.py +64 -0
- parsl/tests/test_staging/staging_provider.py +2 -2
- parsl/tests/test_staging/test_1316.py +3 -4
- parsl/tests/test_staging/test_docs_1.py +2 -1
- parsl/tests/test_staging/test_docs_2.py +2 -1
- parsl/tests/test_staging/test_elaborate_noop_file.py +2 -3
- parsl/tests/{test_data → test_staging}/test_file.py +6 -6
- parsl/tests/{test_data → test_staging}/test_output_chain_filenames.py +3 -0
- parsl/tests/test_staging/test_staging_ftp.py +1 -0
- parsl/tests/test_staging/test_staging_https.py +5 -2
- parsl/tests/test_staging/test_staging_stdout.py +64 -0
- parsl/tests/test_staging/test_zip_in.py +39 -0
- parsl/tests/test_staging/test_zip_out.py +110 -0
- parsl/tests/test_staging/test_zip_to_zip.py +41 -0
- parsl/tests/test_summary.py +2 -2
- parsl/tests/test_thread_parallelism.py +0 -1
- parsl/tests/test_threads/test_configs.py +1 -2
- parsl/tests/test_threads/test_lazy_errors.py +2 -2
- parsl/tests/test_utils/test_execute_wait.py +35 -0
- parsl/tests/test_utils/test_sanitize_dns.py +76 -0
- parsl/tests/unit/test_address.py +20 -0
- parsl/tests/unit/test_file.py +99 -0
- parsl/tests/unit/test_usage_tracking.py +66 -0
- parsl/usage_tracking/api.py +65 -0
- parsl/usage_tracking/levels.py +6 -0
- parsl/usage_tracking/usage.py +104 -62
- parsl/utils.py +137 -4
- parsl/version.py +1 -1
- {parsl-2024.3.18.data → parsl-2025.1.13.data}/scripts/exec_parsl_function.py +6 -5
- parsl-2025.1.13.data/scripts/interchange.py +649 -0
- {parsl-2024.3.18.data → parsl-2025.1.13.data}/scripts/process_worker_pool.py +77 -75
- parsl-2025.1.13.dist-info/METADATA +96 -0
- parsl-2025.1.13.dist-info/RECORD +462 -0
- {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/WHEEL +1 -1
- parsl/channels/__init__.py +0 -7
- parsl/channels/base.py +0 -141
- parsl/channels/errors.py +0 -113
- parsl/channels/local/local.py +0 -164
- parsl/channels/oauth_ssh/oauth_ssh.py +0 -110
- parsl/channels/ssh/ssh.py +0 -276
- parsl/channels/ssh_il/__init__.py +0 -0
- parsl/channels/ssh_il/ssh_il.py +0 -74
- parsl/configs/ad_hoc.py +0 -35
- parsl/executors/radical/rpex_master.py +0 -42
- parsl/monitoring/radios.py +0 -175
- parsl/providers/ad_hoc/__init__.py +0 -0
- parsl/providers/ad_hoc/ad_hoc.py +0 -248
- parsl/providers/cobalt/__init__.py +0 -0
- parsl/providers/cobalt/cobalt.py +0 -236
- parsl/providers/cobalt/template.py +0 -17
- parsl/tests/configs/ad_hoc_cluster_htex.py +0 -35
- parsl/tests/configs/cooley_htex.py +0 -37
- parsl/tests/configs/htex_ad_hoc_cluster.py +0 -28
- parsl/tests/configs/local_adhoc.py +0 -18
- parsl/tests/configs/swan_htex.py +0 -43
- parsl/tests/configs/theta.py +0 -37
- parsl/tests/integration/test_channels/__init__.py +0 -0
- parsl/tests/integration/test_channels/test_channels.py +0 -17
- parsl/tests/integration/test_channels/test_local_channel.py +0 -42
- parsl/tests/integration/test_channels/test_scp_1.py +0 -45
- parsl/tests/integration/test_channels/test_ssh_1.py +0 -40
- parsl/tests/integration/test_channels/test_ssh_errors.py +0 -46
- parsl/tests/integration/test_channels/test_ssh_file_transport.py +0 -41
- parsl/tests/integration/test_channels/test_ssh_interactive.py +0 -24
- parsl/tests/manual_tests/test_ad_hoc_htex.py +0 -48
- parsl/tests/manual_tests/test_fan_in_out_htex_remote.py +0 -88
- parsl/tests/manual_tests/test_oauth_ssh.py +0 -13
- parsl/tests/sites/test_local_adhoc.py +0 -61
- parsl/tests/test_channels/__init__.py +0 -0
- parsl/tests/test_channels/test_large_output.py +0 -22
- parsl/tests/test_data/__init__.py +0 -0
- parsl/tests/test_mpi_apps/test_mpi_mode_disabled.py +0 -51
- parsl/tests/test_providers/test_cobalt_deprecation_warning.py +0 -16
- parsl-2024.3.18.dist-info/METADATA +0 -98
- parsl-2024.3.18.dist-info/RECORD +0 -449
- parsl/{channels/local → monitoring/radios}/__init__.py +0 -0
- parsl/{channels/oauth_ssh → tests/test_shutdown}/__init__.py +0 -0
- parsl/tests/{test_data → test_staging}/test_file_apps.py +0 -0
- parsl/tests/{test_data → test_staging}/test_file_staging.py +0 -0
- parsl/{channels/ssh → tests/unit}/__init__.py +0 -0
- {parsl-2024.3.18.data → parsl-2025.1.13.data}/scripts/parsl_coprocess.py +1 -1
- {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/LICENSE +0 -0
- {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/entry_points.txt +0 -0
- {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
import parsl
|
4
|
+
from parsl import python_app
|
5
|
+
from parsl.executors.threads import ThreadPoolExecutor
|
6
|
+
|
7
|
+
|
8
|
+
def local_config():
|
9
|
+
return parsl.Config(executors=[ThreadPoolExecutor()])
|
10
|
+
|
11
|
+
|
12
|
+
@pytest.mark.local
|
13
|
+
def test_default_inputs():
|
14
|
+
@python_app
|
15
|
+
def identity(inp):
|
16
|
+
return inp
|
17
|
+
|
18
|
+
@python_app
|
19
|
+
def add_inputs(inputs=[identity(1), identity(2)]):
|
20
|
+
return sum(inputs)
|
21
|
+
|
22
|
+
assert add_inputs().result() == 3
|
@@ -1,26 +1,30 @@
|
|
1
|
+
from concurrent.futures import Future
|
2
|
+
from typing import TypeVar
|
3
|
+
|
1
4
|
import pytest
|
2
5
|
|
3
|
-
from concurrent.futures import Future
|
4
6
|
from parsl import python_app
|
5
7
|
|
8
|
+
T = TypeVar('T')
|
9
|
+
|
6
10
|
|
7
11
|
@python_app
|
8
|
-
def returns_a_dict():
|
12
|
+
def returns_a_dict() -> dict:
|
9
13
|
return {"a": "X", "b": "Y"}
|
10
14
|
|
11
15
|
|
12
16
|
@python_app
|
13
|
-
def returns_a_list():
|
17
|
+
def returns_a_list() -> list:
|
14
18
|
return ["X", "Y"]
|
15
19
|
|
16
20
|
|
17
21
|
@python_app
|
18
|
-
def returns_a_tuple():
|
22
|
+
def returns_a_tuple() -> tuple:
|
19
23
|
return ("X", "Y")
|
20
24
|
|
21
25
|
|
22
26
|
@python_app
|
23
|
-
def returns_a_class():
|
27
|
+
def returns_a_class() -> type:
|
24
28
|
from dataclasses import dataclass
|
25
29
|
|
26
30
|
@dataclass
|
@@ -38,7 +42,7 @@ class MyOuterClass():
|
|
38
42
|
|
39
43
|
|
40
44
|
@python_app
|
41
|
-
def returns_a_class_instance():
|
45
|
+
def returns_a_class_instance() -> object:
|
42
46
|
return MyOuterClass()
|
43
47
|
|
44
48
|
|
@@ -110,7 +114,7 @@ def test_returns_a_class():
|
|
110
114
|
|
111
115
|
|
112
116
|
@python_app
|
113
|
-
def passthrough(v):
|
117
|
+
def passthrough(v: T) -> T:
|
114
118
|
return v
|
115
119
|
|
116
120
|
|
@@ -16,7 +16,7 @@ def double(x, outputs=[]):
|
|
16
16
|
whitelist = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'configs', '*threads*')
|
17
17
|
|
18
18
|
|
19
|
-
@pytest.mark.
|
19
|
+
@pytest.mark.shared_fs
|
20
20
|
def test_launch_apps(tmpd_cwd, n=2):
|
21
21
|
outdir = tmpd_cwd / "outputs"
|
22
22
|
outdir.mkdir()
|
@@ -0,0 +1,161 @@
|
|
1
|
+
from concurrent.futures import Future
|
2
|
+
from pathlib import Path
|
3
|
+
from threading import Event
|
4
|
+
from typing import Sequence
|
5
|
+
|
6
|
+
import pytest
|
7
|
+
|
8
|
+
import parsl
|
9
|
+
from parsl.config import Config
|
10
|
+
from parsl.dataflow.dependency_resolvers import DEEP_DEPENDENCY_RESOLVER
|
11
|
+
from parsl.dataflow.errors import DependencyError
|
12
|
+
|
13
|
+
|
14
|
+
def local_config():
|
15
|
+
return Config(dependency_resolver=DEEP_DEPENDENCY_RESOLVER)
|
16
|
+
|
17
|
+
|
18
|
+
@parsl.python_app
|
19
|
+
def a(event):
|
20
|
+
event.wait()
|
21
|
+
return 7
|
22
|
+
|
23
|
+
|
24
|
+
@parsl.python_app
|
25
|
+
def b(x: int):
|
26
|
+
return x + 1
|
27
|
+
|
28
|
+
|
29
|
+
@pytest.mark.local
|
30
|
+
def test_simple_pos_arg():
|
31
|
+
e = Event()
|
32
|
+
s = a(e)
|
33
|
+
f_b = b(s)
|
34
|
+
e.set()
|
35
|
+
|
36
|
+
assert f_b.result() == 8
|
37
|
+
|
38
|
+
|
39
|
+
@parsl.python_app
|
40
|
+
def b_first(x: Sequence[int]):
|
41
|
+
return x[0] + 1
|
42
|
+
|
43
|
+
|
44
|
+
@pytest.mark.local
|
45
|
+
def test_tuple_pos_arg():
|
46
|
+
e = Event()
|
47
|
+
s = (a(e),)
|
48
|
+
f_b = b_first(s)
|
49
|
+
e.set()
|
50
|
+
assert f_b.result() == 8
|
51
|
+
|
52
|
+
|
53
|
+
@pytest.mark.local
|
54
|
+
def test_list_exception():
|
55
|
+
a = Future()
|
56
|
+
a.set_exception(RuntimeError("artificial error"))
|
57
|
+
f_b = b([a])
|
58
|
+
assert isinstance(f_b.exception(), DependencyError)
|
59
|
+
|
60
|
+
|
61
|
+
@parsl.python_app
|
62
|
+
def make_path(s: str):
|
63
|
+
return Path(s)
|
64
|
+
|
65
|
+
|
66
|
+
@parsl.python_app
|
67
|
+
def append_paths(iterable, end_str: str = "end"):
|
68
|
+
type_ = type(iterable)
|
69
|
+
return type_([Path(s, end_str) for s in iterable])
|
70
|
+
|
71
|
+
|
72
|
+
@pytest.mark.local
|
73
|
+
@pytest.mark.parametrize(
|
74
|
+
"type_",
|
75
|
+
[
|
76
|
+
tuple,
|
77
|
+
list,
|
78
|
+
set,
|
79
|
+
],
|
80
|
+
)
|
81
|
+
def test_resolving_iterables(type_):
|
82
|
+
output1 = make_path("test1")
|
83
|
+
output2 = make_path("test2")
|
84
|
+
output3 = append_paths(type_([output1, output2]), end_str="end")
|
85
|
+
assert output3.result() == type_([Path("test1", "end"), Path("test2", "end")])
|
86
|
+
|
87
|
+
|
88
|
+
@parsl.python_app
|
89
|
+
def append_paths_dict(iterable: dict, end_str: str = "end"):
|
90
|
+
return {Path(k, end_str): Path(v, end_str) for k, v in iterable.items()}
|
91
|
+
|
92
|
+
|
93
|
+
@pytest.mark.local
|
94
|
+
def test_resolving_dict():
|
95
|
+
output1 = make_path("test1")
|
96
|
+
output2 = make_path("test2")
|
97
|
+
output3 = append_paths_dict({output1: output2}, end_str="end")
|
98
|
+
assert output3.result() == {Path("test1", "end"): Path("test2", "end")}
|
99
|
+
|
100
|
+
|
101
|
+
@parsl.python_app
|
102
|
+
def extract_deep(struct: list):
|
103
|
+
return struct[0][0][0][0][0]
|
104
|
+
|
105
|
+
|
106
|
+
@pytest.mark.local
|
107
|
+
def test_deeper_list():
|
108
|
+
f = Future()
|
109
|
+
f.set_result(7)
|
110
|
+
f_b = extract_deep([[[[[f]]]]])
|
111
|
+
|
112
|
+
assert f_b.result() == 7
|
113
|
+
|
114
|
+
|
115
|
+
@pytest.mark.local
|
116
|
+
def test_deeper_list_and_tuple():
|
117
|
+
f = Future()
|
118
|
+
f.set_result(7)
|
119
|
+
f_b = extract_deep([([([f],)],)])
|
120
|
+
|
121
|
+
assert f_b.result() == 7
|
122
|
+
|
123
|
+
|
124
|
+
@parsl.python_app
|
125
|
+
def dictionary_checker(d):
|
126
|
+
assert d["a"] == [30, 10]
|
127
|
+
assert d["b"] == 20
|
128
|
+
|
129
|
+
|
130
|
+
@pytest.mark.local
|
131
|
+
def test_dictionary():
|
132
|
+
k1 = Future()
|
133
|
+
k1.set_result("a")
|
134
|
+
k2 = Future()
|
135
|
+
k2.set_result("b")
|
136
|
+
v1 = Future()
|
137
|
+
v1.set_result(10)
|
138
|
+
|
139
|
+
# this .result() will fail if the asserts fail
|
140
|
+
dictionary_checker({k1: [30, v1], k2: 20}).result()
|
141
|
+
|
142
|
+
|
143
|
+
@pytest.mark.local
|
144
|
+
def test_dictionary_later():
|
145
|
+
k1 = Future()
|
146
|
+
k2 = Future()
|
147
|
+
v1 = Future()
|
148
|
+
|
149
|
+
f1 = dictionary_checker({k1: [30, v1], k2: 20})
|
150
|
+
|
151
|
+
assert not f1.done()
|
152
|
+
k1.set_result("a")
|
153
|
+
k2.set_result("b")
|
154
|
+
v1.set_result(10)
|
155
|
+
|
156
|
+
# having set the results, f1 should fairly rapidly complete (but not
|
157
|
+
# instantly) so we can't assert on done() here... but we can
|
158
|
+
# check that f1 does "eventually" complete... meaning that
|
159
|
+
# none of the assertions inside test_dictionary have failed
|
160
|
+
|
161
|
+
assert f1.result() is None
|
@@ -1,6 +1,6 @@
|
|
1
|
-
import parsl
|
2
1
|
import pytest
|
3
2
|
|
3
|
+
import parsl
|
4
4
|
from parsl.tests.configs.local_radical_mpi import fresh_config as local_config
|
5
5
|
|
6
6
|
|
@@ -16,7 +16,6 @@ def some_mpi_func(msg, sleep, comm=None, parsl_resource_specification={}):
|
|
16
16
|
apps = []
|
17
17
|
|
18
18
|
|
19
|
-
@pytest.mark.skip("hangs in CI - waiting for resolution of issue #3029")
|
20
19
|
@pytest.mark.local
|
21
20
|
@pytest.mark.radical
|
22
21
|
def test_radical_mpi(n=7):
|
@@ -1,11 +1,10 @@
|
|
1
1
|
import pytest
|
2
2
|
|
3
3
|
import parsl
|
4
|
-
|
5
4
|
from parsl.config import Config
|
6
5
|
from parsl.executors import HighThroughputExecutor
|
7
|
-
from parsl.providers import LocalProvider
|
8
6
|
from parsl.launchers import SimpleLauncher
|
7
|
+
from parsl.providers import LocalProvider
|
9
8
|
|
10
9
|
|
11
10
|
def local_config() -> Config:
|
@@ -1,11 +1,16 @@
|
|
1
|
+
from functools import partial
|
2
|
+
from unittest.mock import Mock
|
3
|
+
|
1
4
|
import pytest
|
2
5
|
|
3
6
|
from parsl.executors import HighThroughputExecutor
|
7
|
+
from parsl.jobs.error_handlers import (
|
8
|
+
noop_error_handler,
|
9
|
+
simple_error_handler,
|
10
|
+
windowed_error_handler,
|
11
|
+
)
|
12
|
+
from parsl.jobs.states import JobState, JobStatus
|
4
13
|
from parsl.providers import LocalProvider
|
5
|
-
from unittest.mock import Mock
|
6
|
-
from parsl.jobs.states import JobStatus, JobState
|
7
|
-
from parsl.jobs.error_handlers import simple_error_handler, windowed_error_handler, noop_error_handler
|
8
|
-
from functools import partial
|
9
14
|
|
10
15
|
|
11
16
|
@pytest.mark.local
|
@@ -3,12 +3,19 @@ import threading
|
|
3
3
|
import pytest
|
4
4
|
|
5
5
|
import parsl
|
6
|
-
from parsl.channels import LocalChannel
|
7
6
|
from parsl.config import Config
|
8
7
|
from parsl.executors import HighThroughputExecutor
|
9
8
|
from parsl.launchers import SimpleLauncher
|
10
9
|
from parsl.providers import LocalProvider
|
11
10
|
|
11
|
+
# Timing notes:
|
12
|
+
# The configured strategy_period must be much smaller than the delay in
|
13
|
+
# app() so that multiple iterations of the strategy have had a chance
|
14
|
+
# to (mis)behave.
|
15
|
+
# The status polling interval in OneShotLocalProvider must be much bigger
|
16
|
+
# than the above times, so that the job status cached from the provider
|
17
|
+
# will not be updated while the single invocation of app() runs.
|
18
|
+
|
12
19
|
|
13
20
|
@parsl.python_app
|
14
21
|
def app():
|
@@ -35,7 +42,6 @@ def test_one_block(tmpd_cwd):
|
|
35
42
|
one app is invoked. this is a regression test.
|
36
43
|
"""
|
37
44
|
oneshot_provider = OneShotLocalProvider(
|
38
|
-
channel=LocalChannel(),
|
39
45
|
init_blocks=0,
|
40
46
|
min_blocks=0,
|
41
47
|
max_blocks=10,
|
@@ -55,20 +61,10 @@ def test_one_block(tmpd_cwd):
|
|
55
61
|
)
|
56
62
|
],
|
57
63
|
strategy='simple',
|
64
|
+
strategy_period=0.1
|
58
65
|
)
|
59
66
|
|
60
|
-
parsl.load(config)
|
61
|
-
|
62
|
-
|
63
|
-
def poller():
|
64
|
-
import time
|
65
|
-
while True:
|
66
|
-
dfk.job_status_poller.poll()
|
67
|
-
time.sleep(0.1)
|
68
|
-
|
69
|
-
threading.Thread(target=poller, daemon=True).start()
|
70
|
-
app().result()
|
71
|
-
parsl.dfk().cleanup()
|
72
|
-
parsl.clear()
|
67
|
+
with parsl.load(config):
|
68
|
+
app().result()
|
73
69
|
|
74
70
|
assert oneshot_provider.recorded_submits == 1
|
@@ -0,0 +1,84 @@
|
|
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 WrappedLauncher
|
9
|
+
from parsl.providers import LocalProvider
|
10
|
+
|
11
|
+
|
12
|
+
def local_config():
|
13
|
+
# see the comments inside test_regression for reasoning about why each
|
14
|
+
# of these parameters is set why it is.
|
15
|
+
return Config(
|
16
|
+
max_idletime=1,
|
17
|
+
|
18
|
+
strategy='htex_auto_scale',
|
19
|
+
strategy_period=1,
|
20
|
+
|
21
|
+
executors=[
|
22
|
+
HighThroughputExecutor(
|
23
|
+
label="htex_local",
|
24
|
+
encrypted=True,
|
25
|
+
provider=LocalProvider(
|
26
|
+
init_blocks=1,
|
27
|
+
min_blocks=0,
|
28
|
+
max_blocks=1,
|
29
|
+
launcher=WrappedLauncher(prepend="sleep inf ; "),
|
30
|
+
),
|
31
|
+
)
|
32
|
+
],
|
33
|
+
)
|
34
|
+
|
35
|
+
|
36
|
+
@parsl.python_app
|
37
|
+
def task():
|
38
|
+
return 7
|
39
|
+
|
40
|
+
|
41
|
+
@pytest.mark.local
|
42
|
+
def test_regression(try_assert):
|
43
|
+
# The above config means that we should start scaling out one initial
|
44
|
+
# block, but then scale it back in after a second or so if the executor
|
45
|
+
# is kept idle (which this test does using try_assert).
|
46
|
+
|
47
|
+
# Because of 'sleep inf' in the WrappedLaucher, the block will not ever
|
48
|
+
# register.
|
49
|
+
|
50
|
+
# The bug being tested is about mistreatment of blocks which are scaled in
|
51
|
+
# before they have a chance to register, and the above forces that to
|
52
|
+
# happen.
|
53
|
+
|
54
|
+
# After that scaling in has happened, we should see that we have one block
|
55
|
+
# and it should be in a terminal state. The below try_assert waits for
|
56
|
+
# that to become true.
|
57
|
+
|
58
|
+
# At that time, we should also see htex reporting no blocks registered - as
|
59
|
+
# mentioned above, that is a necessary part of the bug being tested here.
|
60
|
+
|
61
|
+
# Give 10 strategy periods for the above to happen: each step of scale up,
|
62
|
+
# and scale down due to idleness isn't guaranteed to happen in exactly one
|
63
|
+
# scaling step.
|
64
|
+
|
65
|
+
htex = parsl.dfk().executors['htex_local']
|
66
|
+
|
67
|
+
try_assert(lambda: len(htex.status_facade) == 1 and htex.status_facade['0'].terminal,
|
68
|
+
timeout_ms=10000)
|
69
|
+
|
70
|
+
assert htex.connected_blocks() == [], "No block should have connected to interchange"
|
71
|
+
|
72
|
+
# Now we can reconfigure the launcher to let subsequent blocks launch ok,
|
73
|
+
# and run a trivial task. That trivial task will scale up a new block and
|
74
|
+
# run the task successfully.
|
75
|
+
|
76
|
+
# Prior to issue #3568, the bug was that the scale in of the first
|
77
|
+
# block earlier in the test case would have incorrectly been treated as a
|
78
|
+
# failure, and then the block error handler would have treated that failure
|
79
|
+
# as a permanent htex failure, and so the task execution below would raise
|
80
|
+
# a BadStateException rather than attempt to run the task.
|
81
|
+
|
82
|
+
assert htex.provider.launcher.prepend != "", "Pre-req: prepend attribute should exist and be non-empty"
|
83
|
+
htex.provider.launcher.prepend = ""
|
84
|
+
assert task().result() == 7
|
@@ -0,0 +1,103 @@
|
|
1
|
+
import math
|
2
|
+
from unittest.mock import MagicMock
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
from parsl.executors.high_throughput.executor import HighThroughputExecutor
|
7
|
+
from parsl.jobs.states import JobState, JobStatus
|
8
|
+
from parsl.jobs.strategy import Strategy
|
9
|
+
|
10
|
+
|
11
|
+
# the parameterize tuple consists of:
|
12
|
+
# Input:
|
13
|
+
# * number of tasks to mock the load as
|
14
|
+
# * number of workers per node
|
15
|
+
# Expected output:
|
16
|
+
# * the number of blocks we should expect to be launched
|
17
|
+
# in this situation
|
18
|
+
#
|
19
|
+
# This test will configure an executor, then run strategize
|
20
|
+
# a few times, asserting that it converges to the correct
|
21
|
+
# number of blocks without oscillating.
|
22
|
+
@pytest.mark.local
|
23
|
+
@pytest.mark.parametrize("ns", [(14, 48, 1), # values from issue #3696
|
24
|
+
|
25
|
+
(1, 1, 1), # one task needs one block
|
26
|
+
|
27
|
+
(100, 1, 20), # many one-task blocks, hitting hard-coded max blocks
|
28
|
+
|
29
|
+
(47, 48, 1), # some edge cases around #3696 values
|
30
|
+
(48, 48, 1), # "
|
31
|
+
(49, 48, 2), # "
|
32
|
+
(149, 50, 3)]) # "
|
33
|
+
def test_htex_strategy_does_not_oscillate(ns):
|
34
|
+
"""Check for oscillations in htex scaling.
|
35
|
+
In issue 3696, with a large number of workers per block
|
36
|
+
and a smaller number of active tasks, the htex scaling
|
37
|
+
strategy oscillates between 0 and 1 active block, rather
|
38
|
+
than converging to 1 active block.
|
39
|
+
"""
|
40
|
+
|
41
|
+
n_tasks, n_workers, n_blocks = ns
|
42
|
+
|
43
|
+
s = Strategy(strategy='htex_auto_scale', max_idletime=0)
|
44
|
+
|
45
|
+
provider = MagicMock()
|
46
|
+
executor = MagicMock(spec=HighThroughputExecutor)
|
47
|
+
|
48
|
+
statuses = {}
|
49
|
+
|
50
|
+
executor.provider = provider
|
51
|
+
executor.outstanding = n_tasks
|
52
|
+
executor.status_facade = statuses
|
53
|
+
executor.workers_per_node = n_workers
|
54
|
+
|
55
|
+
provider.parallelism = 1
|
56
|
+
provider.init_blocks = 0
|
57
|
+
provider.min_blocks = 0
|
58
|
+
provider.max_blocks = 20
|
59
|
+
provider.nodes_per_block = 1
|
60
|
+
|
61
|
+
def scale_out(n):
|
62
|
+
for _ in range(n):
|
63
|
+
statuses[len(statuses)] = JobStatus(state=JobState.PENDING)
|
64
|
+
|
65
|
+
executor.scale_out_facade.side_effect = scale_out
|
66
|
+
|
67
|
+
def scale_in(n, max_idletime=None):
|
68
|
+
# find n PENDING jobs and set them to CANCELLED
|
69
|
+
for k in statuses:
|
70
|
+
if n == 0:
|
71
|
+
return
|
72
|
+
if statuses[k].state == JobState.PENDING:
|
73
|
+
statuses[k].state = JobState.CANCELLED
|
74
|
+
n -= 1
|
75
|
+
|
76
|
+
executor.scale_in_facade.side_effect = scale_in
|
77
|
+
|
78
|
+
s.add_executors([executor])
|
79
|
+
|
80
|
+
# In issue #3696, this first strategise does initial and load based
|
81
|
+
# scale outs, because n_tasks > n_workers*0
|
82
|
+
s.strategize([executor])
|
83
|
+
|
84
|
+
executor.scale_out_facade.assert_called()
|
85
|
+
assert len(statuses) == n_blocks, "Should have launched n_blocks"
|
86
|
+
assert len([k for k in statuses if statuses[k].state == JobState.PENDING]) == n_blocks
|
87
|
+
# there might be several calls to scale_out_facade inside strategy,
|
88
|
+
# but the end effect should be that exactly one block is scaled out.
|
89
|
+
|
90
|
+
executor.scale_in_facade.assert_not_called()
|
91
|
+
|
92
|
+
# In issue #3696, this second strategize does a scale in, because n_tasks < n_workers*1
|
93
|
+
s.strategize([executor])
|
94
|
+
|
95
|
+
# assert that there should still be n_blocks pending blocks
|
96
|
+
assert len([k for k in statuses if statuses[k].state == JobState.PENDING]) == n_blocks
|
97
|
+
# this assert fails due to issue #3696
|
98
|
+
|
99
|
+
# Now check scale in happens with 0 load
|
100
|
+
executor.outstanding = 0
|
101
|
+
s.strategize([executor])
|
102
|
+
executor.scale_in_facade.assert_called()
|
103
|
+
assert len([k for k in statuses if statuses[k].state == JobState.PENDING]) == 0
|
@@ -4,13 +4,11 @@ import time
|
|
4
4
|
import pytest
|
5
5
|
|
6
6
|
import parsl
|
7
|
-
|
8
7
|
from parsl import File, python_app
|
9
|
-
from parsl.providers import LocalProvider
|
10
|
-
from parsl.channels import LocalChannel
|
11
|
-
from parsl.launchers import SingleNodeLauncher
|
12
8
|
from parsl.config import Config
|
13
9
|
from parsl.executors import HighThroughputExecutor
|
10
|
+
from parsl.launchers import SingleNodeLauncher
|
11
|
+
from parsl.providers import LocalProvider
|
14
12
|
|
15
13
|
logger = logging.getLogger(__name__)
|
16
14
|
|
@@ -30,7 +28,6 @@ def local_config():
|
|
30
28
|
max_workers_per_node=1,
|
31
29
|
encrypted=True,
|
32
30
|
provider=LocalProvider(
|
33
|
-
channel=LocalChannel(),
|
34
31
|
init_blocks=0,
|
35
32
|
max_blocks=_max_blocks,
|
36
33
|
min_blocks=_min_blocks,
|
@@ -1,15 +1,13 @@
|
|
1
|
+
from threading import Event
|
2
|
+
|
1
3
|
import pytest
|
2
4
|
|
3
5
|
import parsl
|
4
|
-
|
5
6
|
from parsl import File, python_app
|
6
|
-
from parsl.providers import LocalProvider
|
7
|
-
from parsl.channels import LocalChannel
|
8
|
-
from parsl.launchers import SingleNodeLauncher
|
9
7
|
from parsl.config import Config
|
10
8
|
from parsl.executors import HighThroughputExecutor
|
11
|
-
|
12
|
-
from
|
9
|
+
from parsl.launchers import SingleNodeLauncher
|
10
|
+
from parsl.providers import LocalProvider
|
13
11
|
|
14
12
|
_max_blocks = 5
|
15
13
|
_min_blocks = 0
|
@@ -24,10 +22,9 @@ def local_config():
|
|
24
22
|
poll_period=100,
|
25
23
|
label="htex_local",
|
26
24
|
address="127.0.0.1",
|
27
|
-
|
25
|
+
max_workers_per_node=1,
|
28
26
|
encrypted=True,
|
29
27
|
provider=LocalProvider(
|
30
|
-
channel=LocalChannel(),
|
31
28
|
init_blocks=0,
|
32
29
|
max_blocks=_max_blocks,
|
33
30
|
min_blocks=_min_blocks,
|