Token导航 LogoToken导航TokenDH.com
前端设计权限需确认github未标认证来源可访问许可证需确认审计异常

graceful-shutdown优雅关闭

Agent Skill

graceful-shutdown 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

494

周安装

21

GitHub Stars

777

下载量

173
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:graceful-shutdown(优雅关闭)
来源仓库:https://github.com/dadbodgeoff/drift
仓库路径:skills/graceful-shutdown
安装命令:
npx skills add https://github.com/dadbodgeoff/drift --skill graceful-shutdown
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/dadbodgeoff/drift --skill graceful-shutdown

简介

graceful-shutdown 用于安全终止进程与释放资源,适合在 Codex、Claude、Cursor、Gemini CLI 中需要处理后台 worker 或长任务时使用。

  • 它捕获信号、停止接收新任务并排空缓冲区,支持回调清理与连接关闭,避免数据丢失。
  • 使用时需定义 in-flight 任务超时与缓冲上限,防止无限等待;建议结合健康检查联动。
  • 安装前请确认信号处理方式,注意是否会中断关键操作,确保退出流程幂等可靠。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

Graceful Shutdown

Clean shutdown without data loss.

When to Use This Skill

  • Running background workers
  • Processing queues or streams
  • Buffering data before persistence
  • Any long-running process that handles state

Core Concepts

  1. Signal handlers - Catch SIGTERM/SIGINT
  2. In-flight tracking - Know what's still running
  3. Buffer draining - Flush before exit
  4. Cleanup callbacks - Close connections properly

Shutdown Flow

SIGTERM received
      │
      ▼
Stop accepting new work
      │
      ▼
Wait for in-flight jobs
      │
      ▼
Drain buffers
      │
      ▼
Run cleanup callbacks
      │
      ▼
Process exits

TypeScript Implementation

// graceful-shutdown.ts
type ShutdownCallback = () => Promise<void>;
type DrainCallback = () => Promise<{ flushed: number; dropped: number }>;

interface InFlightJob {
  id: string;
  workerName: string;
  startedAt: Date;
  timeoutMs: number;
}

class GracefulShutdown {
  private isShuttingDown = false;
  private shutdownPromise: Promise<void> | null = null;
  private callbacks: ShutdownCallback[] = [];
  private drainCallbacks: DrainCallback[] = [];
  private inFlightJobs = new Map<string, InFlightJob>();
  private shutdownTimeoutMs = 30000;

  registerSignals(): void {
    const handler = (signal: string) => {
      console.log(`[Shutdown] Received ${signal}`);
      this.shutdown(`Signal: ${signal}`);
    };

    process.on('SIGTERM', () => handler('SIGTERM'));
    process.on('SIGINT', () => handler('SIGINT'));
  }

  onShutdown(callback: ShutdownCallback): void {
    this.callbacks.push(callback);
  }

  onDrain(callback: DrainCallback): void {
    this.drainCallbacks.push(callback);
  }

  trackJob(id: string, workerName: string, timeoutMs = 60000): void {
    if (this.isShuttingDown) return;
    this.inFlightJobs.set(id, { id, workerName, startedAt: new Date(), timeoutMs });
  }

  completeJob(id: string): void {
    this.inFlightJobs.delete(id);
  }

  isShutdownInProgress(): boolean {
    return this.isShuttingDown;
  }

  async shutdown(reason: string): Promise<void> {
    if (this.shutdownPromise) return this.shutdownPromise;

    this.isShuttingDown = true;
    console.log(`[Shutdown] Starting: ${reason}`);

    this.shutdownPromise = this.performShutdown();
    return this.shutdownPromise;
  }

