bec-server 4.3.0__tar.gz → 4.3.1__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.0 → bec_server-4.3.1}/PKG-INFO +1 -1
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/actors/worker.py +5 -15
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/container_utils.py +3 -2
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/container_worker.py +1 -1
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/manager.py +32 -7
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/oop_worker_base.py +22 -4
- bec_server-4.3.1/bec_server/procedures/subprocess_worker.py +144 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/worker_base.py +17 -6
- {bec_server-4.3.0 → bec_server-4.3.1}/pyproject.toml +1 -1
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_actors/test_actors.py +27 -2
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_container_utils.py +35 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_procedure_container_worker.py +119 -2
- bec_server-4.3.1/tests/tests_scan_server/test_procedure_subprocess_worker.py +121 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_procedures.py +95 -1
- bec_server-4.3.0/bec_server/procedures/subprocess_worker.py +0 -73
- {bec_server-4.3.0 → bec_server-4.3.1}/.gitignore +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/.zed/debug.json +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/README.md +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/actors/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/actors/actor.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/actors/builtin_actor_manager.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/actors/manager.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/actors/scan_interlock.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/bec_server_utils/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/bec_server_utils/launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/bec_server_utils/service_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/bec_server_utils/subprocess_launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/bec_server_utils/tmux_launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/data_processing/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/data_processing/cli/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/data_processing/cli/launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/data_processing/dap_server.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/data_processing/dap_service.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/data_processing/dap_service_manager.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/data_processing/lmfit1d_service.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/bec_message_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/cli/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/cli/launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/device_server.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/devices/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/devices/config_update_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/devices/device_serializer.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/devices/devicemanager.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/friendly_device_exceptions.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/ophyd_callback_monitor.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/rpc_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/tests/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/device_server/tests/utils.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/file_writer/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/file_writer/async_writer.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/file_writer/cli/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/file_writer/cli/launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/file_writer/default_writer.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/file_writer/file_writer.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/file_writer/file_writer_manager.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/file_writer/merged_dicts.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/Containerfile.bec_linux_base +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/Containerfile.requirements +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/Containerfile.worker +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/_dev_runner.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/builtin_procedures.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/constants.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/procedure_registry.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/procedures/protocol.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_bundler/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_bundler/bec_emitter.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_bundler/cli/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_bundler/cli/launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_bundler/emitter.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_bundler/scan_bundler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/beamline_state_manager.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/cli/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/cli/launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/device_lock_registry.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/direct_scan_worker.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/errors.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/instruction_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scan_assembler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scan_guard.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scan_manager.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scan_queue.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scan_server.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scan_worker.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/acquire.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/cont_line_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/device_rpc.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/fermat_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/grid_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/hexagonal_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/line_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/line_sweep_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/list_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/log_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/move.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/multi_region_grid_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/multi_region_line_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/path_optimization.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/position_generators.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/round_roi_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/round_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/scan_actions.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/scan_argument_modifier.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/scan_base.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/scan_components.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/scan_modifier.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/scan_status.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/time_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/scans/updated_move.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/tests/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/tests/fixtures.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/tests/scan_fixtures.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/tests/scan_hook_tests.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scan_server/tests/utils.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/atlas/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/atlas/atlas_active_client_emitter.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/atlas/atlas_connector.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/atlas/atlas_forwarder.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/atlas/atlas_metadata_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/atlas/atlas_plugin_repo_emitter.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/atlas/config_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/cli/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/cli/launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/scihub.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/service_handler/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/scihub/service_handler/service_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/test/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/test/actor_test_utils.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/bec_server/test/helpers.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_bec_server_utils/test_main.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_bec_server_utils/test_service_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_bec_server_utils/test_subprocess_launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_bec_server_utils/test_tmux_launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_data_processing/conftest.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_data_processing/test_dap_cli_launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_data_processing/test_dap_server.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_data_processing/test_dap_service_manager.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_data_processing/test_lmfit1d_service.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_device_server/conftest.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_device_server/test_config_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_device_server/test_device_instructions.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_device_server/test_device_manager_ds.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_device_server/test_device_serializer.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_device_server/test_device_server.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_device_server/test_device_server_cli_launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_device_server/test_ophyd_callback_monitor.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_device_server/test_rpc_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_file_writer/conftest.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_file_writer/test_async_file_writer.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_file_writer/test_file_writer.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_file_writer/test_file_writer_cli_launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_file_writer/test_file_writer_manager.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_bundler/conftest.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_bundler/test_bec_emitter.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_bundler/test_scan_bundler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_bundler/test_scan_bundler_cli_launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/conftest.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/__init__.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_acquire.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_cont_line_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_device_rpc_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_fermat_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_grid_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_hexagonal_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_line_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_line_sweep_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_list_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_log_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_move_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_multi_region_grid_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_multi_region_line_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_position_preview.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_relative_scan_return_to_start.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_round_roi_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_round_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_scan_actions.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_scan_components.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_scan_status.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_time_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/scans/test_updated_move_scan.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_beamline_state_manager.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_builtin_actor_manager.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_device_lock_registry.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_direct_scan_worker.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_instruction_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_path_optimization.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_scan_argument_modifier.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_scan_assembler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_scan_fixture_imports.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_scan_guard.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_scan_interlock.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_scan_modifier.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_scan_server_cli_launch.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_scan_server_queue.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_scan_server_scan_manager.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_scan_worker.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scihub/conftest.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scihub/test_atlas_active_client_emitter.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scihub/test_atlas_config_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scihub/test_atlas_connector.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scihub/test_atlas_forwarder.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scihub/test_atlas_metadata_handler.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scihub/test_atlas_plugin_repo_emitter.py +0 -0
- {bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scihub/test_scihub_cli_launch.py +0 -0
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import os
|
|
3
|
-
from contextlib import redirect_stdout
|
|
4
3
|
from inspect import isclass
|
|
5
4
|
|
|
6
5
|
from bec_lib.client import BECClient
|
|
7
|
-
from bec_lib.logger import
|
|
6
|
+
from bec_lib.logger import bec_logger
|
|
8
7
|
from bec_lib.messages import ProcedureWorkerStatus
|
|
9
|
-
from bec_lib.redis_connector import RedisConnector
|
|
10
8
|
from bec_server.actors.actor import ActorBase
|
|
11
|
-
from bec_server.procedures.oop_worker_base import
|
|
9
|
+
from bec_server.procedures.oop_worker_base import get_env, push_status, setup
|
|
12
10
|
from bec_server.procedures.subprocess_worker import SubProcessWorker
|
|
13
11
|
|
|
14
12
|
logger = bec_logger.logger
|
|
@@ -53,20 +51,12 @@ def get_actor_env():
|
|
|
53
51
|
def main():
|
|
54
52
|
env, helper, client, conn = setup(get_env())
|
|
55
53
|
actor_env = get_actor_env()
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
with redirect_stdout(output_diverter):
|
|
59
|
-
logger.add(
|
|
60
|
-
output_diverter,
|
|
61
|
-
level=LogLevel.SUCCESS,
|
|
62
|
-
format=bec_logger.formatting(is_container=True),
|
|
63
|
-
filter=bec_logger.filter(),
|
|
64
|
-
)
|
|
65
|
-
logger.success(f"Starting ActorProcedureWorker with env: {actor_env}")
|
|
54
|
+
# The parent SubProcessWorker streams stdout and stderr to Redis.
|
|
55
|
+
logger.success(f"Starting ActorProcedureWorker with env: {actor_env}")
|
|
66
56
|
push_status(conn, env["queue"], ProcedureWorkerStatus.IDLE)
|
|
67
57
|
actor_procedure(bec=client, **actor_env)
|
|
58
|
+
push_status(conn, env["queue"], ProcedureWorkerStatus.FINISHED)
|
|
68
59
|
conn.shutdown()
|
|
69
|
-
logger_connector.shutdown()
|
|
70
60
|
|
|
71
61
|
|
|
72
62
|
if __name__ == "__main__":
|
|
@@ -220,7 +220,7 @@ class PodmanCliUtils(_PodmanUtilsBase):
|
|
|
220
220
|
_name_arg = ["--replace", "--name", container_name] if container_name else []
|
|
221
221
|
if container_name:
|
|
222
222
|
# in case of incomplete cleanup previously, even with --replace it is possible to get an error
|
|
223
|
-
_run_and_capture_error("podman", "rm", "-f", container_name)
|
|
223
|
+
_run_and_capture_error("podman", "rm", "-f", "--ignore", container_name)
|
|
224
224
|
return (
|
|
225
225
|
_run_and_capture_error(
|
|
226
226
|
"podman",
|
|
@@ -251,7 +251,8 @@ class PodmanCliUtils(_PodmanUtilsBase):
|
|
|
251
251
|
|
|
252
252
|
def logs(self, id: str) -> list[str]:
|
|
253
253
|
try:
|
|
254
|
-
|
|
254
|
+
output = _run_and_capture_error("podman", "logs", id)
|
|
255
|
+
return (output.stdout + output.stderr).decode(errors="replace").splitlines()
|
|
255
256
|
except ProcedureWorkerError as e:
|
|
256
257
|
logger.error(e)
|
|
257
258
|
return [f"No logs found for container {id}\n"]
|
|
@@ -28,7 +28,7 @@ class ContainerProcedureWorker(OutOfProcessWorkerBase):
|
|
|
28
28
|
self._backend.build_worker_image()
|
|
29
29
|
self._container_id = self._backend.run(
|
|
30
30
|
image_tag,
|
|
31
|
-
self._worker_environment(),
|
|
31
|
+
self._worker_environment() | self._additional_env,
|
|
32
32
|
[
|
|
33
33
|
{
|
|
34
34
|
"source": str(PROCEDURE.CONTAINER.DEPLOYMENT_PATH),
|
|
@@ -46,6 +46,8 @@ class RedisCredentials(TypedDict):
|
|
|
46
46
|
|
|
47
47
|
def _log_on_end(future: Future):
|
|
48
48
|
"""Use as a callback so that future is always done."""
|
|
49
|
+
if future.cancelled():
|
|
50
|
+
return
|
|
49
51
|
if e := future.exception():
|
|
50
52
|
logger.error(f"Worker failed with exception: {e}")
|
|
51
53
|
else:
|
|
@@ -141,9 +143,9 @@ class ProcedureManagerBase(ABC, Generic[_ReqMsgT, _ExecMsgT]):
|
|
|
141
143
|
with self.lock:
|
|
142
144
|
if exec_msg.queue not in self._active_workers:
|
|
143
145
|
new_worker = self.executor.submit(self.spawn, queue=queue, env=env)
|
|
146
|
+
self._active_workers[queue] = {"worker": None, "future": new_worker}
|
|
144
147
|
new_worker.add_done_callback(_log_on_end)
|
|
145
148
|
new_worker.add_done_callback(self._cleanup_worker_function(queue))
|
|
146
|
-
self._active_workers[queue] = {"worker": None, "future": new_worker}
|
|
147
149
|
return exec_msg
|
|
148
150
|
|
|
149
151
|
def spawn(self, queue: str, env: dict[str, str]):
|
|
@@ -156,11 +158,20 @@ class ProcedureManagerBase(ABC, Generic[_ReqMsgT, _ExecMsgT]):
|
|
|
156
158
|
raise WorkerAlreadyExists(
|
|
157
159
|
f"Queue {queue} already has an active worker in {self._active_workers}!"
|
|
158
160
|
)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
worker = None
|
|
162
|
+
try:
|
|
163
|
+
with self._worker_cls(
|
|
164
|
+
self._server, queue, PROCEDURE.WORKER.QUEUE_TIMEOUT_S, env
|
|
165
|
+
) as worker:
|
|
166
|
+
with self.lock:
|
|
167
|
+
self._active_workers[queue]["worker"] = worker
|
|
168
|
+
worker.work()
|
|
169
|
+
finally:
|
|
170
|
+
if worker is not None:
|
|
171
|
+
logs = worker.logs()
|
|
172
|
+
self._logs.extend(logs)
|
|
173
|
+
for line in logs:
|
|
174
|
+
logger.info(f"Procedure worker '{queue}': {line}")
|
|
164
175
|
|
|
165
176
|
def _wait_for_all_futures(self):
|
|
166
177
|
with self.lock:
|
|
@@ -195,7 +206,7 @@ class ProcedureManagerBase(ABC, Generic[_ReqMsgT, _ExecMsgT]):
|
|
|
195
206
|
self._conn.shutdown()
|
|
196
207
|
# cancel futures by hand to give us the opportunity to detach them from redis if they have started
|
|
197
208
|
with self.lock:
|
|
198
|
-
for entry in self._active_workers.values():
|
|
209
|
+
for entry in list(self._active_workers.values()):
|
|
199
210
|
cancelled = entry["future"].cancel()
|
|
200
211
|
if not cancelled:
|
|
201
212
|
# unblock any waiting workers and let them shutdown
|
|
@@ -292,6 +303,20 @@ class ProcedureManager(ProcedureManagerBase[ProcedureRequestMessage, ProcedureEx
|
|
|
292
303
|
def cleanup_worker(fut):
|
|
293
304
|
with self.lock:
|
|
294
305
|
logger.debug(f"cleaning up worker {fut} for queue {queue}...")
|
|
306
|
+
error = None if fut.cancelled() else fut.exception()
|
|
307
|
+
if error is not None:
|
|
308
|
+
pending = self._helper.get.exec_queue(queue)
|
|
309
|
+
running = [
|
|
310
|
+
msg for msg in self._helper.get.running_procedures() if msg.queue == queue
|
|
311
|
+
]
|
|
312
|
+
self._helper.move.execution_queue_to_unhandled(queue)
|
|
313
|
+
for msg in running:
|
|
314
|
+
self._helper.push.unhandled(queue, msg)
|
|
315
|
+
for msg in pending + running:
|
|
316
|
+
self._helper.status_update(
|
|
317
|
+
msg.execution_id, "Finished", error=f"Procedure worker failed: {error}"
|
|
318
|
+
)
|
|
319
|
+
self._helper.notify_watchers(queue, "unhandled")
|
|
295
320
|
self._helper.remove_from_active.by_queue(queue)
|
|
296
321
|
self._run_callbacks(queue)
|
|
297
322
|
self._helper.notify_watchers(queue, "execution")
|
|
@@ -59,7 +59,7 @@ class OutOfProcessWorkerBase(ProcedureWorker):
|
|
|
59
59
|
minimum necessary, or things which are only necessary for the functioning of the worker,
|
|
60
60
|
and other information should be passed through redis"""
|
|
61
61
|
return {
|
|
62
|
-
"redis_server":
|
|
62
|
+
"redis_server": self._redis_server,
|
|
63
63
|
"queue": self._queue,
|
|
64
64
|
"timeout_s": str(self._lifetime_s),
|
|
65
65
|
}
|
|
@@ -70,7 +70,7 @@ class OutOfProcessWorkerBase(ProcedureWorker):
|
|
|
70
70
|
# on timeout check if container is still running
|
|
71
71
|
|
|
72
72
|
status_update = None
|
|
73
|
-
while not self.
|
|
73
|
+
while not self._aborted.is_set():
|
|
74
74
|
status_update = self._conn.blocking_list_pop(
|
|
75
75
|
MessageEndpoints.procedure_worker_status_update(self._queue), timeout_s=0.2
|
|
76
76
|
)
|
|
@@ -82,10 +82,17 @@ class OutOfProcessWorkerBase(ProcedureWorker):
|
|
|
82
82
|
logger.info(
|
|
83
83
|
f"Procedure worker '{self._queue}' status update: {status_update.status.name}"
|
|
84
84
|
)
|
|
85
|
+
elif self._ending_or_ended():
|
|
86
|
+
break
|
|
85
87
|
# TODO: we probably do want to handle some kind of timeout here but we don't know how
|
|
86
88
|
# long a running procedure should actually take - it could theoretically be infinite
|
|
87
89
|
if self.status != ProcedureWorkerStatus.FINISHED:
|
|
88
90
|
self.status = ProcedureWorkerStatus.DEAD
|
|
91
|
+
if not self._aborted.is_set():
|
|
92
|
+
logs = "\n".join(self.logs())
|
|
93
|
+
raise ProcedureWorkerError(
|
|
94
|
+
f"Procedure worker for queue {self._queue} exited unexpectedly.\n{logs}"
|
|
95
|
+
)
|
|
89
96
|
|
|
90
97
|
|
|
91
98
|
def _default_env() -> dict[str, str]:
|
|
@@ -230,10 +237,21 @@ def _main(
|
|
|
230
237
|
helper.remove_from_active.by_exec_id(item.execution_id)
|
|
231
238
|
|
|
232
239
|
|
|
233
|
-
def main():
|
|
234
|
-
"""
|
|
240
|
+
def main(*, output_to_redis: bool = True) -> None:
|
|
241
|
+
"""Run the worker entry point.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
output_to_redis (bool): Redirect procedure output to Redis in this process. Subprocess
|
|
245
|
+
workers disable this because their parent streams both output pipes instead.
|
|
246
|
+
"""
|
|
235
247
|
|
|
236
248
|
env, helper, client, conn = setup(get_env())
|
|
249
|
+
if not output_to_redis:
|
|
250
|
+
try:
|
|
251
|
+
_main(env, helper, client, conn)
|
|
252
|
+
finally:
|
|
253
|
+
conn.shutdown()
|
|
254
|
+
return
|
|
237
255
|
logger_connector = RedisConnector(env["redis_server"])
|
|
238
256
|
output_diverter = RedisOutputDiverter(logger_connector, env["queue"])
|
|
239
257
|
with redirect_stdout(output_diverter):
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from codecs import getincrementaldecoder
|
|
5
|
+
from collections import deque
|
|
6
|
+
from signal import SIGINT
|
|
7
|
+
from threading import Lock, Thread
|
|
8
|
+
|
|
9
|
+
from bec_lib.endpoints import MessageEndpoints
|
|
10
|
+
from bec_lib.logger import bec_logger
|
|
11
|
+
from bec_lib.messages import RawMessage
|
|
12
|
+
from bec_lib.redis_connector import RedisConnector
|
|
13
|
+
from bec_server.procedures.constants import ProcedureWorkerError
|
|
14
|
+
from bec_server.procedures.oop_worker_base import PROCESS_TIMEOUT, OutOfProcessWorkerBase, main
|
|
15
|
+
|
|
16
|
+
logger = bec_logger.logger
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SubProcessWorker(OutOfProcessWorkerBase):
|
|
20
|
+
"""Run procedures in a subprocess, streaming stdout and stderr to Redis."""
|
|
21
|
+
|
|
22
|
+
WORKER_FILE = __file__
|
|
23
|
+
|
|
24
|
+
def _setup_execution_environment(self):
|
|
25
|
+
# Match the UTF-8 decoder in the parent even when the host uses another encoding.
|
|
26
|
+
env: dict[str, str] = {"PYTHONIOENCODING": "utf-8"} | self._worker_environment()
|
|
27
|
+
first_start = not hasattr(self, "_output_logs")
|
|
28
|
+
self._output_error_logged = False
|
|
29
|
+
if first_start:
|
|
30
|
+
self._output_logs: deque[str] = deque(maxlen=10000)
|
|
31
|
+
self._output_lock = Lock()
|
|
32
|
+
output_conn = RedisConnector(
|
|
33
|
+
[self._redis_server], name=f"ProcedureWorker-{self._queue} output"
|
|
34
|
+
)
|
|
35
|
+
try:
|
|
36
|
+
if first_start:
|
|
37
|
+
output_conn.delete(MessageEndpoints.procedure_logs(self._queue))
|
|
38
|
+
self._process = subprocess.Popen(
|
|
39
|
+
[sys.executable, "-u", self.WORKER_FILE],
|
|
40
|
+
stdout=subprocess.PIPE,
|
|
41
|
+
stderr=subprocess.STDOUT,
|
|
42
|
+
bufsize=0,
|
|
43
|
+
env=os.environ | env | self._additional_env,
|
|
44
|
+
)
|
|
45
|
+
except BaseException:
|
|
46
|
+
output_conn.shutdown()
|
|
47
|
+
raise
|
|
48
|
+
self._output_thread = Thread(
|
|
49
|
+
target=self._capture_output,
|
|
50
|
+
args=(self._process, output_conn),
|
|
51
|
+
name=f"ProcedureWorker-{self._queue} output",
|
|
52
|
+
daemon=True,
|
|
53
|
+
)
|
|
54
|
+
self._output_thread.start()
|
|
55
|
+
logger.success("Subprocess Procedure Worker started.")
|
|
56
|
+
self._ending: bool = False
|
|
57
|
+
|
|
58
|
+
def _capture_output(self, process: subprocess.Popen, conn: RedisConnector) -> None:
|
|
59
|
+
"""Drain both streams even if Redis rejects an output write."""
|
|
60
|
+
decoder = getincrementaldecoder("utf-8")(errors="replace")
|
|
61
|
+
try:
|
|
62
|
+
with process.stdout as output:
|
|
63
|
+
while True:
|
|
64
|
+
chunk = output.read(4096)
|
|
65
|
+
if not chunk:
|
|
66
|
+
self._publish_output(decoder.decode(b"", final=True), conn)
|
|
67
|
+
break
|
|
68
|
+
self._publish_output(decoder.decode(chunk), conn)
|
|
69
|
+
finally:
|
|
70
|
+
conn.shutdown()
|
|
71
|
+
|
|
72
|
+
def _publish_output(self, text: str, conn: RedisConnector) -> None:
|
|
73
|
+
if not text:
|
|
74
|
+
return
|
|
75
|
+
with self._output_lock:
|
|
76
|
+
self._output_logs.append(text)
|
|
77
|
+
try:
|
|
78
|
+
conn.xadd(
|
|
79
|
+
MessageEndpoints.procedure_logs(self._queue),
|
|
80
|
+
{"data": RawMessage(data=text)},
|
|
81
|
+
max_size=10000,
|
|
82
|
+
expire=3600,
|
|
83
|
+
)
|
|
84
|
+
except Exception as exc:
|
|
85
|
+
if not self._output_error_logged:
|
|
86
|
+
logger.error(f"Failed to publish procedure output: {exc}")
|
|
87
|
+
self._output_error_logged = True
|
|
88
|
+
|
|
89
|
+
def _join_output(self) -> None:
|
|
90
|
+
if hasattr(self, "_output_thread"):
|
|
91
|
+
self._output_thread.join(timeout=PROCESS_TIMEOUT)
|
|
92
|
+
if self._output_thread.is_alive():
|
|
93
|
+
raise ProcedureWorkerError("Subprocess output reader failed to stop")
|
|
94
|
+
|
|
95
|
+
def _cleanup_execution_environment(self) -> None:
|
|
96
|
+
try:
|
|
97
|
+
super()._cleanup_execution_environment()
|
|
98
|
+
finally:
|
|
99
|
+
self._join_output()
|
|
100
|
+
|
|
101
|
+
def _ending_or_ended(self):
|
|
102
|
+
return self._ending or (self._process.poll() is not None)
|
|
103
|
+
|
|
104
|
+
def _kill_process(self):
|
|
105
|
+
"""Attempts to end the process gently by first sending SIGINT, which is handled by the BECIpythonClient to
|
|
106
|
+
gracefully shut down, and end any scans it has started. See:
|
|
107
|
+
bec_ipython_client/main.py#L125 and bec_ipython_client/signals.py#L103
|
|
108
|
+
If the process fails to shut down naturally, kills it unceremoniously. This will leave the running scan running
|
|
109
|
+
on the ScanServer.
|
|
110
|
+
"""
|
|
111
|
+
if not self._ending_or_ended():
|
|
112
|
+
self._ending = True
|
|
113
|
+
self._process.send_signal(SIGINT)
|
|
114
|
+
self._process.send_signal(SIGINT)
|
|
115
|
+
try:
|
|
116
|
+
self._process.wait(PROCESS_TIMEOUT)
|
|
117
|
+
except subprocess.TimeoutExpired:
|
|
118
|
+
logger.warning(
|
|
119
|
+
f"Subprocess Procedure Worker failed to exit in {PROCESS_TIMEOUT} s. Killing."
|
|
120
|
+
)
|
|
121
|
+
self._process.kill()
|
|
122
|
+
self._process.wait(PROCESS_TIMEOUT)
|
|
123
|
+
|
|
124
|
+
def abort_execution(self, execution_id: str):
|
|
125
|
+
"""Abort the execution with the given id. Has no effect if the given ID is not the current job"""
|
|
126
|
+
if execution_id == self._current_execution_id:
|
|
127
|
+
self._kill_process()
|
|
128
|
+
self._helper.remove_from_active.by_exec_id(execution_id)
|
|
129
|
+
logger.info(
|
|
130
|
+
f"Aborting execution {execution_id}, restarting worker for queue: {self._queue}"
|
|
131
|
+
)
|
|
132
|
+
self._join_output()
|
|
133
|
+
self._setup_execution_environment()
|
|
134
|
+
|
|
135
|
+
def logs(self) -> list[str]:
|
|
136
|
+
"""Return captured output without consuming it or waiting for a live process."""
|
|
137
|
+
if self._process.poll() is not None:
|
|
138
|
+
self._join_output()
|
|
139
|
+
with self._output_lock:
|
|
140
|
+
return "".join(self._output_logs).splitlines()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
main(output_to_redis=False)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from threading import Event
|
|
4
|
+
from threading import Event, Lock
|
|
5
5
|
|
|
6
6
|
from bec_lib.endpoints import MessageEndpoints
|
|
7
7
|
from bec_lib.logger import bec_logger
|
|
@@ -52,15 +52,27 @@ class ProcedureWorker(ABC):
|
|
|
52
52
|
self.client_id = self._conn.client_id()
|
|
53
53
|
self._current_execution_id: str | None = None
|
|
54
54
|
self._aborted = Event()
|
|
55
|
+
self._shutdown_lock = Lock()
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
try:
|
|
58
|
+
self._setup_execution_environment()
|
|
59
|
+
except BaseException:
|
|
60
|
+
self._conn.shutdown()
|
|
61
|
+
raise
|
|
57
62
|
|
|
58
63
|
def __enter__(self):
|
|
59
64
|
return self
|
|
60
65
|
|
|
61
66
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
62
|
-
self.
|
|
63
|
-
|
|
67
|
+
self._cleanup_execution_environment()
|
|
68
|
+
|
|
69
|
+
def _cleanup_execution_environment(self) -> None:
|
|
70
|
+
"""Serialize abort and context teardown, always stopping the external process."""
|
|
71
|
+
with self._shutdown_lock:
|
|
72
|
+
try:
|
|
73
|
+
self._conn.shutdown()
|
|
74
|
+
finally:
|
|
75
|
+
self._kill_process()
|
|
64
76
|
|
|
65
77
|
@abstractmethod
|
|
66
78
|
def _kill_process(self):
|
|
@@ -86,8 +98,7 @@ class ProcedureWorker(ABC):
|
|
|
86
98
|
def abort(self):
|
|
87
99
|
"""Abort the entire worker"""
|
|
88
100
|
self._aborted.set()
|
|
89
|
-
self.
|
|
90
|
-
self._kill_process()
|
|
101
|
+
self._cleanup_execution_environment()
|
|
91
102
|
|
|
92
103
|
@abstractmethod
|
|
93
104
|
def abort_execution(self, execution_id: str):
|
|
@@ -20,8 +20,8 @@ from bec_server.test.helpers import wait_until
|
|
|
20
20
|
|
|
21
21
|
@pytest.fixture
|
|
22
22
|
def fakeredis_config():
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
server = TcpFakeServer(("127.0.0.1", 0))
|
|
24
|
+
redis_config = server.server_address
|
|
25
25
|
t = Thread(target=server.serve_forever, kwargs={"poll_interval": 0.1}, daemon=True)
|
|
26
26
|
t.start()
|
|
27
27
|
yield redis_config
|
|
@@ -122,6 +122,7 @@ def test_subscription_actor_unsubs():
|
|
|
122
122
|
def test_polling_actor_inline(fakeredis_config):
|
|
123
123
|
host, port = fakeredis_config
|
|
124
124
|
redis = {"host": host, "port": port}
|
|
125
|
+
BECClient._reset_singleton()
|
|
125
126
|
client: BECClient = _create_client(BecClientType.BECClient, redis=redis) # type: ignore
|
|
126
127
|
client.start()
|
|
127
128
|
redis = f"{host}:{port}"
|
|
@@ -140,6 +141,7 @@ def test_polling_actor_inline(fakeredis_config):
|
|
|
140
141
|
actor.stop_event.set()
|
|
141
142
|
actor_thread.join()
|
|
142
143
|
client.shutdown()
|
|
144
|
+
BECClient._reset_singleton()
|
|
143
145
|
|
|
144
146
|
|
|
145
147
|
def test_actor_procedure_happy_path():
|
|
@@ -263,3 +265,26 @@ def test_bl_state_actor_cancelled_startup(ready, stop_before_run, threads_check)
|
|
|
263
265
|
mock_client.beamline_states.ready = True
|
|
264
266
|
actor.stop_event.set()
|
|
265
267
|
t.join(timeout=2)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@patch("bec_server.actors.worker.actor_procedure")
|
|
271
|
+
@patch("bec_server.actors.worker.push_status")
|
|
272
|
+
@patch("bec_server.actors.worker.setup")
|
|
273
|
+
@patch("bec_server.actors.worker.get_actor_env", return_value={})
|
|
274
|
+
@patch("bec_server.actors.worker.get_env")
|
|
275
|
+
def test_actor_worker_reports_finished(
|
|
276
|
+
get_env_mock, actor_env_mock, setup_mock, push_mock, procedure_mock
|
|
277
|
+
):
|
|
278
|
+
from bec_server.actors.worker import main
|
|
279
|
+
|
|
280
|
+
conn = MagicMock()
|
|
281
|
+
setup_mock.return_value = (
|
|
282
|
+
{"redis_server": "localhost:6379", "queue": "test"},
|
|
283
|
+
None,
|
|
284
|
+
MagicMock(),
|
|
285
|
+
conn,
|
|
286
|
+
)
|
|
287
|
+
with patch("bec_server.actors.worker.logger"):
|
|
288
|
+
main()
|
|
289
|
+
assert push_mock.call_args.args == (conn, "test", ProcedureWorkerStatus.FINISHED)
|
|
290
|
+
conn.shutdown.assert_called_once()
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from subprocess import CompletedProcess
|
|
1
2
|
from unittest.mock import ANY, MagicMock, patch
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
@@ -217,3 +218,37 @@ def test_cli_get_state(cli_utils_with_fake_container_json: PodmanCliUtils):
|
|
|
217
218
|
assert cli_utils_with_fake_container_json.state(
|
|
218
219
|
"13826d25a737b733a5d87975b50a9c1efb5d087365d956cb7db2ccbe6aca07c4"
|
|
219
220
|
) == PodmanContainerStates("running")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_cli_logs_include_stdout_and_stderr(cli_utils):
|
|
224
|
+
utils, run = cli_utils
|
|
225
|
+
run.return_value.stdout = b"startup output\n"
|
|
226
|
+
run.return_value.stderr = b"startup error\n"
|
|
227
|
+
assert utils.logs("test") == ["startup output", "startup error"]
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_cli_run_ignores_missing_previous_container(cli_utils):
|
|
231
|
+
utils, run_mock = cli_utils
|
|
232
|
+
run_mock.reset_mock()
|
|
233
|
+
|
|
234
|
+
def run_command(command, **kwargs):
|
|
235
|
+
missing_container = command[1] == "rm" and "--ignore" not in command
|
|
236
|
+
return CompletedProcess(
|
|
237
|
+
command,
|
|
238
|
+
returncode=1 if missing_container else 0,
|
|
239
|
+
stdout=b"worker-id\n",
|
|
240
|
+
stderr=b"No such container" if missing_container else b"",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
run_mock.side_effect = run_command
|
|
244
|
+
container_id = utils.run(
|
|
245
|
+
"worker-image",
|
|
246
|
+
{"redis_server": "localhost:6379", "queue": "test", "timeout_s": "10"},
|
|
247
|
+
[],
|
|
248
|
+
"bec-procedure-worker",
|
|
249
|
+
container_name="new-worker",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
assert container_id == "worker-id"
|
|
253
|
+
run_mock.assert_any_call(["podman", "rm", "-f", "--ignore", "new-worker"], capture_output=True)
|
|
254
|
+
assert run_mock.call_args.args[0][:2] == ["podman", "run"]
|
{bec_server-4.3.0 → bec_server-4.3.1}/tests/tests_scan_server/test_procedure_container_worker.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import time
|
|
3
3
|
from itertools import repeat
|
|
4
|
-
from threading import Thread
|
|
4
|
+
from threading import Event, Thread
|
|
5
5
|
from time import sleep
|
|
6
6
|
from unittest.mock import MagicMock, call, patch
|
|
7
7
|
|
|
@@ -25,7 +25,7 @@ def test_container_worker_init(redis_mock):
|
|
|
25
25
|
redis_mock().port = "port"
|
|
26
26
|
worker = ContainerProcedureWorker(server="server:port", queue="test_queue", lifetime_s=1)
|
|
27
27
|
assert worker._worker_environment() == {
|
|
28
|
-
"redis_server": "
|
|
28
|
+
"redis_server": "server:port",
|
|
29
29
|
"queue": "test_queue",
|
|
30
30
|
"timeout_s": "1",
|
|
31
31
|
}
|
|
@@ -185,3 +185,120 @@ def test_main_running_newly_registered_proc(redis_mock, logger_mock):
|
|
|
185
185
|
logger_mock.debug.assert_has_calls([call(f"running task {PROC_NAME}")], any_order=True)
|
|
186
186
|
function_recorder.assert_called_once_with("a", b="c")
|
|
187
187
|
logger_mock.success.assert_called_with("Procedure runner shutting down")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@patch("bec_server.procedures.container_worker.get_backend")
|
|
191
|
+
@patch("bec_server.procedures.worker_base.RedisConnector")
|
|
192
|
+
def test_container_worker_uses_supplied_environment(redis_mock, backend_mock):
|
|
193
|
+
worker = ContainerProcedureWorker(
|
|
194
|
+
server="127.0.0.1:20772",
|
|
195
|
+
queue="test",
|
|
196
|
+
env={"redis_server": "host.containers.internal:20772"},
|
|
197
|
+
)
|
|
198
|
+
assert backend_mock().run.call_args.args[1]["redis_server"] == "host.containers.internal:20772"
|
|
199
|
+
assert worker._worker_environment()["redis_server"] == "127.0.0.1:20772"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@patch("bec_server.procedures.container_worker.get_backend")
|
|
203
|
+
@patch("bec_server.procedures.worker_base.RedisConnector")
|
|
204
|
+
def test_container_worker_setup_failure_closes_connector(redis_mock, backend_mock):
|
|
205
|
+
backend_mock().run.side_effect = RuntimeError("missing pod")
|
|
206
|
+
with pytest.raises(RuntimeError, match="missing pod"):
|
|
207
|
+
ContainerProcedureWorker(server="localhost:6379", queue="test")
|
|
208
|
+
redis_mock().shutdown.assert_called_once()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@patch("bec_server.procedures.container_worker.get_backend")
|
|
212
|
+
@patch("bec_server.procedures.worker_base.RedisConnector")
|
|
213
|
+
def test_container_worker_early_exit_reports_logs(redis_mock, backend_mock):
|
|
214
|
+
from bec_server.procedures.constants import ProcedureWorkerError
|
|
215
|
+
|
|
216
|
+
redis_mock().blocking_list_pop.return_value = None
|
|
217
|
+
backend_mock().state.return_value = PodmanContainerStates.EXITED
|
|
218
|
+
backend_mock().logs.return_value = ["Cannot connect to Redis"]
|
|
219
|
+
worker = ContainerProcedureWorker(server="localhost:6379", queue="test")
|
|
220
|
+
with pytest.raises(ProcedureWorkerError, match="Cannot connect to Redis"):
|
|
221
|
+
worker.work()
|
|
222
|
+
assert worker.status == ProcedureWorkerStatus.DEAD
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@patch("bec_server.procedures.container_worker.get_backend")
|
|
226
|
+
@patch("bec_server.procedures.worker_base.RedisConnector")
|
|
227
|
+
def test_container_worker_drains_final_status_before_exit(redis_mock, backend_mock):
|
|
228
|
+
redis_mock().blocking_list_pop.side_effect = [
|
|
229
|
+
ProcedureWorkerStatusMessage(worker_queue="test", status=ProcedureWorkerStatus.FINISHED),
|
|
230
|
+
None,
|
|
231
|
+
]
|
|
232
|
+
backend_mock().state.return_value = PodmanContainerStates.EXITED
|
|
233
|
+
worker = ContainerProcedureWorker(server="localhost:6379", queue="test")
|
|
234
|
+
worker.work()
|
|
235
|
+
assert worker.status == ProcedureWorkerStatus.FINISHED
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@patch("bec_server.procedures.container_worker.get_backend")
|
|
239
|
+
@patch("bec_server.procedures.worker_base.RedisConnector")
|
|
240
|
+
def test_abort_and_context_exit_serialize_connector_shutdown(redis_mock, backend_mock):
|
|
241
|
+
worker = ContainerProcedureWorker(server="localhost:6379", queue="test")
|
|
242
|
+
closing = Event()
|
|
243
|
+
release = Event()
|
|
244
|
+
exit_started = Event()
|
|
245
|
+
concurrent_close = Event()
|
|
246
|
+
errors = []
|
|
247
|
+
|
|
248
|
+
def close_connector():
|
|
249
|
+
if closing.is_set():
|
|
250
|
+
concurrent_close.set()
|
|
251
|
+
raise RuntimeError("concurrent Redis connector shutdown")
|
|
252
|
+
closing.set()
|
|
253
|
+
try:
|
|
254
|
+
assert release.wait(2)
|
|
255
|
+
finally:
|
|
256
|
+
closing.clear()
|
|
257
|
+
|
|
258
|
+
def abort():
|
|
259
|
+
try:
|
|
260
|
+
worker.abort()
|
|
261
|
+
except BaseException as exc:
|
|
262
|
+
errors.append(exc)
|
|
263
|
+
|
|
264
|
+
def exit_context():
|
|
265
|
+
exit_started.set()
|
|
266
|
+
try:
|
|
267
|
+
worker.__exit__(None, None, None)
|
|
268
|
+
except BaseException as exc:
|
|
269
|
+
errors.append(exc)
|
|
270
|
+
|
|
271
|
+
redis_mock().shutdown.side_effect = close_connector
|
|
272
|
+
worker._kill_process = MagicMock()
|
|
273
|
+
abort_thread = Thread(target=abort)
|
|
274
|
+
exit_thread = Thread(target=exit_context)
|
|
275
|
+
abort_thread.start()
|
|
276
|
+
try:
|
|
277
|
+
assert closing.wait(2)
|
|
278
|
+
exit_thread.start()
|
|
279
|
+
assert exit_started.wait(2)
|
|
280
|
+
assert not concurrent_close.wait(0.1)
|
|
281
|
+
finally:
|
|
282
|
+
release.set()
|
|
283
|
+
abort_thread.join(2)
|
|
284
|
+
if exit_thread.ident is not None:
|
|
285
|
+
exit_thread.join(2)
|
|
286
|
+
assert not abort_thread.is_alive()
|
|
287
|
+
assert not exit_thread.is_alive()
|
|
288
|
+
assert errors == []
|
|
289
|
+
worker._kill_process.assert_called()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@patch("bec_server.procedures.container_worker.get_backend")
|
|
293
|
+
@patch("bec_server.procedures.worker_base.RedisConnector")
|
|
294
|
+
@pytest.mark.parametrize("abort", [True, False])
|
|
295
|
+
def test_connector_shutdown_failure_still_stops_worker(redis_mock, backend_mock, abort):
|
|
296
|
+
worker = ContainerProcedureWorker(server="localhost:6379", queue="test")
|
|
297
|
+
redis_mock().shutdown.side_effect = RuntimeError("failed to close Redis")
|
|
298
|
+
worker._kill_process = MagicMock()
|
|
299
|
+
with pytest.raises(RuntimeError, match="failed to close Redis"):
|
|
300
|
+
if abort:
|
|
301
|
+
worker.abort()
|
|
302
|
+
else:
|
|
303
|
+
worker.__exit__(None, None, None)
|
|
304
|
+
worker._kill_process.assert_called_once()
|