parsl 2024.3.11__py3-none-any.whl → 2025.1.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- parsl/__init__.py +9 -10
- parsl/addresses.py +29 -7
- 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 +57 -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 +262 -224
- 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 +316 -282
- parsl/executors/high_throughput/interchange.py +158 -167
- parsl/executors/high_throughput/manager_record.py +5 -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 +115 -77
- 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 +41 -57
- parsl/executors/taskvine/factory.py +1 -1
- parsl/executors/taskvine/factory_config.py +1 -1
- parsl/executors/taskvine/manager.py +18 -13
- parsl/executors/taskvine/manager_config.py +9 -5
- 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 +30 -113
- 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 +6 -12
- parsl/log_utils.py +9 -6
- parsl/monitoring/db_manager.py +59 -95
- parsl/monitoring/errors.py +6 -0
- parsl/monitoring/monitoring.py +87 -356
- 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 +11 -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 -8
- 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 +4 -12
- 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 +2 -8
- 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 +79 -0
- 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 +86 -0
- 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 +6 -18
- 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 +139 -6
- parsl/version.py +1 -1
- {parsl-2024.3.11.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.11.data → parsl-2025.1.13.data}/scripts/process_worker_pool.py +115 -77
- parsl-2025.1.13.dist-info/METADATA +96 -0
- parsl-2025.1.13.dist-info/RECORD +462 -0
- {parsl-2024.3.11.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.11.dist-info/METADATA +0 -98
- parsl-2024.3.11.dist-info/RECORD +0 -447
- 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.11.data → parsl-2025.1.13.data}/scripts/parsl_coprocess.py +1 -1
- {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/LICENSE +0 -0
- {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/entry_points.txt +0 -0
- {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
"""Test usage_tracking values."""
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
import parsl
|
6
|
+
from parsl.config import Config
|
7
|
+
from parsl.errors import ConfigurationError
|
8
|
+
|
9
|
+
|
10
|
+
@pytest.mark.local
|
11
|
+
def test_config_load():
|
12
|
+
"""Test loading a config with usage tracking."""
|
13
|
+
with parsl.load(Config(usage_tracking=3)):
|
14
|
+
pass
|
15
|
+
parsl.clear()
|
16
|
+
|
17
|
+
|
18
|
+
@pytest.mark.local
|
19
|
+
@pytest.mark.parametrize("level", (0, 1, 2, 3, False, True))
|
20
|
+
def test_valid(level):
|
21
|
+
"""Test valid usage_tracking values."""
|
22
|
+
Config(usage_tracking=level)
|
23
|
+
assert Config(usage_tracking=level).usage_tracking == level
|
24
|
+
|
25
|
+
|
26
|
+
@pytest.mark.local
|
27
|
+
@pytest.mark.parametrize("level", (12, 1000, -1))
|
28
|
+
def test_invalid_values(level):
|
29
|
+
"""Test invalid usage_tracking values."""
|
30
|
+
with pytest.raises(ConfigurationError):
|
31
|
+
Config(usage_tracking=level)
|
32
|
+
|
33
|
+
|
34
|
+
@pytest.mark.local
|
35
|
+
@pytest.mark.parametrize("level", ("abcd", None, bytes(1), 1.0, 1j, object()))
|
36
|
+
def test_invalid_types(level):
|
37
|
+
"""Test invalid usage_tracking types."""
|
38
|
+
with pytest.raises(Exception) as ex:
|
39
|
+
Config(usage_tracking=level)
|
40
|
+
|
41
|
+
# with typeguard 4.x this is TypeCheckError,
|
42
|
+
# with typeguard 2.x this is TypeError
|
43
|
+
# we can't instantiate TypeCheckError if we're in typeguard 2.x environment
|
44
|
+
# because it does not exist... so check name using strings.
|
45
|
+
assert ex.type.__name__ in ["TypeCheckError", "TypeError"]
|
46
|
+
|
47
|
+
|
48
|
+
@pytest.mark.local
|
49
|
+
def test_valid_project_name():
|
50
|
+
"""Test valid project_name."""
|
51
|
+
assert (
|
52
|
+
Config(
|
53
|
+
usage_tracking=3,
|
54
|
+
project_name="unit-test",
|
55
|
+
).project_name == "unit-test"
|
56
|
+
)
|
57
|
+
|
58
|
+
|
59
|
+
@pytest.mark.local
|
60
|
+
@pytest.mark.parametrize("name", (1, 1.0, True, object()))
|
61
|
+
def test_invalid_project_name(name):
|
62
|
+
"""Test invalid project_name."""
|
63
|
+
with pytest.raises(Exception) as ex:
|
64
|
+
Config(usage_tracking=3, project_name=name)
|
65
|
+
|
66
|
+
assert ex.type.__name__ in ["TypeCheckError", "TypeError"]
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import inspect
|
2
|
+
from abc import abstractmethod
|
3
|
+
from functools import singledispatch
|
4
|
+
from typing import Any, List, Sequence
|
5
|
+
|
6
|
+
from parsl.utils import RepresentationMixin
|
7
|
+
|
8
|
+
# Traverse the configuration hierarchy, returning a JSON component
|
9
|
+
# for each one. Configuration components which implement
|
10
|
+
# RepresentationMixin will be in the right form for inspecting
|
11
|
+
# object attributes. Configuration components which are lists or tuples
|
12
|
+
# are traversed in sequence. Other types default to reporting no
|
13
|
+
# usage information.
|
14
|
+
|
15
|
+
|
16
|
+
@singledispatch
|
17
|
+
def get_parsl_usage(obj) -> List[Any]:
|
18
|
+
return []
|
19
|
+
|
20
|
+
|
21
|
+
@get_parsl_usage.register
|
22
|
+
def get_parsl_usage_representation_mixin(obj: RepresentationMixin) -> List[Any]:
|
23
|
+
t = type(obj)
|
24
|
+
qualified_name = t.__module__ + "." + t.__name__
|
25
|
+
|
26
|
+
# me can contain anything that can be rendered as JSON
|
27
|
+
me: List[Any] = []
|
28
|
+
|
29
|
+
if isinstance(obj, UsageInformation):
|
30
|
+
# report rich usage information for this component
|
31
|
+
attrs = {'c': qualified_name}
|
32
|
+
attrs.update(obj.get_usage_information())
|
33
|
+
me = [attrs]
|
34
|
+
else:
|
35
|
+
# report the class name of this component
|
36
|
+
me = [qualified_name]
|
37
|
+
|
38
|
+
# unwrap typeguard-style unwrapping
|
39
|
+
init: Any = type(obj).__init__
|
40
|
+
if hasattr(init, '__wrapped__'):
|
41
|
+
init = init.__wrapped__
|
42
|
+
|
43
|
+
argspec = inspect.getfullargspec(init)
|
44
|
+
|
45
|
+
for arg in argspec.args[1:]: # skip first arg, self
|
46
|
+
arg_value = getattr(obj, arg)
|
47
|
+
d = get_parsl_usage(arg_value)
|
48
|
+
me += d
|
49
|
+
|
50
|
+
return me
|
51
|
+
|
52
|
+
|
53
|
+
@get_parsl_usage.register(list)
|
54
|
+
@get_parsl_usage.register(tuple)
|
55
|
+
def get_parsl_usage_sequence(obj: Sequence) -> List[Any]:
|
56
|
+
result = []
|
57
|
+
for v in obj:
|
58
|
+
result += get_parsl_usage(v)
|
59
|
+
return result
|
60
|
+
|
61
|
+
|
62
|
+
class UsageInformation:
|
63
|
+
@abstractmethod
|
64
|
+
def get_usage_information(self) -> dict:
|
65
|
+
pass
|
@@ -0,0 +1,6 @@
|
|
1
|
+
"""Module for defining the usage tracking levels."""
|
2
|
+
|
3
|
+
DISABLED = 0 # Tracking is disabled
|
4
|
+
LEVEL_1 = 1 # Share info about Parsl version, Python version, platform
|
5
|
+
LEVEL_2 = 2 # Share info about config + level 1
|
6
|
+
LEVEL_3 = 3 # Share info about app count, app fails, execution time + level 2
|
parsl/usage_tracking/usage.py
CHANGED
@@ -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
|
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:
|
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(
|
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.
|
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.
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
128
|
-
if envvar == "false":
|
129
|
-
track = False
|
143
|
+
return self.config.usage_tracking
|
130
144
|
|
131
|
-
|
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 = {'
|
151
|
+
message = {'correlator': self.correlator_uuid,
|
140
152
|
'parsl_v': self.parsl_version,
|
141
153
|
'python_v': self.python_version,
|
142
|
-
'
|
143
|
-
'
|
144
|
-
|
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
|
-
|
167
|
+
logger.debug(f"Usage tracking start message: {message}")
|
147
168
|
|
148
|
-
|
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
|
-
|
178
|
+
end_time = int(time.time())
|
155
179
|
|
156
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
196
|
+
if self.project_name:
|
197
|
+
message['project_name'] = self.project_name
|
168
198
|
|
169
|
-
|
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
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
180
|
-
|
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
|
-
|
184
|
-
|
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.
|
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
|
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
|
137
|
+
raise pe.BadStdStreamFile(msg)
|
125
138
|
fname, mode = stdfspec
|
126
|
-
|
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
|
@@ -296,12 +317,12 @@ class Timer:
|
|
296
317
|
|
297
318
|
"""
|
298
319
|
|
299
|
-
def __init__(self, callback: Callable, *args: Any, interval: int = 5, name: Optional[str] = None) -> None:
|
320
|
+
def __init__(self, callback: Callable, *args: Any, interval: Union[float, int] = 5, name: Optional[str] = None) -> None:
|
300
321
|
"""Initialize the Timer object.
|
301
322
|
We start the timer thread here
|
302
323
|
|
303
324
|
KWargs:
|
304
|
-
- interval (int) : number of seconds between callback events
|
325
|
+
- interval (int or float) : number of seconds between callback events
|
305
326
|
- name (str) : a base name to use when naming the started thread
|
306
327
|
"""
|
307
328
|
|
@@ -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
@@ -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"]
|
97
|
+
func, args, kwargs = unpack_apply_message(function_info["byte code"])
|
97
98
|
return (func, 'parsl_function_name', args, kwargs)
|
98
99
|
|
99
100
|
|