refactor: own active workflow executions

This commit is contained in:
jxxghp
2026-08-24 16:26:39 +08:00
parent 41b1460b60
commit ffe6da2135
10 changed files with 421 additions and 31 deletions
+20
View File
@@ -44,6 +44,26 @@ class WorkflowRuntime(Protocol):
"""按最新定义刷新工作流事件触发器。"""
...
def register_execution(self, owner: "WorkflowExecutionOwner") -> bool:
"""登记活动工作流执行 owner;停机封口后返回 False。"""
...
def unregister_execution(self, owner: "WorkflowExecutionOwner") -> None:
"""在工作流执行真实终止后释放 owner。"""
...
class WorkflowExecutionOwner(Protocol):
"""声明 concrete 工作流管理器需要持有的执行生命周期能力。"""
def request_stop(self) -> None:
"""请求停止继续调度,并通知支持取消的活动动作。"""
...
def wait_stopped(self, timeout: float) -> bool:
"""有限等待执行及其节点线程池真实终止。"""
...
WorkflowRuntimeProvider = Callable[[], WorkflowRuntime]
+113 -13
View File
@@ -5,11 +5,10 @@ import inspect
import pickle
import threading
from collections import defaultdict, deque
from concurrent.futures import ThreadPoolExecutor
from contextvars import Context, copy_context
from datetime import date, datetime
from functools import partial
from time import sleep
from time import monotonic, sleep
from typing import Any, Callable, List, Optional, Tuple
from pydantic import BaseModel
@@ -19,6 +18,7 @@ from app.runtime.config import global_vars
from app.runtime.events import Event, eventmanager
from app.application.workflow import get_workflow_manager
from app.application.chain.data import get_chain_workflow_port
from app.runtime.execution import OwnedThreadPoolExecutor
from app.runtime.log import logger
from app.schemas.workflow import ActionContext
from app.schemas.workflow import ActionFlow
@@ -29,6 +29,7 @@ from app.schemas.types import EventType
ARTIFACT_FIELDS = {"torrents", "medias", "fileitems", "downloads", "sites", "subscribes"}
DEFAULT_WORKFLOW_MAX_WORKERS = 4
WORKFLOW_EXECUTOR_STOP_TIMEOUT_SECONDS = 10.0
CIRCULAR_REFERENCE_PLACEHOLDER = "[Circular]"
Workflow = Any
@@ -93,18 +94,27 @@ class WorkflowCancelToken:
工作流取消令牌。
"""
def __init__(self, workflow_id: int):
def __init__(
self,
workflow_id: int,
stop_event: Optional[threading.Event] = None,
) -> None:
"""
初始化取消令牌。
:param workflow_id: 工作流ID
:param stop_event: 单次执行 owner 的本地停止信号
"""
self.workflow_id = workflow_id
self.stop_event = stop_event
def is_cancelled(self) -> bool:
"""
判断工作流是否已被取消。
"""
return global_vars.is_workflow_stopped(self.workflow_id)
return bool(
(self.stop_event and self.stop_event.is_set())
or global_vars.is_workflow_stopped(self.workflow_id)
)
class WorkflowExecutor:
@@ -163,6 +173,13 @@ class WorkflowExecutor:
# 工作流管理器
self.workflowmanager = get_workflow_manager()
# 具体管理器登记活动执行;旧自定义 provider 不实现 owner 接口时保持原调用兼容。
self._execution_lock = threading.RLock()
self._admission_state = "pending"
self._registered_execution = False
self._stop_event = threading.Event()
self._execute_returned = threading.Event()
self._stopped_event = threading.Event()
# 线程安全队列
self.queue = deque()
self.queued_actions = set()
@@ -170,8 +187,8 @@ class WorkflowExecutor:
# 锁用于保证线程安全
self.lock = threading.Lock()
# 线程池
self.executor = ThreadPoolExecutor(max_workers=self.get_workflow_max_workers())
self.cancel_token = WorkflowCancelToken(self.workflow.id)
self.executor = OwnedThreadPoolExecutor(max_workers=self.get_workflow_max_workers())
self.cancel_token = WorkflowCancelToken(self.workflow.id, self._stop_event)
# 跟踪运行中的任务数
self.running_tasks = 0
@@ -188,8 +205,6 @@ class WorkflowExecutor:
self.context = self.restore_context()
self.ensure_context_partitions()
# 恢复工作流
global_vars.workflow_resume(self.workflow.id)
# 恢复时重新释放已终态节点的出边,使后继节点能继续执行或保持跳过传播。
for action_id, state in self.node_states.items():
if state == "success":
@@ -201,6 +216,78 @@ class WorkflowExecutor:
if self.node_states.get(action_id) == "pending" and not self.incoming_flows.get(action_id):
self.enqueue_node(action_id)
def admit(self) -> bool:
"""向 concrete manager 登记本次执行,并保持旧 provider 可直接运行。"""
with self._execution_lock:
if self._admission_state == "admitted":
return True
if self._admission_state == "rejected":
return False
register = getattr(self.workflowmanager, "register_execution", None)
if callable(register) and not register(self):
self._admission_state = "rejected"
self.success = False
self.stopped = True
self.errmsg = "工作流服务正在停止"
self.executor.shutdown_bounded(timeout=0.0)
self._execute_returned.set()
self._stopped_event.set()
return False
self._registered_execution = callable(register)
self._admission_state = "admitted"
# 只有获得执行准入后才能清除历史单工作流停止标记。
global_vars.workflow_resume(self.workflow.id)
return True
def request_stop(self) -> None:
"""停止调度新节点,并通过本地令牌通知支持取消的活动动作。"""
self._stop_event.set()
def abort_before_execute(self) -> None:
"""执行状态启动失败时释放尚未使用的节点池和 manager owner。"""
self.request_stop()
converged = self.executor.shutdown_bounded(timeout=0.0)
self._execute_returned.set()
if converged:
self._release_execution()
def wait_stopped(self, timeout: float) -> bool:
"""有限等待 execute 返回,并在需要时重试节点线程池收敛。"""
deadline = monotonic() + max(0.0, timeout)
if self._stopped_event.is_set():
return True
if not self._execute_returned.wait(
timeout=max(0.0, deadline - monotonic()),
):
return False
if self._stopped_event.is_set():
return True
if not self.executor.shutdown_bounded(
timeout=max(0.0, deadline - monotonic()),
):
return False
self._release_execution()
return self._stopped_event.is_set()
def _release_execution(self) -> None:
"""从 concrete manager 释放已真实终止的执行 owner。"""
with self._execution_lock:
if self._stopped_event.is_set():
return
if self._registered_execution:
unregister = getattr(self.workflowmanager, "unregister_execution", None)
if callable(unregister):
unregister(self)
self._registered_execution = False
self._stopped_event.set()
def _stop_requested(self) -> bool:
"""判断本次执行或全局工作流是否已收到停止请求。"""
return bool(
self._stop_event.is_set()
or global_vars.is_workflow_stopped(self.workflow.id)
)
def get_workflow_max_workers(self) -> int:
"""
获取工作流最大并发数。
@@ -339,12 +426,14 @@ class WorkflowExecutor:
"""
执行工作流
"""
if not self.admit():
return
try:
while True:
should_sleep = False
node_id = None
with self.lock:
if global_vars.is_workflow_stopped(self.workflow.id):
if self._stop_requested():
self.success = False
self.stopped = True
self.errmsg = "工作流已停止"
@@ -385,7 +474,12 @@ class WorkflowExecutor:
)
future.add_done_callback(partial(context.run, self.on_node_complete))
finally:
self.executor.shutdown(wait=True, cancel_futures=True)
converged = self.executor.shutdown_bounded(
timeout=WORKFLOW_EXECUTOR_STOP_TIMEOUT_SECONDS,
)
self._execute_returned.set()
if converged:
self._release_execution()
def pop_dispatchable_node(self) -> Optional[str]:
"""
@@ -432,7 +526,7 @@ class WorkflowExecutor:
try:
action, action_result = future.result()
with self.lock:
if global_vars.is_workflow_stopped(self.workflow.id):
if self._stop_requested():
self.success = False
self.stopped = True
self.errmsg = "工作流已停止"
@@ -1238,10 +1332,16 @@ class WorkflowChain(ChainBase):
text=f"开始执行工作流 {workflow.name} ...",
data={"total": len(workflow.actions), "finished": 0},
)
workflowoper.start(workflow_id)
# 执行工作流
executor = WorkflowExecutor(workflow, step_callback=save_step)
if not executor.admit():
logger.warning("工作流服务正在停止,拒绝执行 %s", workflow.name)
return False, executor.errmsg
try:
workflowoper.start(workflow_id)
except Exception:
executor.abort_before_execute()
raise
executor.execute()
if executor.stopped:
+3 -3
View File
@@ -13,8 +13,8 @@ def init_workflow():
WorkFlowManager()
def stop_workflow():
def stop_workflow() -> bool:
"""
停止工作流
停止工作流并返回全部活动执行是否收敛。
"""
WorkFlowManager().stop()
return WorkFlowManager().stop()
+1
View File
@@ -482,6 +482,7 @@ def build_lifecycle_components(app: FastAPI) -> tuple[LifecycleComponent, ...]:
stop_order=20,
start_timeout_seconds=120,
stop_timeout_seconds=120,
stop_failure=LifecycleFailurePolicy.FAIL_FAST,
),
LifecycleComponent(
name="插件备份",
+60 -7
View File
@@ -7,6 +7,7 @@ from pydantic import BaseModel
from app.runtime.config import global_vars
from app.runtime.events import eventmanager, Event
from app.application.chain.data import get_chain_workflow_port
from app.application.workflow import WorkflowExecutionOwner
from app.foundation.reflection import ModuleHelper
from app.runtime.log import logger
from app.schemas.workflow import ActionContext
@@ -16,17 +17,22 @@ from app.schemas.workflow import Workflow
from app.schemas.types import EventType
from app.foundation.singleton import Singleton
_WORKFLOW_STOP_TIMEOUT_SECONDS = 10.0
class WorkFlowManager(metaclass=Singleton):
"""
工作流管理器
"""
def __init__(self):
def __init__(self) -> None:
"""创建动作、事件触发器和活动执行 owner 注册表。"""
# 所有动作定义
self._lock = threading.Lock()
self._lock = threading.RLock()
self._actions: Dict[str, Any] = {}
self._event_workflows: Dict[str, List[int]] = {}
self._accepting_executions = True
self._executions: Dict[int, WorkflowExecutionOwner] = {}
self.init()
def init(self):
@@ -62,14 +68,61 @@ class WorkFlowManager(metaclass=Singleton):
# 加载工作流事件触发器
self.load_workflow_events()
def stop(self):
def register_execution(self, owner: WorkflowExecutionOwner) -> bool:
"""登记活动执行;生命周期封口后拒绝新的工作流。"""
with self._lock:
if not self._accepting_executions:
return False
self._executions[id(owner)] = owner
return True
def unregister_execution(self, owner: WorkflowExecutionOwner) -> None:
"""仅在执行及其节点线程池真实终止后释放 owner。"""
with self._lock:
self._executions.pop(id(owner), None)
def stop(
self,
timeout: float = _WORKFLOW_STOP_TIMEOUT_SECONDS,
) -> bool:
"""
停止
封口工作流入口并有限等待全部活动执行。
:param timeout: 等待活动执行真实终止的最长秒数
:return: 全部执行终止并安全释放动作注册表时返回 True
"""
for event_type_str in list(self._event_workflows.keys()):
with self._lock:
self._accepting_executions = False
event_type_values = tuple(self._event_workflows)
for event_type_str in event_type_values:
self.remove_workflow_event(event_type_str=event_type_str)
self._actions = {}
self._event_workflows = {}
with self._lock:
self._event_workflows = {}
executions = tuple(self._executions.values())
converged = True
for execution in executions:
try:
execution.request_stop()
except Exception as err:
converged = False
logger.error("请求停止工作流执行失败:%s", err)
deadline = monotonic() + max(0.0, timeout)
for execution in executions:
try:
if not execution.wait_stopped(
timeout=max(0.0, deadline - monotonic()),
):
converged = False
except Exception as err:
converged = False
logger.error("等待工作流执行停止失败:%s", err)
with self._lock:
converged = converged and not self._executions
if converged:
self._actions = {}
return converged
def execute(self, workflow_id: int, action: Action, context: ActionContext = None,
inputs: Optional[dict] = None, runtime: Optional[dict] = None,