execution-lifecycle-manager について
DAG 実行のライフサイクル管理を実装する開発者向けのスキルです。状態追跡、キャンセルの伝播、グレースフルシャットダウン、孤児プロセスのクリーンアップ、中断時のコスト記録を扱います。
主な機能
- 01実行中の処理と状態の追跡
- 02AbortSignal による子実行へのキャンセル伝播
- 03SIGTERM を試してから SIGKILL に移行
- 04終了時の実行停止とクリーンアップ
- 05中断処理での最終コスト記録
活用シーン
- 01DAG 実行に開始・停止・一時停止・再開を追加
- 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 構造の構築や変更
- スキルの照合や選択
- プロセスの起動(executor を直接使用します)
主なパターン
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〜5 秒経っても停止しない場合:SIGKILL を使用する
正しい方法:必ず最初に SIGTERM を使用し、SIGKILL はフォールバックとして使用します。
Abort シグナルの伝播が不足している
初心者の考え方:「最上位の実行だけを追跡すればよい」
実際の問題:シグナルが伝播しないと、子プロセスが孤立します:
- 親プロセスが終了しても、子プロセスは実行を続ける
- リソースがリークする
- コストが発生し続ける
正しい方法:実行ツリー全体に 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 を参照してください。