关于 execution-lifecycle-manager
为实现 DAG 执行管理的开发者提供生命周期管理模式,涵盖状态跟踪、取消传播与优雅关闭。内容也涉及清理孤儿进程和中止后的成本记录。
主要功能
- 01跟踪活动执行及其状态
- 02传播 AbortSignal 以取消子执行
- 03先发送 SIGTERM,再按需使用 SIGKILL
- 04关闭时停止执行并清理进程
- 05中止处理中的最终成本记录
适用场景
- 01为 DAG 执行添加开始、停止、暂停和恢复控制
- 02在服务关闭时终止活动执行
- 03处理超时或用户取消并清理子进程
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill execution-lifecycle-manager安装后在 Claude Code 中输入/execution-lifecycle-manager确认已加载。
附带文件references/process-signals.md
详细介绍
执行生命周期管理器
集中管理运行中的 DAG 执行,并采用优雅关闭模式。
适用场景
✅ 适用于:
- 实现执行的启动/停止/暂停/恢复控制
- 优雅地终止进程(SIGTERM → SIGKILL)
- 跟踪整个系统中的活动执行
- 清理孤立进程
- 实现带成本跟踪的中止处理程序
❌ 不适用于:
- 成本估算或定价计算(使用 cost-accrual-tracker)
- 构建或修改 DAG 结构
- 技能匹配或选择
- 启动进程(直接使用执行器)
核心模式
1. 优雅关闭模式
始终先使用 SIGTERM,然后再升级为 SIGKILL:
// CORRECT: Two-phase shutdown
const GRACEFUL_TIMEOUT_MS = 2000;
async function terminateProcess(proc: ChildProcess): Promise<void> {
proc.kill('SIGTERM');
const forceKillTimer = setTimeout(() => {
if (!proc.killed) {
proc.kill('SIGKILL');
}
}, GRACEFUL_TIMEOUT_MS);
await waitForExit(proc);
clearTimeout(forceKillTimer);
}
2. AbortController 模式
使用 AbortController 传播取消信号:
// Parent (DAGExecutor)
const abortController = new AbortController();
// Pass signal to child executors
await executor.execute({
...request,
abortSignal: abortController.signal,
});
// To abort all children:
abortController.abort();
3. 执行注册表模式
跟踪活动执行,以便监控和清理:
interface ActiveExecution {
executionId: string;
abortController: AbortController;
status: 'running' | 'stopping' | 'stopped' | 'completed' | 'failed';
startedAt: number;
stoppedAt?: number;
}
class ExecutionManager {
private executions: Map<string, ActiveExecution> = new Map();
create(id: string): ActiveExecution { /* ... */ }
stop(id: string, reason: string): Promise<StopResult> { /* ... */ }
listActive(): ActiveExecution[] { /* ... */ }
}
反模式
不发送 SIGTERM,直接使用 SIGKILL
新手想法:“直接杀掉进程就行。”
实际情况:SIGKILL 不允许执行清理操作。进程无法:
- 将缓冲区刷新到磁盘
- 正常关闭网络连接
- 释放锁
- 保存部分进度
时间安排:
- 始终先使用 SIGTERM,以便优雅关闭
- 如果进程在 2-5s 后仍无响应,再使用 SIGKILL
正确做法:始终先使用 SIGTERM,将 SIGKILL 作为后备方案。
未传播中止信号
新手想法:“只跟踪顶层执行就行。”
实际情况:如果没有传播信号,子进程就会成为孤立进程:
- 父进程退出,子进程仍在运行
- 资源泄漏
- 成本持续累积
正确做法:在整个执行树中传递 AbortSignal。
同步停止处理程序
新手想法:“停止操作应该立即返回。”
实际情况:停止是异步操作,进程需要时间才能终止:
- 网络请求需要超时
- 文件句柄需要关闭
- 需要进行最终成本计算
正确做法:返回 Promise,并在清理完成后提供最终状态。
状态机
┌──────────┐
│ idle │
└────┬─────┘
│ start()
▼
┌──────────┐
┌───►│ running │◄───┐
│ └────┬─────┘ │
│ │ │ resume()
│ │ pause() │
│ ▼ │
│ ┌──────────┐ │
│ │ paused │────┘
│ └────┬─────┘
│ │ stop()
│ ▼
│ ┌──────────┐
└────│ stopping │ (transitional - 2-10s)
└────┬─────┘
│
┌────────┴────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ stopped │ │ failed │
└──────────┘ └──────────┘
API 设计
停止端点响应
interface StopResponse {
status: 'stopped';
executionId: string;
reason: string; // 'user_abort' | 'timeout' | 'error'
finalCostUsd: number;
stoppedAt: number;
summary: {
nodesCompleted: number;
nodesFailed: number;
nodesTotal: number;
durationMs: number;
};
}
服务关闭时的清理
// In server.ts
process.on('SIGINT', async () => {
console.log('Shutting down...');
// Stop all active executions gracefully
const active = executionManager.listActive();
await Promise.all(
active.map(e => executionManager.stop(e.executionId, 'server_shutdown'))
);
server.close();
});
集成点
| 组件 | 职责 |
|---|---|
ExecutionManager | 跟踪执行并协调停止操作 |
DAGExecutor | 管理 AbortController 并编排执行波次 |
ProcessExecutor | 启动进程并处理 SIGTERM/SIGKILL |
/api/execute/stop | 停止请求的 HTTP 接口 |
参考资料
有关 Unix 信号处理的详情,请参阅 /references/process-signals.md。
评论
支持 Markdown