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.
Files changed (215) hide show
  1. {bec_server-4.3.1 → bec_server-4.3.2}/PKG-INFO +1 -1
  2. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/constants.py +21 -2
  3. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/container_utils.py +71 -3
  4. bec_server-4.3.2/bec_server/procedures/container_worker.py +152 -0
  5. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/manager.py +66 -126
  6. bec_server-4.3.2/bec_server/procedures/oop_worker_base.py +603 -0
  7. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/protocol.py +11 -0
  8. bec_server-4.3.2/bec_server/procedures/queue_manager.py +598 -0
  9. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/subprocess_worker.py +10 -61
  10. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/worker_base.py +27 -4
  11. {bec_server-4.3.1 → bec_server-4.3.2}/pyproject.toml +1 -1
  12. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_container_utils.py +58 -0
  13. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_procedure_container_worker.py +24 -0
  14. bec_server-4.3.2/tests/tests_scan_server/test_procedure_queue_pool.py +608 -0
  15. bec_server-4.3.2/tests/tests_scan_server/test_procedure_submission.py +137 -0
  16. bec_server-4.3.2/tests/tests_scan_server/test_procedure_subprocess_worker.py +490 -0
  17. bec_server-4.3.2/tests/tests_scan_server/test_procedures.py +313 -0
  18. bec_server-4.3.1/bec_server/procedures/container_worker.py +0 -78
  19. bec_server-4.3.1/bec_server/procedures/oop_worker_base.py +0 -266
  20. bec_server-4.3.1/tests/tests_scan_server/test_procedure_subprocess_worker.py +0 -121
  21. bec_server-4.3.1/tests/tests_scan_server/test_procedures.py +0 -772
  22. {bec_server-4.3.1 → bec_server-4.3.2}/.gitignore +0 -0
  23. {bec_server-4.3.1 → bec_server-4.3.2}/.zed/debug.json +0 -0
  24. {bec_server-4.3.1 → bec_server-4.3.2}/README.md +0 -0
  25. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/__init__.py +0 -0
  26. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/actors/__init__.py +0 -0
  27. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/actors/actor.py +0 -0
  28. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/actors/builtin_actor_manager.py +0 -0
  29. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/actors/manager.py +0 -0
  30. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/actors/scan_interlock.py +0 -0
  31. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/actors/worker.py +0 -0
  32. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/bec_server_utils/__init__.py +0 -0
  33. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/bec_server_utils/launch.py +0 -0
  34. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/bec_server_utils/service_handler.py +0 -0
  35. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/bec_server_utils/subprocess_launch.py +0 -0
  36. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/bec_server_utils/tmux_launch.py +0 -0
  37. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/__init__.py +0 -0
  38. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/cli/__init__.py +0 -0
  39. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/cli/launch.py +0 -0
  40. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/dap_server.py +0 -0
  41. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/dap_service.py +0 -0
  42. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/dap_service_manager.py +0 -0
  43. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/data_processing/lmfit1d_service.py +0 -0
  44. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/__init__.py +0 -0
  45. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/bec_message_handler.py +0 -0
  46. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/cli/__init__.py +0 -0
  47. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/cli/launch.py +0 -0
  48. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/device_server.py +0 -0
  49. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/devices/__init__.py +0 -0
  50. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/devices/config_update_handler.py +0 -0
  51. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/devices/device_serializer.py +0 -0
  52. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/devices/devicemanager.py +0 -0
  53. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/friendly_device_exceptions.py +0 -0
  54. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/ophyd_callback_monitor.py +0 -0
  55. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/rpc_handler.py +0 -0
  56. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/tests/__init__.py +0 -0
  57. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/device_server/tests/utils.py +0 -0
  58. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/__init__.py +0 -0
  59. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/async_writer.py +0 -0
  60. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/cli/__init__.py +0 -0
  61. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/cli/launch.py +0 -0
  62. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/default_writer.py +0 -0
  63. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/file_writer.py +0 -0
  64. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/file_writer_manager.py +0 -0
  65. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/file_writer/merged_dicts.py +0 -0
  66. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/Containerfile.bec_linux_base +0 -0
  67. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/Containerfile.requirements +0 -0
  68. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/Containerfile.worker +0 -0
  69. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/__init__.py +0 -0
  70. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/_dev_runner.py +0 -0
  71. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/builtin_procedures.py +0 -0
  72. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/procedures/procedure_registry.py +0 -0
  73. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_bundler/__init__.py +0 -0
  74. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_bundler/bec_emitter.py +0 -0
  75. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_bundler/cli/__init__.py +0 -0
  76. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_bundler/cli/launch.py +0 -0
  77. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_bundler/emitter.py +0 -0
  78. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_bundler/scan_bundler.py +0 -0
  79. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/__init__.py +0 -0
  80. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/beamline_state_manager.py +0 -0
  81. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/cli/__init__.py +0 -0
  82. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/cli/launch.py +0 -0
  83. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/device_lock_registry.py +0 -0
  84. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/direct_scan_worker.py +0 -0
  85. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/errors.py +0 -0
  86. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/instruction_handler.py +0 -0
  87. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scan_assembler.py +0 -0
  88. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scan_guard.py +0 -0
  89. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scan_manager.py +0 -0
  90. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scan_queue.py +0 -0
  91. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scan_server.py +0 -0
  92. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scan_worker.py +0 -0
  93. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/__init__.py +0 -0
  94. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/acquire.py +0 -0
  95. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/cont_line_scan.py +0 -0
  96. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/device_rpc.py +0 -0
  97. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/fermat_scan.py +0 -0
  98. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/grid_scan.py +0 -0
  99. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/hexagonal_scan.py +0 -0
  100. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/line_scan.py +0 -0
  101. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/line_sweep_scan.py +0 -0
  102. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/list_scan.py +0 -0
  103. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/log_scan.py +0 -0
  104. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/move.py +0 -0
  105. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/multi_region_grid_scan.py +0 -0
  106. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/multi_region_line_scan.py +0 -0
  107. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/path_optimization.py +0 -0
  108. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/position_generators.py +0 -0
  109. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/round_roi_scan.py +0 -0
  110. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/round_scan.py +0 -0
  111. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/scan_actions.py +0 -0
  112. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/scan_argument_modifier.py +0 -0
  113. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/scan_base.py +0 -0
  114. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/scan_components.py +0 -0
  115. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/scan_modifier.py +0 -0
  116. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/scan_status.py +0 -0
  117. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/time_scan.py +0 -0
  118. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/scans/updated_move.py +0 -0
  119. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/tests/__init__.py +0 -0
  120. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/tests/fixtures.py +0 -0
  121. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/tests/scan_fixtures.py +0 -0
  122. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/tests/scan_hook_tests.py +0 -0
  123. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scan_server/tests/utils.py +0 -0
  124. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/__init__.py +0 -0
  125. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/__init__.py +0 -0
  126. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/atlas_active_client_emitter.py +0 -0
  127. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/atlas_connector.py +0 -0
  128. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/atlas_forwarder.py +0 -0
  129. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/atlas_metadata_handler.py +0 -0
  130. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/atlas_plugin_repo_emitter.py +0 -0
  131. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/atlas/config_handler.py +0 -0
  132. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/cli/__init__.py +0 -0
  133. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/cli/launch.py +0 -0
  134. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/scihub.py +0 -0
  135. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/service_handler/__init__.py +0 -0
  136. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/scihub/service_handler/service_handler.py +0 -0
  137. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/test/__init__.py +0 -0
  138. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/test/actor_test_utils.py +0 -0
  139. {bec_server-4.3.1 → bec_server-4.3.2}/bec_server/test/helpers.py +0 -0
  140. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_actors/test_actors.py +0 -0
  141. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_bec_server_utils/test_main.py +0 -0
  142. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_bec_server_utils/test_service_handler.py +0 -0
  143. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_bec_server_utils/test_subprocess_launch.py +0 -0
  144. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_bec_server_utils/test_tmux_launch.py +0 -0
  145. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_data_processing/conftest.py +0 -0
  146. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_data_processing/test_dap_cli_launch.py +0 -0
  147. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_data_processing/test_dap_server.py +0 -0
  148. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_data_processing/test_dap_service_manager.py +0 -0
  149. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_data_processing/test_lmfit1d_service.py +0 -0
  150. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/conftest.py +0 -0
  151. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_config_handler.py +0 -0
  152. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_device_instructions.py +0 -0
  153. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_device_manager_ds.py +0 -0
  154. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_device_serializer.py +0 -0
  155. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_device_server.py +0 -0
  156. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_device_server_cli_launch.py +0 -0
  157. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_ophyd_callback_monitor.py +0 -0
  158. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_device_server/test_rpc_handler.py +0 -0
  159. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_file_writer/conftest.py +0 -0
  160. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_file_writer/test_async_file_writer.py +0 -0
  161. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_file_writer/test_file_writer.py +0 -0
  162. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_file_writer/test_file_writer_cli_launch.py +0 -0
  163. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_file_writer/test_file_writer_manager.py +0 -0
  164. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_bundler/conftest.py +0 -0
  165. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_bundler/test_bec_emitter.py +0 -0
  166. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_bundler/test_scan_bundler.py +0 -0
  167. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_bundler/test_scan_bundler_cli_launch.py +0 -0
  168. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/conftest.py +0 -0
  169. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/__init__.py +0 -0
  170. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_acquire.py +0 -0
  171. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_cont_line_scan.py +0 -0
  172. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_device_rpc_scan.py +0 -0
  173. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_fermat_scan.py +0 -0
  174. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_grid_scan.py +0 -0
  175. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_hexagonal_scan.py +0 -0
  176. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_line_scan.py +0 -0
  177. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_line_sweep_scan.py +0 -0
  178. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_list_scan.py +0 -0
  179. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_log_scan.py +0 -0
  180. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_move_scan.py +0 -0
  181. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_multi_region_grid_scan.py +0 -0
  182. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_multi_region_line_scan.py +0 -0
  183. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_position_preview.py +0 -0
  184. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_relative_scan_return_to_start.py +0 -0
  185. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_round_roi_scan.py +0 -0
  186. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_round_scan.py +0 -0
  187. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_scan_actions.py +0 -0
  188. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_scan_components.py +0 -0
  189. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_scan_status.py +0 -0
  190. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_time_scan.py +0 -0
  191. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/scans/test_updated_move_scan.py +0 -0
  192. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_beamline_state_manager.py +0 -0
  193. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_builtin_actor_manager.py +0 -0
  194. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_device_lock_registry.py +0 -0
  195. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_direct_scan_worker.py +0 -0
  196. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_instruction_handler.py +0 -0
  197. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_path_optimization.py +0 -0
  198. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_argument_modifier.py +0 -0
  199. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_assembler.py +0 -0
  200. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_fixture_imports.py +0 -0
  201. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_guard.py +0 -0
  202. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_interlock.py +0 -0
  203. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_modifier.py +0 -0
  204. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_server_cli_launch.py +0 -0
  205. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_server_queue.py +0 -0
  206. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_server_scan_manager.py +0 -0
  207. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scan_server/test_scan_worker.py +0 -0
  208. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/conftest.py +0 -0
  209. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_atlas_active_client_emitter.py +0 -0
  210. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_atlas_config_handler.py +0 -0
  211. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_atlas_connector.py +0 -0
  212. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_atlas_forwarder.py +0 -0
  213. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_atlas_metadata_handler.py +0 -0
  214. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_atlas_plugin_repo_emitter.py +0 -0
  215. {bec_server-4.3.1 → bec_server-4.3.2}/tests/tests_scihub/test_scihub_cli_launch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: bec-server
