ruvnet/ruflo · error
Connection pool is shutting down
Error message
Connection pool is shutting down
What it means
The MCP connection pool's acquire() refuses to hand out a connection once shutdown() has begun: during teardown idle connections are destroyed and the pool drains, so any handle granted mid-shutdown would be dead on arrival. The internal isShuttingDown flag is checked at the top of acquire(), making the race fail fast instead of returning a broken connection. Note isShuttingDown is private; pool.isHealthy() indirectly reflects it (it returns false when shutting down).
Source
Thrown at v3/@claude-flow/shared/src/mcp/connection-pool.ts:169
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;
}
/**
* Acquire a connection from the pool
*/
async acquire(): Promise<PooledConnection> {
const startTime = performance.now();
if (this.isShuttingDown) {
throw new Error('Connection pool is shutting down');
}
// Try to find an idle connection
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;
}
}
// Create new connection if under limit
if (this.connections.size < this.config.maxConnections) {View on GitHub (pinned to fa13ee4ad6)
Solutions
- Fix teardown order: stop admitting new work (close the HTTP server, set a draining flag), await outstanding requests, THEN call pool.shutdown()
- Gate every acquire with a caller-side closing flag, and treat 'Connection pool is shutting down' as a terminal, non-retryable signal
- Await pool.shutdown() fully during teardown so no acquire can race it
- In tests, ensure every request has settled before closing a shared pool in afterAll
Example fix
// before
process.on('SIGTERM', () => pool.shutdown());
app.listen(3000); // requests still arrive; acquire() races shutdown
// after
process.on('SIGTERM', async () => {
draining = true; // gate new acquires in the request path
await closeServerGracefully(); // stop admitting work first
await pool.shutdown(); // now drain safely
}); Defensive patterns
Strategy: try-catch
Validate before calling
if (draining || !pool.isHealthy()) {
throw new ServiceUnavailableError('pool is draining');
}
const conn = await pool.acquire(); // still wrap in try-catch: isHealthy() can race shutdown Try / catch
try {
return await withTimeout(pool.acquire(), 5_000);
} catch (e) {
if (e instanceof Error && /shutting down/i.test(e.message)) {
throw new ServiceUnavailableError('MCP pool is shutting down'); // terminal: never retry
}
throw e; // other errors may be transient and handled by the caller
} Prevention
- Order teardown: stop admitting work, await in-flight requests, then pool.shutdown()
- Keep a caller-side draining flag checked before every acquire
- Classify 'shutting down' as non-retryable so retry loops terminate instead of spinning
- In tests, close shared pools only after all requests have settled
When it happens
Trigger: An in-flight request calls acquire() after a SIGTERM handler invoked pool.shutdown(); a background loop (heartbeat, keepalive, worker poll) keeps acquiring while the app closes; the HTTP server still accepts new work after pool.shutdown() was called; a test's afterAll closes a shared pool while a dangling async test still acquires.
Common situations: Graceful-shutdown ordering bugs: closing the pool before the listener that feeds it; long-lived workers never signalled to stop before teardown; retry wrappers that treat this terminal error as transient and keep looping.
Related errors
- Connection pool is shutting down
- SSRF guard: invalid URL — ${rawUrl}
- User token not found
- Unauthorized
- Stdio transport already running
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/9743fe654fd4dfaf.
Report an issue: GitHub.