bec-server 4.3.1__tar.gz → 4.3.2__tar.gz
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.
- {bec_server-4.3.1 → bec_server-4.3.2}/PKG-INFO +1 -1
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/constants.py +21 -2
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/container_utils.py +71 -3
- bec_server-4.3.2/bec_server/procedures/container_worker.py +152 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/manager.py +66 -126
- bec_server-4.3.2/bec_server/procedures/oop_worker_base.py +603 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/protocol.py +11 -0
- bec_server-4.3.2/bec_server/procedures/queue_manager.py +598 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/subprocess_worker.py +10 -61
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/worker_base.py +27 -4
- {bec_server-4.3.1 → bec_server-4.3.2}/pyproject.toml +1 -1
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_container_utils.py +58 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_procedure_container_worker.py +24 -0
- bec_server-4.3.2/tests/tests_scan_server/test_procedure_queue_pool.py +608 -0
- bec_server-4.3.2/tests/tests_scan_server/test_procedure_submission.py +137 -0
- bec_server-4.3.2/tests/tests_scan_server/test_procedure_subprocess_worker.py +490 -0
- bec_server-4.3.2/tests/tests_scan_server/test_procedures.py +313 -0
- bec_server-4.3.1/bec_server/procedures/container_worker.py +0 -78
- bec_server-4.3.1/bec_server/procedures/oop_worker_base.py +0 -266
- bec_server-4.3.1/tests/tests_scan_server/test_procedure_subprocess_worker.py +0 -121
- bec_server-4.3.1/tests/tests_scan_server/test_procedures.py +0 -772
- {bec_server-4.3.1 → bec_server-4.3.2}/.gitignore +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/.zed/debug.json +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/README.md +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/actors/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/actors/actor.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/actors/builtin_actor_manager.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/actors/manager.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/actors/scan_interlock.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/actors/worker.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/bec_server_utils/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/bec_server_utils/launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/bec_server_utils/service_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/bec_server_utils/subprocess_launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/bec_server_utils/tmux_launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/cli/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/cli/launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/dap_server.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/dap_service.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/dap_service_manager.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/lmfit1d_service.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/bec_message_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/cli/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/cli/launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/device_server.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/devices/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/devices/config_update_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/devices/device_serializer.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/devices/devicemanager.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/friendly_device_exceptions.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/ophyd_callback_monitor.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/rpc_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/tests/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/tests/utils.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/async_writer.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/cli/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/cli/launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/default_writer.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/file_writer.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/file_writer_manager.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/merged_dicts.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/Containerfile.bec_linux_base +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/Containerfile.requirements +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/Containerfile.worker +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/_dev_runner.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/builtin_procedures.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/procedure_registry.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_bundler/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_bundler/bec_emitter.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_bundler/cli/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_bundler/cli/launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_bundler/emitter.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_bundler/scan_bundler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/beamline_state_manager.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/cli/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/cli/launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/device_lock_registry.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/direct_scan_worker.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/errors.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/instruction_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scan_assembler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scan_guard.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scan_manager.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scan_queue.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scan_server.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scan_worker.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/acquire.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/cont_line_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/device_rpc.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/fermat_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/grid_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/hexagonal_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/line_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/line_sweep_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/list_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/log_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/move.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/multi_region_grid_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/multi_region_line_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/path_optimization.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/position_generators.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/round_roi_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/round_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/scan_actions.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/scan_argument_modifier.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/scan_base.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/scan_components.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/scan_modifier.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/scan_status.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/time_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/updated_move.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/tests/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/tests/fixtures.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/tests/scan_fixtures.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/tests/scan_hook_tests.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/tests/utils.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/atlas_active_client_emitter.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/atlas_connector.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/atlas_forwarder.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/atlas_metadata_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/atlas_plugin_repo_emitter.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/config_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/cli/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/cli/launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/scihub.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/service_handler/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/service_handler/service_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/test/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/test/actor_test_utils.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/test/helpers.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_actors/test_actors.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_bec_server_utils/test_main.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_bec_server_utils/test_service_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_bec_server_utils/test_subprocess_launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_bec_server_utils/test_tmux_launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_data_processing/conftest.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_data_processing/test_dap_cli_launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_data_processing/test_dap_server.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_data_processing/test_dap_service_manager.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_data_processing/test_lmfit1d_service.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/conftest.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_config_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_device_instructions.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_device_manager_ds.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_device_serializer.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_device_server.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_device_server_cli_launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_ophyd_callback_monitor.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_rpc_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_file_writer/conftest.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_file_writer/test_async_file_writer.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_file_writer/test_file_writer.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_file_writer/test_file_writer_cli_launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_file_writer/test_file_writer_manager.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_bundler/conftest.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_bundler/test_bec_emitter.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_bundler/test_scan_bundler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_bundler/test_scan_bundler_cli_launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/conftest.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/__init__.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_acquire.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_cont_line_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_device_rpc_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_fermat_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_grid_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_hexagonal_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_line_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_line_sweep_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_list_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_log_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_move_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_multi_region_grid_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_multi_region_line_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_position_preview.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_relative_scan_return_to_start.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_round_roi_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_round_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_scan_actions.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_scan_components.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_scan_status.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_time_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_updated_move_scan.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_beamline_state_manager.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_builtin_actor_manager.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_device_lock_registry.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_direct_scan_worker.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_instruction_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_path_optimization.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_argument_modifier.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_assembler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_fixture_imports.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_guard.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_interlock.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_modifier.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_server_cli_launch.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_server_queue.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_server_scan_manager.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_worker.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/conftest.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_atlas_active_client_emitter.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_atlas_config_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_atlas_connector.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_atlas_forwarder.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_atlas_metadata_handler.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_atlas_plugin_repo_emitter.py +0 -0
- {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_scihub_cli_launch.py +0 -0
|
@@ -18,6 +18,7 @@ class OopWorkerEnv(TypedDict):
|
|
|
18
18
|
queue: str
|
|
19
19
|
timeout_s: str
|
|
20
20
|
client_class: NotRequired[BecClientType]
|
|
21
|
+
worker_id: NotRequired[str]
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
P = ParamSpec("P")
|
|
@@ -33,7 +34,25 @@ class BecProcedure(Protocol[P]):
|
|
|
33
34
|
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None: ...
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
class
|
|
37
|
+
class ProcedureWorkerFailure(str, Enum):
|
|
38
|
+
"""Fixed, credential-free descriptions suitable for public execution status."""
|
|
39
|
+
|
|
40
|
+
STARTUP = "Procedure worker startup failed"
|
|
41
|
+
AUTHENTICATION = "Procedure worker authentication failed"
|
|
42
|
+
EXITED = "Procedure worker exited unexpectedly"
|
|
43
|
+
INPUT_CLOSED = "Procedure worker input closed unexpectedly"
|
|
44
|
+
ABORTED = "Procedure worker was aborted"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ProcedureWorkerError(RuntimeError):
|
|
48
|
+
"""Worker failure with an optional safe category for public status."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, *args: object, failure: ProcedureWorkerFailure | None = None) -> None:
|
|
51
|
+
"""Default to the public failure description unless internal details are supplied."""
|
|
52
|
+
if not args and failure is not None:
|
|
53
|
+
args = (failure.value,)
|
|
54
|
+
super().__init__(*args)
|
|
55
|
+
self.failure = failure
|
|
37
56
|
|
|
38
57
|
|
|
39
58
|
class WorkerAlreadyExists(ProcedureWorkerError): ...
|
|
@@ -48,7 +67,7 @@ class NoImage(ProcedureWorkerError): ...
|
|
|
48
67
|
@dataclass(frozen=True)
|
|
49
68
|
class _WORKER:
|
|
50
69
|
MAX_WORKERS = 10
|
|
51
|
-
QUEUE_TIMEOUT_S =
|
|
70
|
+
QUEUE_TIMEOUT_S = 60
|
|
52
71
|
DEFAULT_QUEUE = "primary"
|
|
53
72
|
|
|
54
73
|
|
|
@@ -10,7 +10,7 @@ from typing import Iterator, Literal, cast
|
|
|
10
10
|
|
|
11
11
|
from podman import PodmanClient
|
|
12
12
|
from podman.domain.containers import Container
|
|
13
|
-
from podman.errors import APIError
|
|
13
|
+
from podman.errors import APIError, NotFound
|
|
14
14
|
|
|
15
15
|
from bec_lib.logger import bec_logger
|
|
16
16
|
from bec_server.procedures.constants import (
|
|
@@ -81,6 +81,67 @@ class _PodmanUtilsBase(ContainerCommandBackend):
|
|
|
81
81
|
tag=f"{PROCEDURE.CONTAINER.IMAGE_NAME}:v{PROCEDURE.BEC_VERSION}",
|
|
82
82
|
)
|
|
83
83
|
|
|
84
|
+
def run_interactive(
|
|
85
|
+
self,
|
|
86
|
+
image_tag: str,
|
|
87
|
+
environment: OopWorkerEnv,
|
|
88
|
+
volumes: list[VolumeSpec],
|
|
89
|
+
command: str,
|
|
90
|
+
pod_name: str | None = None,
|
|
91
|
+
container_name: str | None = None,
|
|
92
|
+
) -> subprocess.Popen:
|
|
93
|
+
"""Attach process pipes so private worker bootstrap never enters Podman metadata.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
image_tag (str): Worker image.
|
|
97
|
+
environment (OopWorkerEnv): Non-secret worker settings.
|
|
98
|
+
volumes (list[VolumeSpec]): Code mounts.
|
|
99
|
+
command (str): Worker entry point.
|
|
100
|
+
pod_name (str | None): Existing Podman pod.
|
|
101
|
+
container_name (str | None): Unique worker container name.
|
|
102
|
+
Returns:
|
|
103
|
+
subprocess.Popen: Attached process with stdin and separate output pipes.
|
|
104
|
+
"""
|
|
105
|
+
prefix = self._cli_prefix()
|
|
106
|
+
args = [*prefix, "create", "-i"]
|
|
107
|
+
args.extend(_multi_args_from_dict("-e", environment))
|
|
108
|
+
for volume in volumes:
|
|
109
|
+
args.extend(
|
|
110
|
+
[
|
|
111
|
+
"-v",
|
|
112
|
+
f"{volume['source']}:{volume['target']}{':ro' if volume['read_only'] else ''}",
|
|
113
|
+
]
|
|
114
|
+
)
|
|
115
|
+
if pod_name:
|
|
116
|
+
args.extend(["--pod", pod_name])
|
|
117
|
+
if container_name:
|
|
118
|
+
args.extend(["--name", container_name])
|
|
119
|
+
# Create synchronously: cancelling the attached start cannot leave a later
|
|
120
|
+
# `podman run` creating an orphan container after cleanup has returned.
|
|
121
|
+
container_id = _run_and_capture_error(*args, image_tag, command).stdout.decode().strip()
|
|
122
|
+
try:
|
|
123
|
+
return subprocess.Popen(
|
|
124
|
+
[*prefix, "start", "--attach", "--interactive", container_id],
|
|
125
|
+
stdin=subprocess.PIPE,
|
|
126
|
+
stdout=subprocess.PIPE,
|
|
127
|
+
stderr=subprocess.PIPE,
|
|
128
|
+
bufsize=0,
|
|
129
|
+
)
|
|
130
|
+
except BaseException:
|
|
131
|
+
self.remove(container_id)
|
|
132
|
+
raise
|
|
133
|
+
|
|
134
|
+
def _cli_prefix(self) -> list[str]:
|
|
135
|
+
return ["podman", "--url", self.uri] if isinstance(self, PodmanApiUtils) else ["podman"]
|
|
136
|
+
|
|
137
|
+
def remove(self, id: str) -> None:
|
|
138
|
+
"""Remove a stopped or starting worker container, preventing a later start.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
id (str): Container name or ID.
|
|
142
|
+
"""
|
|
143
|
+
_run_and_capture_error(*self._cli_prefix(), "rm", "--force", "--ignore", id)
|
|
144
|
+
|
|
84
145
|
|
|
85
146
|
class PodmanApiOutput(ContainerCommandOutput):
|
|
86
147
|
def __init__(self, command_output: Iterator[bytes]):
|
|
@@ -157,7 +218,10 @@ class PodmanApiUtils(_PodmanUtilsBase):
|
|
|
157
218
|
|
|
158
219
|
def state(self, id: str) -> PodmanContainerStates | None:
|
|
159
220
|
with PodmanClient(base_url=self.uri) as client:
|
|
160
|
-
|
|
221
|
+
try:
|
|
222
|
+
status = client.containers.get(id).status
|
|
223
|
+
except NotFound:
|
|
224
|
+
return None
|
|
161
225
|
if status == "unknown":
|
|
162
226
|
return None
|
|
163
227
|
return PodmanContainerStates(status)
|
|
@@ -259,7 +323,11 @@ class PodmanCliUtils(_PodmanUtilsBase):
|
|
|
259
323
|
|
|
260
324
|
def state(self, id: str) -> PodmanContainerStates | None:
|
|
261
325
|
for container in self._podman_ls_json():
|
|
262
|
-
if
|
|
326
|
+
if (
|
|
327
|
+
container["Id"] == id
|
|
328
|
+
or container["Id"].startswith(id)
|
|
329
|
+
or id in container.get("Names", [])
|
|
330
|
+
):
|
|
263
331
|
if names := container.get("Names"):
|
|
264
332
|
logger.debug(f"Container {names[0]} status: {container['State']}")
|
|
265
333
|
return PodmanContainerStates(container["State"])
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from bec_lib.logger import bec_logger
|
|
4
|
+
from bec_lib.redis_connector import RedisConnector
|
|
5
|
+
from bec_server.procedures.constants import PROCEDURE, PodmanContainerStates
|
|
6
|
+
from bec_server.procedures.container_utils import get_backend
|
|
7
|
+
from bec_server.procedures.oop_worker_base import (
|
|
8
|
+
main, # temporarily needed before update of base image on release
|
|
9
|
+
)
|
|
10
|
+
from bec_server.procedures.oop_worker_base import PROCESS_TIMEOUT, OutOfProcessWorkerBase
|
|
11
|
+
from bec_server.procedures.protocol import ContainerCommandBackend
|
|
12
|
+
|
|
13
|
+
logger = bec_logger.logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ContainerProcedureWorker(OutOfProcessWorkerBase):
|
|
17
|
+
"""A worker which runs scripts in a container with a full BEC environment,
|
|
18
|
+
mounted from the filesystem, and only access to Redis"""
|
|
19
|
+
|
|
20
|
+
# The Podman client is a thin wrapper around the libpod API
|
|
21
|
+
# documented at https://docs.podman.io/en/latest/_static/api.html
|
|
22
|
+
# which is more detailed than the podman-py documentation
|
|
23
|
+
|
|
24
|
+
def _setup_execution_environment(self):
|
|
25
|
+
self._backend: ContainerCommandBackend = get_backend()
|
|
26
|
+
image_tag = f"{PROCEDURE.CONTAINER.IMAGE_NAME}:v{PROCEDURE.BEC_VERSION}"
|
|
27
|
+
self.container_name = (
|
|
28
|
+
f"bec_procedure_{PROCEDURE.BEC_VERSION}_{self._worker_id or self._queue}"
|
|
29
|
+
)
|
|
30
|
+
if not self._backend.image_exists(image_tag):
|
|
31
|
+
self._backend.build_worker_image()
|
|
32
|
+
environment = self._worker_environment() | self._additional_env
|
|
33
|
+
if self._worker_id is not None:
|
|
34
|
+
self._container_id = self.container_name
|
|
35
|
+
self._process = self._backend.run_interactive(
|
|
36
|
+
image_tag,
|
|
37
|
+
environment,
|
|
38
|
+
[
|
|
39
|
+
{
|
|
40
|
+
"source": str(PROCEDURE.CONTAINER.DEPLOYMENT_PATH),
|
|
41
|
+
"target": "/bec",
|
|
42
|
+
"type": "bind",
|
|
43
|
+
"read_only": True,
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
PROCEDURE.CONTAINER.COMMAND,
|
|
47
|
+
pod_name=PROCEDURE.CONTAINER.POD_NAME,
|
|
48
|
+
container_name=self.container_name,
|
|
49
|
+
)
|
|
50
|
+
output_conn = RedisConnector([self._redis_server], name="Procedure container output")
|
|
51
|
+
try:
|
|
52
|
+
if self._control_credentials is not None:
|
|
53
|
+
output_conn.authenticate(
|
|
54
|
+
username=self._control_credentials["username"] or "default",
|
|
55
|
+
password=self._control_credentials["token"],
|
|
56
|
+
)
|
|
57
|
+
self._start_output_capture(output_conn)
|
|
58
|
+
except BaseException:
|
|
59
|
+
output_conn.shutdown()
|
|
60
|
+
self._kill_process()
|
|
61
|
+
raise
|
|
62
|
+
return
|
|
63
|
+
self._container_id = self._backend.run(
|
|
64
|
+
image_tag,
|
|
65
|
+
environment,
|
|
66
|
+
[
|
|
67
|
+
{
|
|
68
|
+
"source": str(PROCEDURE.CONTAINER.DEPLOYMENT_PATH),
|
|
69
|
+
"target": "/bec",
|
|
70
|
+
"type": "bind",
|
|
71
|
+
"read_only": True,
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
PROCEDURE.CONTAINER.COMMAND,
|
|
75
|
+
pod_name=PROCEDURE.CONTAINER.POD_NAME,
|
|
76
|
+
container_name=self.container_name,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def _ending_or_ended(self):
|
|
80
|
+
if self._worker_id is not None:
|
|
81
|
+
return self._process.poll() is not None
|
|
82
|
+
return self._backend.state(self._container_id) in [
|
|
83
|
+
PodmanContainerStates.EXITED,
|
|
84
|
+
PodmanContainerStates.STOPPED,
|
|
85
|
+
PodmanContainerStates.STOPPING,
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
def _kill_process(self):
|
|
89
|
+
if self._worker_id is not None:
|
|
90
|
+
# The attached CLI can fail while the container is still running.
|
|
91
|
+
terminal = (
|
|
92
|
+
None,
|
|
93
|
+
PodmanContainerStates.CREATED,
|
|
94
|
+
PodmanContainerStates.EXITED,
|
|
95
|
+
PodmanContainerStates.STOPPED,
|
|
96
|
+
)
|
|
97
|
+
if self._backend.state(self.container_name) not in terminal:
|
|
98
|
+
self._backend.interrupt(self.container_name)
|
|
99
|
+
deadline = time.monotonic() + PROCESS_TIMEOUT
|
|
100
|
+
while time.monotonic() < deadline:
|
|
101
|
+
if self._backend.state(self.container_name) in terminal:
|
|
102
|
+
break
|
|
103
|
+
time.sleep(0.1)
|
|
104
|
+
else:
|
|
105
|
+
self._backend.kill(self.container_name)
|
|
106
|
+
deadline = time.monotonic() + PROCESS_TIMEOUT
|
|
107
|
+
while self._backend.state(self.container_name) not in terminal:
|
|
108
|
+
if time.monotonic() >= deadline:
|
|
109
|
+
raise RuntimeError("Procedure container failed to stop")
|
|
110
|
+
time.sleep(0.1)
|
|
111
|
+
# Creation completed before the launcher was started, so removal also
|
|
112
|
+
# prevents an attached start that has not reached the engine yet.
|
|
113
|
+
self._backend.remove(self.container_name)
|
|
114
|
+
if self._process.stdin is not None:
|
|
115
|
+
self._process.stdin.close()
|
|
116
|
+
try:
|
|
117
|
+
self._process.wait(PROCESS_TIMEOUT)
|
|
118
|
+
except Exception:
|
|
119
|
+
self._process.kill()
|
|
120
|
+
self._process.wait(PROCESS_TIMEOUT)
|
|
121
|
+
return
|
|
122
|
+
if not self._ending_or_ended():
|
|
123
|
+
self._backend.interrupt(self.container_name)
|
|
124
|
+
start = time.monotonic()
|
|
125
|
+
while time.monotonic() < start + PROCESS_TIMEOUT:
|
|
126
|
+
if self._ending_or_ended():
|
|
127
|
+
return
|
|
128
|
+
time.sleep(0.2)
|
|
129
|
+
logger.warning(
|
|
130
|
+
f"Procedure worker {self._container_id} for queue {self._queue} failed to shut down, killing."
|
|
131
|
+
)
|
|
132
|
+
self._backend.kill(self.container_name)
|
|
133
|
+
|
|
134
|
+
def abort_execution(self, execution_id: str):
|
|
135
|
+
"""Abort the execution with the given id. Has no effect if the given ID is not the current job"""
|
|
136
|
+
if execution_id == self._current_execution_id:
|
|
137
|
+
self._backend.kill(self._container_id)
|
|
138
|
+
self._helper.remove_from_active.by_exec_id(execution_id)
|
|
139
|
+
logger.info(
|
|
140
|
+
f"Aborting execution {execution_id}, restarting worker for queue: {self._queue}"
|
|
141
|
+
)
|
|
142
|
+
self._setup_execution_environment()
|
|
143
|
+
|
|
144
|
+
def logs(self):
|
|
145
|
+
if self._worker_id is not None:
|
|
146
|
+
if self._process.poll() is not None:
|
|
147
|
+
self._join_output()
|
|
148
|
+
with self._output_lock:
|
|
149
|
+
return "".join(self._output_logs).splitlines()
|
|
150
|
+
if self._container_id is None:
|
|
151
|
+
return [""]
|
|
152
|
+
return self._backend.logs(self._container_id)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import atexit
|
|
4
|
+
import re
|
|
4
5
|
from abc import ABC, abstractmethod
|
|
5
6
|
from collections import deque
|
|
6
7
|
from concurrent import futures
|
|
7
8
|
from concurrent.futures import Future, ThreadPoolExecutor
|
|
8
9
|
from threading import RLock
|
|
9
|
-
from typing import TYPE_CHECKING, Any, Callable, Generic, Protocol, TypedDict, TypeVar
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Callable, Generic, NotRequired, Protocol, TypedDict, TypeVar
|
|
10
11
|
|
|
11
12
|
from pydantic import ValidationError
|
|
12
13
|
|
|
@@ -15,7 +16,6 @@ from bec_lib.logger import bec_logger
|
|
|
15
16
|
from bec_lib.messages import (
|
|
16
17
|
AvailableResourceMessage,
|
|
17
18
|
BECMessage,
|
|
18
|
-
ProcedureAbortMessage,
|
|
19
19
|
ProcedureClearUnhandledMessage,
|
|
20
20
|
ProcedureExecutionMessage,
|
|
21
21
|
ProcedureQNotifMessage,
|
|
@@ -23,10 +23,10 @@ from bec_lib.messages import (
|
|
|
23
23
|
ProcedureWorkerStatus,
|
|
24
24
|
RequestResponseMessage,
|
|
25
25
|
)
|
|
26
|
-
from bec_lib.procedures.helper import BackendProcedureHelper
|
|
27
26
|
from bec_lib.redis_connector import MessageObject, RedisConnector
|
|
28
27
|
from bec_server.procedures import procedure_registry
|
|
29
28
|
from bec_server.procedures.constants import PROCEDURE, WorkerAlreadyExists
|
|
29
|
+
from bec_server.procedures.queue_manager import QueueWorkerPools
|
|
30
30
|
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
32
|
from bec_server.procedures.worker_base import ProcedureWorker
|
|
@@ -37,6 +37,7 @@ logger = bec_logger.logger
|
|
|
37
37
|
class ProcedureWorkerEntry(TypedDict):
|
|
38
38
|
worker: ProcedureWorker | None
|
|
39
39
|
future: Future
|
|
40
|
+
abort_requested: NotRequired[bool]
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
class RedisCredentials(TypedDict):
|
|
@@ -75,6 +76,8 @@ class ProcedureManagerBase(ABC, Generic[_ReqMsgT, _ExecMsgT]):
|
|
|
75
76
|
worker_type: type[ProcedureWorker],
|
|
76
77
|
thread_prefix: str = "user_procedure_",
|
|
77
78
|
redis_credentials: RedisCredentials | None = None,
|
|
79
|
+
*,
|
|
80
|
+
_defer_startup: bool = False,
|
|
78
81
|
):
|
|
79
82
|
"""Watches the request queue and pushes to worker execution queues. Manages
|
|
80
83
|
instantiating and cleaning up after workers.
|
|
@@ -114,8 +117,9 @@ class ProcedureManagerBase(ABC, Generic[_ReqMsgT, _ExecMsgT]):
|
|
|
114
117
|
self._server = f"{self._conn.host}:{self._conn.port}"
|
|
115
118
|
|
|
116
119
|
self._reply_ep: EndpointInfo
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
if not _defer_startup:
|
|
121
|
+
self._register_endpoints()
|
|
122
|
+
self._publish_available()
|
|
119
123
|
logger.success("Done initialising procedure manager.")
|
|
120
124
|
|
|
121
125
|
def _ack(self, accepted: bool, msg: str, exec_id: str):
|
|
@@ -135,7 +139,7 @@ class ProcedureManagerBase(ABC, Generic[_ReqMsgT, _ExecMsgT]):
|
|
|
135
139
|
Args:
|
|
136
140
|
msg (dict[str, Any]): dict corresponding to a ProcedureRequestMessage"""
|
|
137
141
|
|
|
138
|
-
logger.debug(f"Procedure manager got request
|
|
142
|
+
logger.debug(f"Procedure manager got queue request on topic {msg.topic}")
|
|
139
143
|
if (message := self._validate_request(msg.value)) is None:
|
|
140
144
|
return
|
|
141
145
|
exec_msg = self._respond_to_valid_request(message)
|
|
@@ -164,8 +168,13 @@ class ProcedureManagerBase(ABC, Generic[_ReqMsgT, _ExecMsgT]):
|
|
|
164
168
|
self._server, queue, PROCEDURE.WORKER.QUEUE_TIMEOUT_S, env
|
|
165
169
|
) as worker:
|
|
166
170
|
with self.lock:
|
|
167
|
-
self._active_workers[queue]
|
|
168
|
-
|
|
171
|
+
entry = self._active_workers[queue]
|
|
172
|
+
entry["worker"] = worker
|
|
173
|
+
abort_requested = entry.get("abort_requested", False)
|
|
174
|
+
if abort_requested:
|
|
175
|
+
worker.abort()
|
|
176
|
+
else:
|
|
177
|
+
worker.work()
|
|
169
178
|
finally:
|
|
170
179
|
if worker is not None:
|
|
171
180
|
logs = worker.logs()
|
|
@@ -173,9 +182,22 @@ class ProcedureManagerBase(ABC, Generic[_ReqMsgT, _ExecMsgT]):
|
|
|
173
182
|
for line in logs:
|
|
174
183
|
logger.info(f"Procedure worker '{queue}': {line}")
|
|
175
184
|
|
|
176
|
-
def
|
|
185
|
+
def _abort_workers(
|
|
186
|
+
self, entries: list[ProcedureWorkerEntry], before_wait: Callable[[], None] | None = None
|
|
187
|
+
) -> None:
|
|
188
|
+
"""Stop selected workers, optionally draining queues before waiting for startup."""
|
|
177
189
|
with self.lock:
|
|
178
|
-
|
|
190
|
+
for entry in entries:
|
|
191
|
+
entry["abort_requested"] = True
|
|
192
|
+
workers = [entry["worker"] for entry in entries if entry["worker"] is not None]
|
|
193
|
+
for entry in entries:
|
|
194
|
+
entry["future"].cancel()
|
|
195
|
+
for worker in workers:
|
|
196
|
+
worker.abort()
|
|
197
|
+
if before_wait is not None:
|
|
198
|
+
before_wait()
|
|
199
|
+
# A cancelled future need not have been picked up by an executor thread yet.
|
|
200
|
+
futs = [entry["future"] for entry in entries if not entry["future"].cancelled()]
|
|
179
201
|
futures.wait(futs, timeout=PROCEDURE.MANAGER_SHUTDOWN_TIMEOUT_S)
|
|
180
202
|
|
|
181
203
|
def active_workers(self) -> list[str]:
|
|
@@ -204,16 +226,9 @@ class ProcedureManagerBase(ABC, Generic[_ReqMsgT, _ExecMsgT]):
|
|
|
204
226
|
logger.debug(f"shutting down {self.__class__.__name__}")
|
|
205
227
|
self._unregister_endpoints()
|
|
206
228
|
self._conn.shutdown()
|
|
207
|
-
# cancel futures by hand to give us the opportunity to detach them from redis if they have started
|
|
208
229
|
with self.lock:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if not cancelled:
|
|
212
|
-
# unblock any waiting workers and let them shutdown
|
|
213
|
-
if worker := entry["worker"]:
|
|
214
|
-
# redis unblock executor.client_id
|
|
215
|
-
worker.abort()
|
|
216
|
-
self._wait_for_all_futures()
|
|
230
|
+
entries = list(self._active_workers.values())
|
|
231
|
+
self._abort_workers(entries)
|
|
217
232
|
self.executor.shutdown()
|
|
218
233
|
|
|
219
234
|
@abstractmethod
|
|
@@ -252,24 +267,15 @@ class ProcedureManagerBase(ABC, Generic[_ReqMsgT, _ExecMsgT]):
|
|
|
252
267
|
"""Returns a function which will be attached as a callback to finishing workers."""
|
|
253
268
|
|
|
254
269
|
|
|
255
|
-
class ProcedureManager(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
redis: str,
|
|
259
|
-
worker_type: type[ProcedureWorker],
|
|
260
|
-
thread_prefix: str = "user_procedure_",
|
|
261
|
-
redis_credentials: RedisCredentials | None = None,
|
|
262
|
-
):
|
|
263
|
-
super().__init__(redis, worker_type, thread_prefix, redis_credentials)
|
|
264
|
-
self._messages_by_ids: dict[str, ProcedureExecutionMessage] = {}
|
|
265
|
-
self._helper = BackendProcedureHelper(self._conn, monitor_responses=False)
|
|
266
|
-
self._startup()
|
|
270
|
+
class ProcedureManager(
|
|
271
|
+
QueueWorkerPools, ProcedureManagerBase[ProcedureRequestMessage, ProcedureExecutionMessage]
|
|
272
|
+
):
|
|
267
273
|
|
|
268
274
|
def _define_endpoints(self):
|
|
269
275
|
self._reply_ep = MessageEndpoints.procedure_request_response()
|
|
270
276
|
self._abort_ep = MessageEndpoints.procedure_abort()
|
|
271
277
|
self._clear_uh_ep = MessageEndpoints.procedure_clear_unhandled()
|
|
272
|
-
self._request_ep = MessageEndpoints.procedure_request()
|
|
278
|
+
self._request_ep = MessageEndpoints.procedure_request("*")
|
|
273
279
|
self._q_notif_ep = MessageEndpoints.procedure_queue_notif()
|
|
274
280
|
self._exec_ep = MessageEndpoints.procedure_execution
|
|
275
281
|
|
|
@@ -287,7 +293,7 @@ class ProcedureManager(ProcedureManagerBase[ProcedureRequestMessage, ProcedureEx
|
|
|
287
293
|
def _register_endpoints(self):
|
|
288
294
|
self._conn.register(self._abort_ep, None, self._process_abort)
|
|
289
295
|
self._conn.register(self._clear_uh_ep, None, self._process_clear_unhandled)
|
|
290
|
-
self._conn.register(self._request_ep,
|
|
296
|
+
self._conn.register(patterns=self._request_ep, cb=self._procedure_request_callback)
|
|
291
297
|
self._conn.register(self._q_notif_ep, None, self._publish_metrics)
|
|
292
298
|
self._conn.set(
|
|
293
299
|
MessageEndpoints.available_procedures(),
|
|
@@ -299,33 +305,32 @@ class ProcedureManager(ProcedureManagerBase[ProcedureRequestMessage, ProcedureEx
|
|
|
299
305
|
),
|
|
300
306
|
)
|
|
301
307
|
|
|
302
|
-
def
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return
|
|
308
|
+
def _procedure_request_callback(self, msg: MessageObject):
|
|
309
|
+
"""Validate the personal topic against the submitted ACL identity before enqueueing."""
|
|
310
|
+
message = msg.value
|
|
311
|
+
if not isinstance(message, ProcedureRequestMessage):
|
|
312
|
+
logger.warning("Received an invalid procedure request.")
|
|
313
|
+
return
|
|
314
|
+
client_info = message.metadata.get("client_info")
|
|
315
|
+
token = client_info.pop("acl_token", None) if isinstance(client_info, dict) else None
|
|
316
|
+
if token is not None and not isinstance(token, str):
|
|
317
|
+
self._ack(False, "Invalid procedure ACL token type.", message.execution_id)
|
|
318
|
+
return
|
|
319
|
+
username_regex = MessageEndpoints.procedure_request("([^/]+)").endpoint
|
|
320
|
+
match = re.fullmatch(username_regex, msg.topic)
|
|
321
|
+
if not match:
|
|
322
|
+
self._ack(False, "Could not extract username from topic.", message.execution_id)
|
|
323
|
+
return
|
|
324
|
+
if not isinstance(client_info, dict) or not client_info:
|
|
325
|
+
self._ack(False, "Missing client info in request metadata.", message.execution_id)
|
|
326
|
+
return
|
|
327
|
+
acl_user = client_info.get("acl_user") or "default"
|
|
328
|
+
if acl_user != match.group(1):
|
|
329
|
+
self._ack(False, "Username in topic does not match client info.", message.execution_id)
|
|
330
|
+
return
|
|
331
|
+
return self._process_queue_request(msg, token=token)
|
|
326
332
|
|
|
327
333
|
def _respond_to_valid_request(self, message: ProcedureRequestMessage):
|
|
328
|
-
self._ack(True, f"Running procedure {message.identifier}", message.execution_id)
|
|
329
334
|
queue = message.queue or PROCEDURE.WORKER.DEFAULT_QUEUE
|
|
330
335
|
exec_message = ProcedureExecutionMessage(
|
|
331
336
|
identifier=message.identifier,
|
|
@@ -333,16 +338,9 @@ class ProcedureManager(ProcedureManagerBase[ProcedureRequestMessage, ProcedureEx
|
|
|
333
338
|
args_kwargs=message.args_kwargs or ((), {}),
|
|
334
339
|
execution_id=message.execution_id,
|
|
335
340
|
)
|
|
336
|
-
logger.debug(f"
|
|
337
|
-
self._helper.push.exec(queue, exec_message)
|
|
341
|
+
logger.debug(f"Procedure submitted to logical queue {queue}")
|
|
338
342
|
return exec_message
|
|
339
343
|
|
|
340
|
-
def _process_queue_request(self, msg: MessageObject):
|
|
341
|
-
with self.lock:
|
|
342
|
-
if (exec_msg := super()._process_queue_request(msg)) is not None:
|
|
343
|
-
self._messages_by_ids[exec_msg.execution_id] = exec_msg
|
|
344
|
-
return exec_msg
|
|
345
|
-
|
|
346
344
|
def _publish_metrics(self, msg: ProcedureQNotifMessage):
|
|
347
345
|
self._conn.publish_metrics(
|
|
348
346
|
"pending_procedure_queues",
|
|
@@ -385,65 +383,6 @@ class ProcedureManager(ProcedureManagerBase[ProcedureRequestMessage, ProcedureEx
|
|
|
385
383
|
self._callbacks[queue] = []
|
|
386
384
|
self._callbacks[queue].append(cb)
|
|
387
385
|
|
|
388
|
-
def _run_callbacks(self, queue: str):
|
|
389
|
-
with self.lock:
|
|
390
|
-
if queue not in self._active_workers:
|
|
391
|
-
logger.error(f"Attempted to run callbacks for nonexistent worker {queue}")
|
|
392
|
-
return
|
|
393
|
-
if (worker := self._active_workers[queue]["worker"]) is None:
|
|
394
|
-
return
|
|
395
|
-
for cb in self._callbacks.get(queue, []):
|
|
396
|
-
cb(worker)
|
|
397
|
-
self._callbacks[queue] = []
|
|
398
|
-
|
|
399
|
-
def _process_abort(self, msg: MessageObject):
|
|
400
|
-
message: ProcedureAbortMessage = msg.value # type: ignore
|
|
401
|
-
with self.lock:
|
|
402
|
-
if message.abort_all:
|
|
403
|
-
self._abort_all()
|
|
404
|
-
if message.queue is not None:
|
|
405
|
-
self._abort_queue(message.queue)
|
|
406
|
-
if message.execution_id is not None:
|
|
407
|
-
self._abort_execution(message.execution_id)
|
|
408
|
-
|
|
409
|
-
def _abort_execution(self, execution_id: str):
|
|
410
|
-
if (msg := self._messages_by_ids.get(execution_id)) is None:
|
|
411
|
-
logger.warning(f"Procedure execution with ID {execution_id} not known.")
|
|
412
|
-
return
|
|
413
|
-
# Remove it from the queue if not yet started
|
|
414
|
-
if self._conn.lrem(MessageEndpoints.procedure_execution(msg.queue), 0, msg) > 0:
|
|
415
|
-
logger.debug(f"Removed execution {msg} from queue.")
|
|
416
|
-
self._helper.notify_watchers(msg.queue, "execution")
|
|
417
|
-
# Otherwise try to remove it from whichever worker has it
|
|
418
|
-
for entry in self._active_workers.values():
|
|
419
|
-
if (worker := entry["worker"]) is not None and worker:
|
|
420
|
-
worker.abort_execution(execution_id)
|
|
421
|
-
# Move it to unhandled and stop tracking
|
|
422
|
-
self._helper.push.unhandled(msg.queue, msg)
|
|
423
|
-
del self._messages_by_ids[execution_id]
|
|
424
|
-
|
|
425
|
-
def _abort_queue(self, queue: str):
|
|
426
|
-
self._helper.move.execution_queue_to_unhandled(queue)
|
|
427
|
-
if (entry := self._active_workers.get(queue)) is not None:
|
|
428
|
-
if entry["worker"] is not None:
|
|
429
|
-
entry["worker"].abort()
|
|
430
|
-
entry["future"].cancel()
|
|
431
|
-
futures.wait((entry["future"],), PROCEDURE.MANAGER_SHUTDOWN_TIMEOUT_S)
|
|
432
|
-
else:
|
|
433
|
-
logger.warning(f"Received abort request for unknown queue {queue}!")
|
|
434
|
-
self._helper.notify_watchers(queue, "execution")
|
|
435
|
-
|
|
436
|
-
def _abort_all(self):
|
|
437
|
-
with self.lock:
|
|
438
|
-
entries = list(self._active_workers.values())
|
|
439
|
-
for entry in entries:
|
|
440
|
-
if entry["worker"] is not None:
|
|
441
|
-
entry["worker"].abort()
|
|
442
|
-
for entry in entries:
|
|
443
|
-
entry["future"].cancel()
|
|
444
|
-
self._helper.move.all_execution_queues_to_unhandled()
|
|
445
|
-
self._wait_for_all_futures()
|
|
446
|
-
|
|
447
386
|
def _process_clear_unhandled(self, msg: MessageObject):
|
|
448
387
|
message: ProcedureClearUnhandledMessage = msg.value # type: ignore
|
|
449
388
|
with self.lock:
|
|
@@ -458,4 +397,5 @@ class ProcedureManager(ProcedureManagerBase[ProcedureRequestMessage, ProcedureEx
|
|
|
458
397
|
"""Remove any callbacks from any Redis endpoints"""
|
|
459
398
|
self._conn.unregister(self._abort_ep, None, self._process_abort)
|
|
460
399
|
self._conn.unregister(self._clear_uh_ep, None, self._process_clear_unhandled)
|
|
461
|
-
self._conn.unregister(self._request_ep,
|
|
400
|
+
self._conn.unregister(patterns=self._request_ep, cb=self._procedure_request_callback)
|
|
401
|
+
self._conn.unregister(self._q_notif_ep, None, self._publish_metrics)
|