3
- Version: 4.3.1
3
+ Version: 4.3.2
4
4
  Summary: BEC server
5
5
  Project-URL: Bug Tracker, https://github.com/bec-project/bec/issues
6
6
  Project-URL: Homepage, https://github.com/bec-project/bec
@@ -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 ProcedureWorkerError(RuntimeError): ...
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 = 10
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
- status = client.containers.get(id).status
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 container["Id"] == id or container["Id"].startswith(id):
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
- self._register_endpoints()
118
- self._publish_available()
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 message {msg}")
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]["worker"] = worker
168
- worker.work()
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 _wait_for_all_futures(self):
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
- futs = list(entry["future"] for entry in self._active_workers.values())
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
- for entry in list(self._active_workers.values()):
210
- cancelled = entry["future"].cancel()
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(ProcedureManagerBase[ProcedureRequestMessage, ProcedureExecutionMessage]):
256
- def __init__(
257
- self,
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, None, self._process_queue_request)
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 _cleanup_worker_function(self, queue: str):
303
- def cleanup_worker(fut):
304
- with self.lock:
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")
320
- self._helper.remove_from_active.by_queue(queue)
321
- self._run_callbacks(queue)
322
- self._helper.notify_watchers(queue, "execution")
323
- del self._active_workers[queue]
324
-
325
- return cleanup_worker
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"active workers: {self._active_workers}, worker requested: {queue}")
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, None, self._process_queue_request)
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)