66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
import {
|
|
Injectable,
|
|
Logger,
|
|
OnApplicationShutdown,
|
|
BeforeApplicationShutdown,
|
|
} from '@nestjs/common';
|
|
import { HealthController } from './health.controller';
|
|
|
|
/**
|
|
* Graceful Shutdown Service - ensures zero-downtime rolling upgrades.
|
|
*
|
|
* Shutdown sequence:
|
|
* 1. Receive SIGTERM (from K8s, docker stop, etc.)
|
|
* 2. Mark service as NOT ready (health/ready returns 503)
|
|
* 3. Wait for drain period (allow in-flight requests to complete)
|
|
* 4. Close connections (DB, Redis, Kafka)
|
|
* 5. Exit process
|
|
*
|
|
* K8s preStop hook should wait ~5s before sending SIGTERM,
|
|
* giving the load balancer time to remove this pod from rotation.
|
|
*/
|
|
@Injectable()
|
|
export class GracefulShutdownService
|
|
implements BeforeApplicationShutdown, OnApplicationShutdown
|
|
{
|
|
private readonly logger = new Logger('GracefulShutdown');
|
|
private readonly drainTimeoutMs: number;
|
|
|
|
constructor(private readonly healthController: HealthController) {
|
|
this.drainTimeoutMs = parseInt(
|
|
process.env.GRACEFUL_SHUTDOWN_DRAIN_MS || '10000',
|
|
10,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Called before application shutdown begins.
|
|
* Mark as not ready and wait for drain period.
|
|
*/
|
|
async beforeApplicationShutdown(signal?: string) {
|
|
this.logger.warn(
|
|
`Shutdown signal received: ${signal || 'unknown'}. Starting graceful shutdown...`,
|
|
);
|
|
|
|
// Step 1: Mark as not ready (stop accepting new requests)
|
|
this.healthController.setReady(false);
|
|
this.logger.log('Marked service as NOT ready');
|
|
|
|
// Step 2: Wait for drain period (in-flight requests to complete)
|
|
this.logger.log(
|
|
`Waiting ${this.drainTimeoutMs}ms for in-flight requests to drain...`,
|
|
);
|
|
await new Promise((resolve) =>
|
|
setTimeout(resolve, this.drainTimeoutMs),
|
|
);
|
|
this.logger.log('Drain period complete');
|
|
}
|
|
|
|
/**
|
|
* Called after application shutdown. Final cleanup logging.
|
|
*/
|
|
async onApplicationShutdown(signal?: string) {
|
|
this.logger.log(`Application shutdown complete (signal: ${signal || 'none'})`);
|
|
}
|
|
}
|