Files

179 lines
6.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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)