feat(api): abort active workflow runs during Celery warm shutdown#38220
Conversation
Pyrefly Type Coverage
|
Pyrefly Diffbase → PR--- /tmp/pyrefly_base.txt 2026-07-03 02:41:29.840793690 +0000
+++ /tmp/pyrefly_pr.txt 2026-07-03 02:41:21.388797318 +0000
@@ -3764,6 +3764,8 @@
ERROR Object of class `StreamResponse` has no attribute `data` [missing-attribute]
--> tests/unit_tests/core/app/apps/test_workflow_pause_events.py:284:12
ERROR Object of class `FileInputConfig` has no attribute `option_source`
+ERROR Generator function should return `Generator` [bad-return]
+ --> tests/unit_tests/core/app/apps/workflow/test_active_workflow_tasks.py:11:29
ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `core.app.apps.workflow.app_config_manager.WorkflowAppConfigManager.get_app_config` [bad-argument-type]
--> tests/unit_tests/core/app/apps/workflow/test_app_config_manager.py:23:66
ERROR Argument `SimpleNamespace` is not assignable to parameter `workflow` with type `Workflow` in function `core.app.apps.workflow.app_config_manager.WorkflowAppConfigManager.get_app_config` [bad-argument-type]
@@ -3874,6 +3876,8 @@
--> tests/unit_tests/core/app/apps/workflow/test_app_generator_extra.py:505:43
ERROR Argument `SimpleNamespace` is not assignable to parameter `workflow_node_execution_repository` with type `WorkflowNodeExecutionRepository` in function `core.app.apps.workflow.app_generator.WorkflowAppGenerator._generate_worker` [bad-argument-type]
--> tests/unit_tests/core/app/apps/workflow/test_app_generator_extra.py:506:48
+ERROR Argument `tuple[SimpleNamespace, _CommandChannelStub]` is not assignable to parameter `command_channels` with type `Sequence[CommandChannel]` in function `core.app.apps.workflow.command_channels.CombinedCommandChannel.__init__` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/workflow/test_command_channels.py:64:39
ERROR `in` is not supported between `Literal['blocking response mode']` and `None` [not-iterable]
--> tests/unit_tests/core/app/apps/workflow/test_errors.py:9:16
ERROR Cannot index into `str` [bad-index]
@@ -6416,6 +6420,8 @@
--> tests/unit_tests/extensions/test_set_secretkey.py:49:32
ERROR Argument `Flask` is not assignable to parameter `app` with type `DifyApp` in function `extensions.ext_set_secretkey.init_app` [bad-argument-type]
--> tests/unit_tests/extensions/test_set_secretkey.py:69:32
+ERROR Generator function should return `Generator` [bad-return]
+ --> tests/unit_tests/extensions/test_workflow_warm_shutdown.py:13:36
ERROR Object of class `NoneType` has no attribute `storage_key` [missing-attribute]
--> tests/unit_tests/factories/test_build_from_mapping.py:132:12
ERROR Object of class `NoneType` has no attribute `storage_key` [missing-attribute]
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #38220 +/- ##
==========================================
+ Coverage 85.36% 85.37% +0.01%
==========================================
Files 4970 4981 +11
Lines 258774 261668 +2894
Branches 49076 49292 +216
==========================================
+ Hits 220893 223411 +2518
- Misses 33576 33866 +290
- Partials 4305 4391 +86
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
The overall approach seems a bit weird, for the following reasons:
I would propose the following solutions:
class CombinedCommandChannel(CommandChannel):
def __init__(self, command_channels: Sequence[CommandChannel]): ...
|
…event duplicate channel registration
…channel instances
|
I think the shutdown modeling can be simplified further. Instead of storing a process-global _shutdown_started = threading.Event()
def _on_shutting_down(*args: object, **kwargs: object) -> None:
_shutdown_started.set()
def shutdown_started() -> bool:
return _shutdown_started.is_set()Then class CelerySignalCommandChannel(CommandChannel):
def __init__(
self,
shutdown_state_getter: Callable[[], bool],
abort_reason: str = WORKFLOW_WARM_SHUTDOWN_ABORT_REASON,
) -> None:
...This gives a cleaner boundary:
I would also suggest keeping an instance-level flag inside With this design, If the active task registry is only there for cancellation, I think it can be removed. If you still want to log the number of in-flight workflow runs during worker shutdown, then keeping the current counting mechanism makes sense as an observability / diagnostic concern, but it should stay separate from the command propagation mechanism. |
Important
Fixes #<issue number>.Summary
Screenshots
Checklist
make lint && make type-check(backend) andcd web && pnpm exec vp staged(frontend) to appease the lint gods