Spaces:
Sleeping
Sleeping
| import IORedis from 'ioredis'; | |
| import { Queue, Job } from 'bullmq'; | |
| import dotenv from 'dotenv'; | |
| import logger from '../logger'; | |
| // import { workerRedis } from './workerRedis'; | |
| const envFile: string = process.env.NODE_ENV === 'production' ? '.env.production' : '.env.development'; | |
| dotenv.config({ path: envFile }); | |
| export class QueueInfra { | |
| public readonly queueRedis: IORedis; | |
| public readonly queue: Queue; | |
| private initialized = false; | |
| private queueName: string; | |
| constructor(queueRedis: IORedis, queueName: string) { | |
| this.queueRedis = queueRedis; | |
| this.queueName = queueName; | |
| // Create the queue for email related tasks | |
| this.queue = new Queue(queueName, { | |
| connection: this.queueRedis, // Pass connection object | |
| defaultJobOptions: { | |
| attempts: 3, | |
| backoff: { | |
| type: 'exponential', // Options: 'fixed' | 'exponential' | |
| delay: 3000 // Delay in ms | |
| }, | |
| removeOnComplete: { | |
| count: 1000, // Keep last 1000 completed jobs | |
| age: 24 * 3600 // OR keep for 24 hours (optional) | |
| }, | |
| removeOnFail: { | |
| count: 6000, // Keep last 5000 failed jobs | |
| age: 72 * 3600 // OR keep for 72 hours (optional) | |
| }, | |
| // Timeouts are handled at worker level | |
| } | |
| }); | |
| } | |
| async init() { | |
| if (this.initialized) return; | |
| this.initialized = true; | |
| // this.setupRedisEventListeners(); | |
| this.setupQueueEventListeners(); | |
| } | |
| /*private setupRedisEventListeners(): void { | |
| // Add event handlers | |
| this.emailQueueRedis.on('connect', () => { | |
| logger.info('β Redis connected (Email queue redis)'); | |
| }); | |
| this.emailQueueRedis.on('ready', () => { | |
| logger.info('β Redis ready (Email queue redis)'); | |
| }); | |
| this.emailQueueRedis.on('error', (err) => { | |
| logger.error({ err }, `β Redis connection error (Email queue redis): ${err.message}`); | |
| // Don't crash the app on Redis errors | |
| }); | |
| this.emailQueueRedis.on('close', () => { | |
| logger.info('β οΈ Redis connection closed (Email queue redis)'); | |
| }); | |
| this.emailQueueRedis.on('reconnecting', (delay: number) => { | |
| logger.info(`π Redis reconnecting in ${delay}ms (email queue redis)`); | |
| }); | |
| this.emailQueueRedis.on('end', () => { | |
| logger.info('π΄ Redis connection ended (email queue redis)'); | |
| }); | |
| } */ | |
| private setupQueueEventListeners(): void { | |
| // The callback receives a Job object directly, not an args object | |
| this.queue.on('waiting', (job: Job) => { | |
| logger.info(`π₯ Job ${job.id} is waiting: ${this.queueName}`); | |
| logger.info(`Job data: ${job.data}: ${this.queueName}`); | |
| }); | |
| this.queue.on('error', (err: Error) => { | |
| logger.error({ err }, `β Queue error: ${err.message}: ${this.queueName}`); | |
| }); | |
| this.queue.on('progress', (job: string, progress: any) => { | |
| logger.debug(`π Job ${job} progress: ${progress} : ${this.queueName}`); | |
| }); | |
| // Other available events: | |
| this.queue.on('paused', () => { | |
| logger.info(`βΈοΈ Queue paused: ${this.queueName}`); | |
| }); | |
| this.queue.on('resumed', () => { | |
| logger.info(`βΆοΈ Queue resumed: ${this.queueName}`); | |
| }); | |
| this.queue.on('cleaned', (jobs: string[], type: string) => { | |
| logger.debug(`π§Ή Cleaned ${jobs.length} ${type} jobs: ${this.queueName}`); | |
| }); | |
| this.queue.on('removed', (job: string) => { | |
| logger.debug(`ποΈ Job ${job} removed: ${this.queueName}`); | |
| }); | |
| // Redis-specific events | |
| this.queue.on('ioredis:close', () => { | |
| logger.info(`π Redis connection closed: ${this.queueName}`); | |
| }); | |
| } | |
| // Test connection on startup | |
| public async testQueueRedisConnection() { | |
| try { | |
| await this.queueRedis.ping(); | |
| logger.info(`β Redis connection test passed: ${this.queueName}`); | |
| } catch (err: any) { | |
| logger.error({err}, `β Redis connection test failed: ${err.message}: ${this.queueName}`); | |
| } | |
| } | |
| public async closeQueue() { | |
| logger.info(`β³ Closing ${this.queueName}...`); | |
| if (!this.queue) { | |
| logger.info(`β ${this.queueName} is not active.`); | |
| return; | |
| } | |
| try { | |
| await this.queue.close(); | |
| logger.info(`β ${this.queueName} closed`); | |
| } catch (err: any) { | |
| logger.error({ err }, `β Failed to close ${this.queueName}`); | |
| throw err; // propagate to main shutdown | |
| } | |
| } | |
| public async closeQueueRedis() { | |
| logger.info(`β³ Closing Redis connection: ${this.queueName}...`); | |
| if(!this.queueRedis) { | |
| logger.info(`β Redis connection: ${this.queueName} is not active`); | |
| return; | |
| } | |
| try { | |
| await this.queueRedis.quit(); // Graceful Redis shutdown | |
| logger.info(`β Redis connection closed: ${this.queueName} `); | |
| } catch (err: any) { | |
| logger.error({ err }, `β Failed to close Redis connection: ${this.queueName}`); | |
| throw err; // propagate to main shutdown | |
| } | |
| } | |
| }; | |
| /* | |
| Never do: | |
| try { | |
| everything | |
| } | |
| Always do: | |
| START log | |
| if exists | |
| try risky close | |
| SUCCESS log | |
| FAIL log | |
| This is architecturally correct | |
| */ |