fixed queue close
This commit is contained in:
parent
c048e00d7f
commit
dbfa80b2a2
2 changed files with 84 additions and 121 deletions
|
|
@ -1,13 +1,13 @@
|
|||
import { getLogger } from '@stock-bot/logger';
|
||||
import { QueueRateLimiter } from './rate-limiter';
|
||||
import { Queue, type QueueWorkerConfig } from './queue';
|
||||
import { CacheProvider, createCache } from '@stock-bot/cache';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
import { Queue, type QueueWorkerConfig } from './queue';
|
||||
import { QueueRateLimiter } from './rate-limiter';
|
||||
import type {
|
||||
GlobalStats,
|
||||
QueueManagerConfig,
|
||||
QueueOptions,
|
||||
GlobalStats,
|
||||
QueueStats,
|
||||
RateLimitRule
|
||||
RateLimitRule,
|
||||
} from './types';
|
||||
import { getRedisConnection } from './utils';
|
||||
|
||||
|
|
@ -52,9 +52,7 @@ export class QueueManager {
|
|||
*/
|
||||
static getInstance(): QueueManager {
|
||||
if (!QueueManager.instance) {
|
||||
throw new Error(
|
||||
'QueueManager not initialized. Call QueueManager.initialize(config) first.'
|
||||
);
|
||||
throw new Error('QueueManager not initialized. Call QueueManager.initialize(config) first.');
|
||||
}
|
||||
return QueueManager.instance;
|
||||
}
|
||||
|
|
@ -161,7 +159,7 @@ export class QueueManager {
|
|||
logger.info('Queue created with batch cache', {
|
||||
queueName,
|
||||
workers: mergedOptions.workers || 0,
|
||||
concurrency: mergedOptions.concurrency || 1
|
||||
concurrency: mergedOptions.concurrency || 1,
|
||||
});
|
||||
|
||||
return queue;
|
||||
|
|
@ -269,7 +267,11 @@ export class QueueManager {
|
|||
/**
|
||||
* Check rate limits for a job
|
||||
*/
|
||||
async checkRateLimit(queueName: string, handler: string, operation: string): Promise<{
|
||||
async checkRateLimit(
|
||||
queueName: string,
|
||||
handler: string,
|
||||
operation: string
|
||||
): Promise<{
|
||||
allowed: boolean;
|
||||
retryAfter?: number;
|
||||
remainingPoints?: number;
|
||||
|
|
@ -365,7 +367,6 @@ export class QueueManager {
|
|||
logger.info('All queues cleaned', { type, grace, limit });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shutdown all queues and workers (thread-safe)
|
||||
*/
|
||||
|
|
@ -393,15 +394,15 @@ export class QueueManager {
|
|||
private async performShutdown(): Promise<void> {
|
||||
try {
|
||||
// Close all queues (this now includes workers since they're managed by Queue class)
|
||||
const queueShutdownPromises = Array.from(this.queues.values()).map(async (queue) => {
|
||||
const queueShutdownPromises = Array.from(this.queues.values()).map(async queue => {
|
||||
try {
|
||||
// Add timeout to queue.close() to prevent hanging
|
||||
const closePromise = queue.close();
|
||||
const timeoutPromise = new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Queue close timeout')), 100)
|
||||
);
|
||||
await queue.close();
|
||||
// const timeoutPromise = new Promise<never>((_, reject) =>
|
||||
// setTimeout(() => reject(new Error('Queue close timeout')), 100)
|
||||
// );
|
||||
|
||||
await Promise.race([closePromise, timeoutPromise]);
|
||||
// await Promise.race([closePromise, timeoutPromise]);
|
||||
} catch (error) {
|
||||
logger.warn('Error closing queue', { error: (error as Error).message });
|
||||
}
|
||||
|
|
@ -410,7 +411,7 @@ export class QueueManager {
|
|||
await Promise.all(queueShutdownPromises);
|
||||
|
||||
// Close all caches
|
||||
const cacheShutdownPromises = Array.from(this.caches.values()).map(async (cache) => {
|
||||
const cacheShutdownPromises = Array.from(this.caches.values()).map(async cache => {
|
||||
try {
|
||||
// Clear cache before shutdown
|
||||
await cache.clear();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Queue as BullQueue, Worker, QueueEvents, type Job } from 'bullmq';
|
||||
import { Queue as BullQueue, QueueEvents, Worker, type Job } from 'bullmq';
|
||||
import { getLogger } from '@stock-bot/logger';
|
||||
import { handlerRegistry } from './handler-registry';
|
||||
import type { JobData, JobOptions, QueueStats, RedisConfig } from './types';
|
||||
|
|
@ -62,7 +62,7 @@ export class Queue {
|
|||
logger.trace('Queue created', {
|
||||
queueName,
|
||||
workers: config.workers || 0,
|
||||
concurrency: config.concurrency || 1
|
||||
concurrency: config.concurrency || 1,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -84,12 +84,10 @@ export class Queue {
|
|||
/**
|
||||
* Add multiple jobs to the queue in bulk
|
||||
*/
|
||||
async addBulk(
|
||||
jobs: Array<{ name: string; data: JobData; opts?: JobOptions }>
|
||||
): Promise<Job[]> {
|
||||
async addBulk(jobs: Array<{ name: string; data: JobData; opts?: JobOptions }>): Promise<Job[]> {
|
||||
logger.trace('Adding bulk jobs', {
|
||||
queueName: this.queueName,
|
||||
jobCount: jobs.length
|
||||
jobCount: jobs.length,
|
||||
});
|
||||
return await this.bullQueue.addBulk(jobs);
|
||||
}
|
||||
|
|
@ -118,7 +116,7 @@ export class Queue {
|
|||
jobName: name,
|
||||
cronPattern,
|
||||
repeatKey: scheduledOptions.repeat?.key,
|
||||
immediately: scheduledOptions.repeat?.immediately
|
||||
immediately: scheduledOptions.repeat?.immediately,
|
||||
});
|
||||
|
||||
return await this.bullQueue.add(name, data, scheduledOptions);
|
||||
|
|
@ -210,62 +208,30 @@ export class Queue {
|
|||
await this.bullQueue.waitUntilReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the queue (cleanup resources)
|
||||
*/
|
||||
/**
|
||||
* Close the queue (cleanup resources)
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
try {
|
||||
// Close workers first with timeout
|
||||
if (this.workers.length > 0) {
|
||||
const workerClosePromises = this.workers.map((worker) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
resolve();
|
||||
}, 50);
|
||||
// Close the queue itself
|
||||
await this.bullQueue.close();
|
||||
logger.info('Queue closed', { queueName: this.queueName });
|
||||
|
||||
worker.close().then(() => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
}).catch(() => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(workerClosePromises);
|
||||
this.workers = [];
|
||||
logger.debug('Workers closed', { queueName: this.queueName });
|
||||
}
|
||||
|
||||
// Close queue events with timeout
|
||||
// Close queue events
|
||||
if (this.queueEvents) {
|
||||
const eventsClosePromise = this.queueEvents.close();
|
||||
const eventsTimeoutPromise = new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Queue events close timeout')), 50)
|
||||
);
|
||||
|
||||
try {
|
||||
await Promise.race([eventsClosePromise, eventsTimeoutPromise]);
|
||||
} catch (error) {
|
||||
// Silently ignore timeout
|
||||
}
|
||||
await this.queueEvents.close();
|
||||
logger.debug('Queue events closed', { queueName: this.queueName });
|
||||
}
|
||||
|
||||
// Close the queue itself with timeout
|
||||
const queueClosePromise = this.bullQueue.close();
|
||||
const queueTimeoutPromise = new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('BullQueue close timeout')), 50)
|
||||
);
|
||||
|
||||
try {
|
||||
await Promise.race([queueClosePromise, queueTimeoutPromise]);
|
||||
} catch (error) {
|
||||
// Silently ignore timeout
|
||||
// Close workers first
|
||||
if (this.workers.length > 0) {
|
||||
await Promise.all(this.workers.map(worker => worker.close()));
|
||||
this.workers = [];
|
||||
logger.debug('Workers closed', { queueName: this.queueName });
|
||||
}
|
||||
|
||||
logger.info('Queue closed', { queueName: this.queueName });
|
||||
} catch (error) {
|
||||
logger.error('Error closing queue', { queueName: this.queueName, error });
|
||||
throw error;
|
||||
|
|
@ -279,19 +245,15 @@ export class Queue {
|
|||
const connection = getRedisConnection(this.redisConfig);
|
||||
|
||||
for (let i = 0; i < workerCount; i++) {
|
||||
const worker = new Worker(
|
||||
`{${this.queueName}}`,
|
||||
this.processJob.bind(this),
|
||||
{
|
||||
const worker = new Worker(`{${this.queueName}}`, this.processJob.bind(this), {
|
||||
connection,
|
||||
concurrency,
|
||||
maxStalledCount: 3,
|
||||
stalledInterval: 30000,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Setup worker event handlers
|
||||
worker.on('completed', (job) => {
|
||||
worker.on('completed', job => {
|
||||
logger.trace('Job completed', {
|
||||
queueName: this.queueName,
|
||||
jobId: job.id,
|
||||
|
|
@ -310,7 +272,7 @@ export class Queue {
|
|||
});
|
||||
});
|
||||
|
||||
worker.on('error', (error) => {
|
||||
worker.on('error', error => {
|
||||
logger.error('Worker error', {
|
||||
queueName: this.queueName,
|
||||
workerId: i,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue