ruvnet/ruflo · error · Error

Connection pool is shutting down

Error message

Connection pool is shutting down

What it means

ConnectionPool.acquire() checks isShuttingDown and refuses new checkouts once shutdown() has begun — the pool is draining existing connections and hands out nothing new. Any acquire that races teardown gets this error.

Source

Thrown at v3/@claude-flow/mcp/src/connection-pool.ts:124

  private async createConnection(): Promise<ManagedConnection> {
    const id = `conn-${++this.connectionCounter}-${Date.now()}`;
    const connection = new ManagedConnection(id, this.transportType);

    this.connections.set(id, connection);
    this.stats.totalCreated++;

    this.emit('pool:connection:created', { connectionId: id });
    this.logger.debug('Connection created', { id, total: this.connections.size });

    return connection;
  }

  async acquire(): Promise<PooledConnection> {
    const startTime = performance.now();

    if (this.isShuttingDown) {
      throw new Error('Connection pool is shutting down');
    }

    for (const connection of this.connections.values()) {
      if (connection.state === 'idle' && connection.isHealthy()) {
        connection.acquire();
        this.stats.totalAcquired++;
        this.recordAcquireTime(startTime);

        this.emit('pool:connection:acquired', { connectionId: connection.id });
        this.logger.debug('Connection acquired from pool', { id: connection.id });

        return connection;
      }
    }

    if (this.connections.size < this.config.maxConnections) {
      const connection = await this.createConnection();
      connection.acquire();

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Fix teardown order: stop accepting/producing work, await in-flight tasks, then call pool.shutdown()
  2. In late-running code, catch this error and skip — acquiring during a drain is a logic bug, not a retryable fault
  3. Gate connection use on your own draining flag set before pool.shutdown()

Example fix

// before
process.on('SIGTERM', () => pool.shutdown());
setInterval(work, 1000); // work() calls pool.acquire() -> throws during drain

// after
let draining = false;
const timer = setInterval(work, 1000);
process.on('SIGTERM', () => {
  draining = true;
  clearInterval(timer);
  pool.shutdown();
});
async function work() {
  if (draining) return;
  const conn = await pool.acquire();
  // ...
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Flip your own gate before initiating shutdown
let draining = false;
async function shutdown() {
  draining = true;
  await inFlight;   // let consumers finish
  await pool.shutdown();
}
async function withConnection<T>(fn: (c: PooledConnection) => Promise<T>): Promise<T> {
  if (draining) throw new Error('app draining');
  return fn(await pool.acquire());
}

Try / catch

try {
  const conn = await pool.acquire();
} catch (e) {
  if (e instanceof Error && e.message === 'Connection pool is shutting down') {
    return; // we're draining — skip this work item entirely, never retry
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling acquire() after pool.shutdown() started: typically in-flight request handlers, timers, or background jobs that still run during process or service shutdown and attempt a checkout.

Common situations: A SIGTERM handler closing the pool while requests are still draining; a timer firing mid-shutdown; cleanup ordering bugs where shutdown runs before the last consumers finish.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/42897d628b95d1e6. Report an issue: GitHub.