179 lines
6.6 KiB
Python
179 lines
6.6 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
并行分支节点处理器
|
||
处理并行分支的执行和汇聚
|
||
"""
|
||
import copy
|
||
import logging
|
||
from typing import Dict, TYPE_CHECKING
|
||
|
||
from sqlalchemy.orm.attributes import flag_modified
|
||
|
||
from online_dev.workflow.engine.handlers.base_handler import BaseNodeHandler
|
||
|
||
if TYPE_CHECKING:
|
||
from online_dev.workflow.engine.base import ExecutionContext
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class ParallelHandler(BaseNodeHandler):
|
||
"""
|
||
并行分支节点处理器
|
||
|
||
同时执行所有分支,所有分支完成后汇聚继续
|
||
"""
|
||
|
||
async def execute(self, context: 'ExecutionContext', node: Dict) -> None:
|
||
"""
|
||
执行并行节点:同时启动所有分支
|
||
"""
|
||
node_id = self.get_node_id(node)
|
||
node_name = self.get_node_name(node)
|
||
branches = node.get('branches', [])
|
||
|
||
logger.info(f"处理并行分支 - 节点: {node_id}, 分支数: {len(branches)}")
|
||
|
||
if not branches:
|
||
logger.warning(f"并行节点 {node_id} 没有分支,自动跳过")
|
||
await self.advance_to_next(context, node)
|
||
return
|
||
|
||
# 更新实例当前节点
|
||
await self.update_instance_node(context, node)
|
||
|
||
# 初始化并行分支状态(深拷贝确保 SQLAlchemy 检测到变更)
|
||
parallel_status = copy.deepcopy(context.instance.parallel_branch_status or {})
|
||
parallel_status[node_id] = {
|
||
branch.get('id', ''): 'pending' for branch in branches
|
||
}
|
||
context.instance.parallel_branch_status = parallel_status
|
||
flag_modified(context.instance, 'parallel_branch_status')
|
||
context.db.add(context.instance)
|
||
await context.db.flush()
|
||
|
||
# 记录进入并行分支
|
||
await self.create_log(
|
||
context, node, 'parallel_start',
|
||
comment=f'进入并行分支,共 {len(branches)} 个分支',
|
||
extra_data={
|
||
'branch_count': len(branches),
|
||
'branch_ids': [b.get('id') for b in branches],
|
||
},
|
||
)
|
||
|
||
# 同时执行所有分支
|
||
for branch in branches:
|
||
branch_children = branch.get('children')
|
||
if branch_children:
|
||
await self._execute_branch(context, node, branch, branch_children)
|
||
else:
|
||
# 空分支,标记为完成
|
||
await self.mark_branch_complete(context, node, branch)
|
||
|
||
# 检查是否所有分支都已完成
|
||
await self.check_completion(context, node)
|
||
|
||
async def _execute_branch(
|
||
self,
|
||
context: 'ExecutionContext',
|
||
parallel_node: Dict,
|
||
branch: Dict,
|
||
first_node: Dict,
|
||
) -> None:
|
||
"""
|
||
执行并行分支中的第一个节点。
|
||
统一通过引擎 handler 分发,支持所有节点类型(approval, handle, copy, notify,
|
||
condition, delay, service, subflow, data_update, parallel 等)。
|
||
非阻塞节点执行后会自动调用 advance_to_next 推进;阻塞节点(approval/handle)
|
||
在任务完成时由引擎推进。当分支走到末尾时,引擎的 _advance_to_next 会通过
|
||
find_parallel_branch_for_node 找到所属并行分支并 mark_branch_complete。
|
||
"""
|
||
node_type = first_node.get('type')
|
||
handler = self.engine._get_handler(node_type)
|
||
|
||
if handler:
|
||
await handler.execute(context, first_node)
|
||
else:
|
||
logger.warning(f"并行分支内未知节点类型: {node_type},标记分支完成")
|
||
await self.mark_branch_complete(context, parallel_node, branch)
|
||
|
||
async def mark_branch_complete(
|
||
self,
|
||
context: 'ExecutionContext',
|
||
parallel_node: Dict,
|
||
branch: Dict,
|
||
) -> None:
|
||
"""
|
||
标记并行分支完成
|
||
"""
|
||
node_id = self.get_node_id(parallel_node)
|
||
branch_id = branch.get('id', '')
|
||
branch_name = branch.get('name', '')
|
||
|
||
# 更新分支状态(深拷贝确保 SQLAlchemy 检测到 JSON 字段变更)
|
||
await context.db.refresh(context.instance)
|
||
parallel_status = copy.deepcopy(context.instance.parallel_branch_status or {})
|
||
|
||
if node_id in parallel_status:
|
||
parallel_status[node_id][branch_id] = 'completed'
|
||
context.instance.parallel_branch_status = parallel_status
|
||
flag_modified(context.instance, 'parallel_branch_status')
|
||
context.db.add(context.instance)
|
||
await context.db.flush()
|
||
|
||
logger.info(f"并行分支完成 - 节点: {node_id}, 分支: {branch_id}")
|
||
|
||
# 记录分支完成
|
||
await self.create_log(
|
||
context, parallel_node, 'parallel_branch_complete',
|
||
comment=f'并行分支 {branch_name} 完成',
|
||
extra_data={'branch_id': branch_id},
|
||
)
|
||
|
||
# 检查是否所有分支都已完成
|
||
await self.check_completion(context, parallel_node)
|
||
|
||
async def check_completion(self, context: 'ExecutionContext', parallel_node: Dict) -> None:
|
||
"""
|
||
检查并行分支是否全部完成
|
||
"""
|
||
node_id = self.get_node_id(parallel_node)
|
||
|
||
# 刷新实例获取最新状态
|
||
await context.db.refresh(context.instance)
|
||
parallel_status = copy.deepcopy(context.instance.parallel_branch_status or {})
|
||
|
||
branch_statuses = parallel_status.get(node_id, {})
|
||
|
||
if not branch_statuses:
|
||
logger.warning(f"并行节点 {node_id} 没有分支状态记录")
|
||
return
|
||
|
||
# 检查是否所有分支都已完成
|
||
all_completed = all(status == 'completed' for status in branch_statuses.values())
|
||
pending_branches = [bid for bid, status in branch_statuses.items() if status != 'completed']
|
||
|
||
logger.info(f"并行节点 {node_id} 状态检查: 全部完成={all_completed}, 待完成={pending_branches}")
|
||
|
||
if all_completed:
|
||
logger.info(f"并行节点 {node_id} 所有分支完成,继续推进")
|
||
|
||
# 清理已完成的并行节点状态(深拷贝确保变更检测)
|
||
parallel_status = copy.deepcopy(parallel_status)
|
||
del parallel_status[node_id]
|
||
context.instance.parallel_branch_status = parallel_status
|
||
flag_modified(context.instance, 'parallel_branch_status')
|
||
context.db.add(context.instance)
|
||
await context.db.flush()
|
||
|
||
# 记录日志
|
||
await self.create_log(
|
||
context, parallel_node, 'parallel_complete',
|
||
comment='所有并行分支完成',
|
||
)
|
||
|
||
# 推进到下一节点
|
||
await self.advance_to_next(context, parallel_node)
|