  private async performShutdown(): Promise<void> {
    const startTime = Date.now();

    // 1. Wait for in-flight jobs
    console.log(`[Shutdown] Waiting for ${this.inFlightJobs.size} jobs...`);

    while (this.inFlightJobs.size > 0) {
      if (Date.now() - startTime > this.shutdownTimeoutMs) {
        console.log(`[Shutdown] Timeout! ${this.inFlightJobs.size} jobs still running`);
        break;
      }

      // Force-complete stuck jobs
      const now = Date.now();
      for (const [id, job] of this.inFlightJobs) {
        if (now - job.startedAt.getTime() > job.timeoutMs) {
          console.log(`[Shutdown] Force-completing stuck job: ${id}`);
          this.inFlightJobs.delete(id);
        }
      }

      await this.sleep(100);
    }

    // 2. Drain buffers
    if (this.drainCallbacks.length > 0) {
      console.log(`[Shutdown] Draining ${this.drainCallbacks.length} buffers...`);

      let totalFlushed = 0, totalDropped = 0;

      for (const drain of this.drainCallbacks) {
        try {
          const result = await Promise.race([
            drain(),
            this.sleep(10000).then(() => ({ flushed: 0, dropped: 0 })),
          ]);
          totalFlushed += result.flushed;
          totalDropped += result.dropped;
        } catch (err) {
          console.error('[Shutdown] Drain error:', err);
        }
      }

      console.log(`[Shutdown] Drained: ${totalFlushed} flushed, ${totalDropped} dropped`);
    }

    // 3. Run cleanup callbacks
    console.log(`[Shutdown] Running ${this.callbacks.length} cleanup callbacks...`);

    for (const callback of this.callbacks) {
      try {
        await Promise.race([
          callback(),
          this.sleep(5000).then(() => { throw new Error('Callback timeout'); }),
        ]);
      } catch (err) {
        console.error('[Shutdown] Callback error:', err);
      }
    }

    console.log(`[Shutdown] Complete in ${Date.now() - startTime}ms`);
    process.exit(0);
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Singleton
let instance: GracefulShutdown | null = null;

export function getShutdownHandler(): GracefulShutdown {
  if (!instance) instance = new GracefulShutdown();
  return instance;
}

Python Implementation

# graceful_shutdown.py
import asyncio
import signal
from dataclasses import dataclass, field
from datetime import datetime
from typing import Callable, Awaitable, Dict, List

@dataclass
class InFlightJob:
    id: str
    worker_name: str
    started_at: datetime
    timeout_seconds: float

ShutdownCallback = Callable[[], Awaitable[None]]
DrainCallback = Callable[[], Awaitable[Dict[str, int]]]

class GracefulShutdown:
    def __init__(self, timeout_seconds: float = 30.0):
        self._is_shutting_down = False
        self._shutdown_task: asyncio.Task | None = None
        self._callbacks: List[ShutdownCallback] = []
        self._drain_callbacks: List[DrainCallback] = []
        self._in_flight: Dict[str, InFlightJob] = {}
        self._timeout = timeout_seconds

    def register_signals(self):
        loop = asyncio.get_event_loop()
        for sig in (signal.SIGTERM, signal.SIGINT):
            loop.add_signal_handler(
                sig,
                lambda s=sig: asyncio.create_task(self.shutdown(f"Signal: {s.name}"))
            )

    def on_shutdown(self, callback: ShutdownCallback):
        self._callbacks.append(callback)

    def on_drain(self, callback: DrainCallback):
        self._drain_callbacks.append(callback)

    def track_job(self, job_id: str, worker_name: str, timeout_seconds: float = 60.0):
        if self._is_shutting_down:
            return
        self._in_flight[job_id] = InFlightJob(
            id=job_id,
            worker_name=worker_name,
            started_at=datetime.now(),
            timeout_seconds=timeout_seconds,
        )

    def complete_job(self, job_id: str):
        self._in_flight.pop(job_id, None)

    @property
    def is_shutting_down(self) -> bool:
        return self._is_shutting_down

    async def shutdown(self, reason: str):
        if self._shutdown_task:
            return await self._shutdown_task

        self._is_shutting_down = True
        print(f"[Shutdown] Starting: {reason}")

        self._shutdown_task = asyncio.create_task(self._perform_shutdown())
        return await self._shutdown_task

    async def _perform_shutdown(self):
        start_time = datetime.now()

        # Wait for in-flight jobs
        print(f"[Shutdown] Waiting for {len(self._in_flight)} jobs...")

        while self._in_flight:
            elapsed = (datetime.now() - start_time).total_seconds()
            if elapsed > self._timeout:
                print(f"[Shutdown] Timeout! {len(self._in_flight)} jobs still running")
                break

            # Force-complete stuck jobs
            now = datetime.now()
            stuck = [
                job_id for job_id, job in self._in_flight.items()
                if (now - job.started_at).total_seconds() > job.timeout_seconds
            ]
            for job_id in stuck:
                print(f"[Shutdown] Force-completing stuck job: {job_id}")
                self._in_flight.pop(job_id)

            await asyncio.sleep(0.1)

        # Drain buffers
        for drain in self._drain_callbacks:
            try:
                result = await asyncio.wait_for(drain(), timeout=10.0)
                print(f"[Shutdown] Drained: {result}")
            except Exception as e:
                print(f"[Shutdown] Drain error: {e}")

        # Run cleanup callbacks
        for callback in self._callbacks:
            try:
                await asyncio.wait_for(callback(), timeout=5.0)
            except Exception as e:
                print(f"[Shutdown] Callback error: {e}")

        elapsed = (datetime.now() - start_time).total_seconds()
        print(f"[Shutdown] Complete in {elapsed:.1f}s")

# Singleton
_instance: GracefulShutdown | None = None

def get_shutdown_handler() -> GracefulShutdown:
    global _instance
    if _instance is None:
        _instance = GracefulShutdown()
    return _instance

Usage Examples

Basic Setup

const shutdown = getShutdownHandler();
shutdown.registerSignals();

// Register cleanup
shutdown.onShutdown(async () => {
  await database.close();
  await redis.quit();
});

// Register buffer drain
shutdown.onDrain(async () => {
  return backpressureBuffer.flush();
});

Job Tracking

async function processJob(jobId: string) {
  const shutdown = getShutdownHandler();

  // Don't start new work during shutdown
  if (shutdown.isShutdownInProgress()) {
    return;
  }

  shutdown.trackJob(jobId, 'my-worker', 30000);

  try {
    await doWork(jobId);
  } finally {
    shutdown.completeJob(jobId);
  }
}

With Express/Fastify

const shutdown = getShutdownHandler();
shutdown.registerSignals();

// Stop accepting new requests
shutdown.onShutdown(async () => {
  await new Promise<void>((resolve) => {
    server.close(() => resolve());
  });
});

// Close database connections
shutdown.onShutdown(async () => {
  await prisma.$disconnect();
});

Best Practices

  1. Register signals early - First thing in app startup
  2. Track all in-flight work - With appropriate timeouts
  3. Drain before cleanup - Buffers first, connections last
  4. Set reasonable timeouts - Don't hang forever
  5. Check before new work - Don't start during shutdown

Common Mistakes

  • Not registering signal handlers
  • Starting new work during shutdown
  • No timeout on cleanup callbacks
  • Forgetting to track in-flight jobs
  • Closing connections before draining buffers

Related Skills

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Claude

32.19%
按下载量换算56

Codex

31.26%
按下载量换算54

Cursor

19.6%
按下载量换算34

Gemini CLI

9.72%
按下载量换算17

安全审计

Gen Agent Trust Hub

未通过

Socket

通过

Snyk

通过

权限和风险

权限需确认

当前来源未能明确判断权限范围,默认进入异常复核队列。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills