fixed shutdown

This commit is contained in:
Boki 2025-06-28 08:41:24 -04:00
parent 00fbd31364
commit 73399ef142
2 changed files with 172 additions and 28 deletions

View file

@ -3,15 +3,15 @@
* Encapsulates common patterns for Hono-based microservices
*/
import type { AwilixContainer } from 'awilix';
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import type { BaseAppConfig, UnifiedAppConfig } from '@stock-bot/config';
import { toUnifiedConfig } from '@stock-bot/config';
import type { HandlerRegistry } from '@stock-bot/handler-registry';
import { getLogger, setLoggerConfig, shutdownLoggers, type Logger } from '@stock-bot/logger';
import { Shutdown, SHUTDOWN_DEFAULTS } from '@stock-bot/shutdown';
import type { IServiceContainer } from '@stock-bot/types';
import type { AwilixContainer } from 'awilix';
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import type { ServiceDefinitions } from './container/types';
/**
@ -164,19 +164,30 @@ export class ServiceApplication {
* Register graceful shutdown handlers
*/
private registerShutdownHandlers(): void {
this.logger.info('Registering shutdown handlers', {
service: this.serviceConfig.serviceName,
scheduledJobs: this.serviceConfig.enableScheduledJobs,
timeout: this.serviceConfig.shutdownTimeout
});
// Priority 1: Queue system (highest priority)
if (this.serviceConfig.enableScheduledJobs) {
this.shutdown.onShutdown(
async () => {
this.logger.info('Shutting down queue system...');
const startTime = Date.now();
try {
const queueManager = this.container?.resolve('queueManager');
if (queueManager) {
await queueManager.shutdown();
const duration = Date.now() - startTime;
this.logger.info('Queue system shut down successfully', { duration });
} else {
this.logger.warn('No queue manager found to shut down');
}
this.logger.info('Queue system shut down');
} catch (error) {
this.logger.error('Error shutting down queue system', { error });
const duration = Date.now() - startTime;
this.logger.error('Error shutting down queue system', { error, duration });
}
},
SHUTDOWN_DEFAULTS.HIGH_PRIORITY,
@ -189,12 +200,17 @@ export class ServiceApplication {
async () => {
if (this.server) {
this.logger.info('Stopping HTTP server...');
const startTime = Date.now();
try {
this.server.stop();
this.logger.info('HTTP server stopped');
const duration = Date.now() - startTime;
this.logger.info('HTTP server stopped successfully', { duration });
} catch (error) {
this.logger.error('Error stopping HTTP server', { error });
const duration = Date.now() - startTime;
this.logger.error('Error stopping HTTP server', { error, duration });
}
} else {
this.logger.warn('No HTTP server to stop');
}
},
SHUTDOWN_DEFAULTS.HIGH_PRIORITY,
@ -220,28 +236,62 @@ export class ServiceApplication {
this.shutdown.onShutdown(
async () => {
this.logger.info('Disposing services and connections...');
const startTime = Date.now();
const disposed: string[] = [];
const failed: string[] = [];
try {
if (this.container) {
// Disconnect database clients
const mongoClient = this.container.resolve('mongoClient');
if (mongoClient?.disconnect) {
await mongoClient.disconnect();
try {
const mongoStart = Date.now();
await mongoClient.disconnect();
disposed.push(`MongoDB (${Date.now() - mongoStart}ms)`);
} catch (error) {
this.logger.error('Failed to disconnect MongoDB', { error });
failed.push('MongoDB');
}
}
const postgresClient = this.container.resolve('postgresClient');
if (postgresClient?.disconnect) {
await postgresClient.disconnect();
try {
const pgStart = Date.now();
await postgresClient.disconnect();
disposed.push(`PostgreSQL (${Date.now() - pgStart}ms)`);
} catch (error) {
this.logger.error('Failed to disconnect PostgreSQL', { error });
failed.push('PostgreSQL');
}
}
const questdbClient = this.container.resolve('questdbClient');
if (questdbClient?.disconnect) {
await questdbClient.disconnect();
try {
const questStart = Date.now();
await questdbClient.disconnect();
disposed.push(`QuestDB (${Date.now() - questStart}ms)`);
} catch (error) {
this.logger.error('Failed to disconnect QuestDB', { error });
failed.push('QuestDB');
}
}
this.logger.info('All services disposed successfully');
const duration = Date.now() - startTime;
this.logger.info('Service disposal completed', {
duration,
disposed,
failed,
success: failed.length === 0
});
} else {
this.logger.warn('No container available for service disposal');
}
} catch (error) {
this.logger.error('Error disposing services', { error });
const duration = Date.now() - startTime;
this.logger.error('Error disposing services', { error, duration, disposed, failed });
}
},
SHUTDOWN_DEFAULTS.MEDIUM_PRIORITY,
@ -253,15 +303,23 @@ export class ServiceApplication {
async () => {
try {
this.logger.info('Shutting down loggers...');
// const startTime = Date.now();
await shutdownLoggers();
// Don't log after shutdown
} catch {
// Silently ignore logger shutdown errors
// Log one final message before shutdown
// console.log(`[${new Date().toISOString()}] Loggers shut down successfully (${Date.now() - startTime}ms)`);
} catch (error) {
// Use console.error since logger might be partially shut down
console.error(`[${new Date().toISOString()}] Error shutting down loggers:`, error);
}
},
SHUTDOWN_DEFAULTS.LOW_PRIORITY,
'Loggers'
);
this.logger.info('All shutdown handlers registered', {
service: this.serviceConfig.serviceName,
handlerCount: 3 + (this.serviceConfig.enableScheduledJobs ? 1 : 0) + (this.hooks.onBeforeShutdown ? 1 : 0)
});
}
/**