File size: 4,365 Bytes
e8a6607
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { Queue, QueueEvents, Job } from 'bullmq';
import IORedis from 'ioredis';
import logger from '../logger';

// Gpt Chat service class
export class GptChatQueueService {

  private queue: Queue;
  private queueEvents: QueueEvents; // ONLY for listening to events
  private connection: IORedis;
  private queueEventsRegistry: Map<string, QueueEvents> = new Map();
  private queueName: string;

  constructor(gptChatQueueRedis: IORedis, queue: Queue, queueName: string) {
    this.queueName = queueName;
    this.queue = queue;
    this.connection = gptChatQueueRedis;
    this.queueEvents = new QueueEvents(queueName, {
      connection: this.connection
    });

    this.queueEventsRegistry.set(queueName, this.queueEvents); // Register queue events for later use, e.g., when shut down etc.

    this.setupEventListeners();
  }

  // Setup all event listeners
  private setupEventListeners(): void {

    // Job started processing
    this.queueEvents.on('active', async({ jobId }: { jobId: string }) => {
      logger.debug(`Job ${jobId} started processing: ${this.queueName}`);
    });

    // Job completed
    this.queueEvents.on('completed', async({ jobId }: { jobId: string }) => {
      logger.debug(`Job ${jobId} completed: ${this.queueName}`);
    });

    // Job failed
    this.queueEvents.on('failed', async({ jobId, failedReason }: { jobId: string, failedReason: string }) => {
      logger.error({failedReason}, `Job ${jobId} failed: ${this.queueName}`);
    });

    // Job progress
    // this.queueEvents.on('progress', ({ jobId, data }: { jobId: string, data: number }) => {
    //   logger.info(`Job ${jobId} progress: ${data}%`);
    //   this.updateProgress(jobId, data);
    // });

    // Stalled job
    // this.queueEvents.on('stalled', ({ jobId }: { jobId: string }) => {
    //   logger.warn(`Job ${jobId} stalled`);
    //   this.infoJobStatus(jobId, 'stalled');
    // });

    // Removed job
    this.queueEvents.on('removed', ({ jobId }: { jobId: string }) => {
      logger.debug(`Job ${jobId} removed: ${this.queueName}`);
    });

    // Error in queue
    this.queueEvents.on('error', (error: Error) => {
      logger.error({err: error}, `QueueEvents error: ${this.queueName}`);
    });
  }

  // Queue gpt chat prompt
  async queueGptChatPrompt(
    prompt: string,
    msgSession: string,
    userId: string, 
    priority = 1
  ) {
    return this.queue.add('gpt-chat-process', {
      prompt,
      msgSession,
      userId,
      type: 'gpt-chat-process',
      queuedAt: new Date().toISOString()
    }, {
      priority,
      delay: 100
    });
  }

  // Get queue metrics
  async getQueueMetrics() {
    const [waiting, active, completed, failed, delayed] = await Promise.all([
      this.queue.getWaitingCount(),
      this.queue.getActiveCount(),
      this.queue.getCompletedCount(),
      this.queue.getFailedCount(),
      this.queue.getDelayedCount()
    ]);

    return {
      waiting,
      active,
      completed,
      failed,
      delayed,
      total: waiting + active + completed + failed + delayed
    };
  }

  // Clean old jobs
  async cleanOldJobs(olderThanHours = 24) {
    const timestamp = Date.now() - (olderThanHours * 60 * 60 * 1000);
    
    // Clean completed jobs
    await this.queue.clean(timestamp, 1000, 'completed');
    
    // Clean failed jobs (keep for longer)
    const failedTimestamp = Date.now() - (olderThanHours * 3 * 60 * 60 * 1000);
    await this.queue.clean(failedTimestamp, 1000, 'failed');
  }


  async closeQueueEvents(): Promise<void> {
    
    if (!this.queueEventsRegistry || this.queueEventsRegistry.size === 0) {
      logger.info(`✅ No active QueueEvents: ${this.queueName}`);
      return;
    }

    logger.info(`Closing ${this.queueEventsRegistry.size} ${this.queueEventsRegistry.size > 1 ? 'QueueEvents...' : 'QueueEvent...'}`);

    await Promise.allSettled(
      Array.from(this.queueEventsRegistry.entries()).map(async ([name, queue]) => {
        try {
          // logger.info(`Closing QueueEvents: ${name}`);
          await queue.close();
          logger.info(`✅ Closed QueueEvents: ${name}`);
        } catch (err: any) {
            logger.error({ err }, `❌ Failed closing QueueEvents: ${name}`);
          throw err;
        }
      })
    );

    this.queueEventsRegistry.clear();
    logger.info('✅ All QueueEvents closed');
  }

}