@onlineapps/mq-client-core 1.0.33 → 1.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/transports/rabbitmqClient.js +265 -142
package/package.json
CHANGED
|
@@ -35,6 +35,13 @@ class RabbitMQClient extends EventEmitter {
|
|
|
35
35
|
this._channel = null; // ConfirmChannel for publish operations
|
|
36
36
|
this._queueChannel = null; // Regular channel for queue operations (assertQueue, checkQueue)
|
|
37
37
|
this._consumerChannel = null; // Dedicated channel for consume operations
|
|
38
|
+
|
|
39
|
+
// Track active consumers for re-registration after channel recreation
|
|
40
|
+
this._activeConsumers = new Map(); // queue -> { handler, options, consumerTag }
|
|
41
|
+
|
|
42
|
+
// Auto-reconnect flags
|
|
43
|
+
this._reconnecting = false;
|
|
44
|
+
this._reconnectAttempts = 0;
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
/**
|
|
@@ -95,14 +102,18 @@ class RabbitMQClient extends EventEmitter {
|
|
|
95
102
|
// Use ConfirmChannel to enable publisher confirms for publish operations
|
|
96
103
|
this._channel = await this._connection.createConfirmChannel();
|
|
97
104
|
// Enable publisher confirms explicitly
|
|
98
|
-
this._channel.on('error', (err) => {
|
|
105
|
+
this._channel.on('error', async (err) => {
|
|
99
106
|
console.error('[RabbitMQClient] [mq-client-core] Publisher channel error:', err.message);
|
|
107
|
+
// Auto-recreate channel if connection is still alive
|
|
108
|
+
await this._ensurePublisherChannel();
|
|
100
109
|
this.emit('error', err);
|
|
101
110
|
});
|
|
102
|
-
this._channel.on('close', () => {
|
|
103
|
-
console.warn('[RabbitMQClient] [mq-client-core] Publisher channel closed');
|
|
104
|
-
//
|
|
105
|
-
this.
|
|
111
|
+
this._channel.on('close', async () => {
|
|
112
|
+
console.warn('[RabbitMQClient] [mq-client-core] Publisher channel closed - will auto-recreate on next publish');
|
|
113
|
+
// Mark channel as null - will be recreated on next publish
|
|
114
|
+
this._channel = null;
|
|
115
|
+
// Try to recreate immediately if connection is alive
|
|
116
|
+
await this._ensurePublisherChannel();
|
|
106
117
|
});
|
|
107
118
|
// Set up publisher confirms callback
|
|
108
119
|
this._channel.on('drain', () => {
|
|
@@ -112,24 +123,35 @@ class RabbitMQClient extends EventEmitter {
|
|
|
112
123
|
// Create a separate regular channel for queue operations (assertQueue, checkQueue)
|
|
113
124
|
// This avoids RPC reply queue issues with ConfirmChannel.assertQueue()
|
|
114
125
|
this._queueChannel = await this._connection.createChannel();
|
|
115
|
-
this._queueChannel.on('error', (err) => {
|
|
126
|
+
this._queueChannel.on('error', async (err) => {
|
|
116
127
|
// Log but don't emit - queue channel errors are less critical
|
|
117
128
|
console.warn('[RabbitMQClient] Queue channel error:', err.message);
|
|
129
|
+
// Auto-recreate channel if connection is still alive
|
|
130
|
+
await this._ensureQueueChannel();
|
|
118
131
|
});
|
|
119
|
-
this._queueChannel.on('close', () => {
|
|
120
|
-
console.warn('[RabbitMQClient] Queue channel closed');
|
|
132
|
+
this._queueChannel.on('close', async () => {
|
|
133
|
+
console.warn('[RabbitMQClient] Queue channel closed - will auto-recreate on next operation');
|
|
134
|
+
// Mark channel as null - will be recreated on next queue operation
|
|
135
|
+
this._queueChannel = null;
|
|
136
|
+
// Try to recreate immediately if connection is alive
|
|
137
|
+
await this._ensureQueueChannel();
|
|
121
138
|
});
|
|
122
139
|
|
|
123
140
|
// Create a dedicated channel for consume operations
|
|
124
141
|
// This prevents channel conflicts between publish (ConfirmChannel) and consume operations
|
|
125
142
|
this._consumerChannel = await this._connection.createChannel();
|
|
126
|
-
this._consumerChannel.on('error', (err) => {
|
|
143
|
+
this._consumerChannel.on('error', async (err) => {
|
|
127
144
|
console.warn('[RabbitMQClient] Consumer channel error:', err.message);
|
|
145
|
+
// Auto-recreate channel and re-register consumers
|
|
146
|
+
await this._ensureConsumerChannel();
|
|
128
147
|
this.emit('error', err);
|
|
129
148
|
});
|
|
130
|
-
this._consumerChannel.on('close', () => {
|
|
131
|
-
console.warn('[RabbitMQClient] Consumer channel closed');
|
|
132
|
-
|
|
149
|
+
this._consumerChannel.on('close', async () => {
|
|
150
|
+
console.warn('[RabbitMQClient] Consumer channel closed - will auto-recreate and re-register consumers');
|
|
151
|
+
// Mark channel as null - will be recreated and consumers re-registered
|
|
152
|
+
this._consumerChannel = null;
|
|
153
|
+
// Try to recreate and re-register consumers immediately if connection is alive
|
|
154
|
+
await this._ensureConsumerChannel();
|
|
133
155
|
});
|
|
134
156
|
} catch (err) {
|
|
135
157
|
// Cleanup partially created resources
|
|
@@ -145,11 +167,192 @@ class RabbitMQClient extends EventEmitter {
|
|
|
145
167
|
}
|
|
146
168
|
}
|
|
147
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Ensure publisher channel exists and is open
|
|
172
|
+
* Auto-recreates if closed
|
|
173
|
+
* @private
|
|
174
|
+
* @returns {Promise<void>}
|
|
175
|
+
*/
|
|
176
|
+
async _ensurePublisherChannel() {
|
|
177
|
+
if (this._channel && !this._channel.closed) {
|
|
178
|
+
return; // Channel is good
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!this._connection || this._connection.closed) {
|
|
182
|
+
throw new Error('Cannot recreate publisher channel: connection is closed');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
// Close old channel if exists
|
|
187
|
+
if (this._channel) {
|
|
188
|
+
try {
|
|
189
|
+
await this._channel.close();
|
|
190
|
+
} catch (_) {
|
|
191
|
+
// Ignore errors when closing already-closed channel
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Create new ConfirmChannel
|
|
196
|
+
this._channel = await this._connection.createConfirmChannel();
|
|
197
|
+
this._channel.on('error', async (err) => {
|
|
198
|
+
console.error('[RabbitMQClient] [mq-client-core] Publisher channel error:', err.message);
|
|
199
|
+
await this._ensurePublisherChannel();
|
|
200
|
+
this.emit('error', err);
|
|
201
|
+
});
|
|
202
|
+
this._channel.on('close', async () => {
|
|
203
|
+
console.warn('[RabbitMQClient] [mq-client-core] Publisher channel closed - will auto-recreate on next publish');
|
|
204
|
+
this._channel = null;
|
|
205
|
+
await this._ensurePublisherChannel();
|
|
206
|
+
});
|
|
207
|
+
this._channel.on('drain', () => {
|
|
208
|
+
console.log('[RabbitMQClient] [mq-client-core] Publisher channel drained');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
console.log('[RabbitMQClient] [mq-client-core] ✓ Publisher channel recreated');
|
|
212
|
+
} catch (err) {
|
|
213
|
+
this._channel = null;
|
|
214
|
+
throw new Error(`Failed to recreate publisher channel: ${err.message}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Ensure queue channel exists and is open
|
|
220
|
+
* Auto-recreates if closed
|
|
221
|
+
* @private
|
|
222
|
+
* @returns {Promise<void>}
|
|
223
|
+
*/
|
|
224
|
+
async _ensureQueueChannel() {
|
|
225
|
+
if (this._queueChannel && !this._queueChannel.closed) {
|
|
226
|
+
return; // Channel is good
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!this._connection || this._connection.closed) {
|
|
230
|
+
throw new Error('Cannot recreate queue channel: connection is closed');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
// Close old channel if exists
|
|
235
|
+
if (this._queueChannel) {
|
|
236
|
+
try {
|
|
237
|
+
await this._queueChannel.close();
|
|
238
|
+
} catch (_) {
|
|
239
|
+
// Ignore errors when closing already-closed channel
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Create new regular channel
|
|
244
|
+
this._queueChannel = await this._connection.createChannel();
|
|
245
|
+
this._queueChannel.on('error', async (err) => {
|
|
246
|
+
console.warn('[RabbitMQClient] Queue channel error:', err.message);
|
|
247
|
+
await this._ensureQueueChannel();
|
|
248
|
+
});
|
|
249
|
+
this._queueChannel.on('close', async () => {
|
|
250
|
+
console.warn('[RabbitMQClient] Queue channel closed - will auto-recreate on next operation');
|
|
251
|
+
this._queueChannel = null;
|
|
252
|
+
await this._ensureQueueChannel();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
console.log('[RabbitMQClient] [mq-client-core] ✓ Queue channel recreated');
|
|
256
|
+
} catch (err) {
|
|
257
|
+
this._queueChannel = null;
|
|
258
|
+
throw new Error(`Failed to recreate queue channel: ${err.message}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Ensure consumer channel exists and is open
|
|
264
|
+
* Auto-recreates and re-registers all consumers if closed
|
|
265
|
+
* @private
|
|
266
|
+
* @returns {Promise<void>}
|
|
267
|
+
*/
|
|
268
|
+
async _ensureConsumerChannel() {
|
|
269
|
+
if (this._consumerChannel && !this._consumerChannel.closed) {
|
|
270
|
+
return; // Channel is good
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!this._connection || this._connection.closed) {
|
|
274
|
+
throw new Error('Cannot recreate consumer channel: connection is closed');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
// Close old channel if exists
|
|
279
|
+
if (this._consumerChannel) {
|
|
280
|
+
try {
|
|
281
|
+
await this._consumerChannel.close();
|
|
282
|
+
} catch (_) {
|
|
283
|
+
// Ignore errors when closing already-closed channel
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Create new regular channel
|
|
288
|
+
this._consumerChannel = await this._connection.createChannel();
|
|
289
|
+
this._consumerChannel.on('error', async (err) => {
|
|
290
|
+
console.warn('[RabbitMQClient] Consumer channel error:', err.message);
|
|
291
|
+
await this._ensureConsumerChannel();
|
|
292
|
+
this.emit('error', err);
|
|
293
|
+
});
|
|
294
|
+
this._consumerChannel.on('close', async () => {
|
|
295
|
+
console.warn('[RabbitMQClient] Consumer channel closed - will auto-recreate and re-register consumers');
|
|
296
|
+
this._consumerChannel = null;
|
|
297
|
+
await this._ensureConsumerChannel();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Re-register all active consumers
|
|
301
|
+
if (this._activeConsumers.size > 0) {
|
|
302
|
+
console.log(`[RabbitMQClient] [mq-client-core] Re-registering ${this._activeConsumers.size} consumers...`);
|
|
303
|
+
for (const [queue, consumerInfo] of this._activeConsumers.entries()) {
|
|
304
|
+
try {
|
|
305
|
+
// Re-assert queue with correct options before consuming (may have been deleted)
|
|
306
|
+
if (consumerInfo.options.queueOptions) {
|
|
307
|
+
try {
|
|
308
|
+
await this._ensureQueueChannel();
|
|
309
|
+
await this._queueChannel.assertQueue(queue, consumerInfo.options.queueOptions);
|
|
310
|
+
} catch (assertErr) {
|
|
311
|
+
if (assertErr.code === 404) {
|
|
312
|
+
console.warn(`[RabbitMQClient] [mq-client-core] Queue ${queue} does not exist, skipping consumer re-registration`);
|
|
313
|
+
this._activeConsumers.delete(queue);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
// 406 error means queue exists with different args - skip this consumer
|
|
317
|
+
if (assertErr.code === 406) {
|
|
318
|
+
console.warn(`[RabbitMQClient] [mq-client-core] Queue ${queue} exists with different args, skipping consumer re-registration`);
|
|
319
|
+
this._activeConsumers.delete(queue);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
throw assertErr;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const consumeResult = await this._consumerChannel.consume(
|
|
327
|
+
queue,
|
|
328
|
+
consumerInfo.handler,
|
|
329
|
+
{ noAck: consumerInfo.options.noAck }
|
|
330
|
+
);
|
|
331
|
+
consumerInfo.consumerTag = consumeResult.consumerTag;
|
|
332
|
+
console.log(`[RabbitMQClient] [mq-client-core] ✓ Re-registered consumer for queue: ${queue} (consumerTag: ${consumeResult.consumerTag})`);
|
|
333
|
+
} catch (err) {
|
|
334
|
+
console.error(`[RabbitMQClient] [mq-client-core] Failed to re-register consumer for queue ${queue}:`, err.message);
|
|
335
|
+
// Remove failed consumer from tracking
|
|
336
|
+
this._activeConsumers.delete(queue);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
console.log('[RabbitMQClient] [mq-client-core] ✓ Consumer channel recreated');
|
|
342
|
+
} catch (err) {
|
|
343
|
+
this._consumerChannel = null;
|
|
344
|
+
throw new Error(`Failed to recreate consumer channel: ${err.message}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
148
348
|
/**
|
|
149
349
|
* Disconnects: closes channel and connection.
|
|
150
350
|
* @returns {Promise<void>}
|
|
151
351
|
*/
|
|
152
352
|
async disconnect() {
|
|
353
|
+
// Clear active consumers tracking
|
|
354
|
+
this._activeConsumers.clear();
|
|
355
|
+
|
|
153
356
|
try {
|
|
154
357
|
if (this._consumerChannel) {
|
|
155
358
|
await this._consumerChannel.close();
|
|
@@ -194,15 +397,11 @@ class RabbitMQClient extends EventEmitter {
|
|
|
194
397
|
* @throws {Error} If publish fails or channel is not available.
|
|
195
398
|
*/
|
|
196
399
|
async publish(queue, buffer, options = {}) {
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
const errorMsg = `Cannot publish to queue "${queue}": channel is not initialized or closed (channel: ${!!this._channel}, closed: ${this._channel?.closed})`;
|
|
200
|
-
console.error('[RabbitMQClient] [mq-client-core] [PUBLISH]', errorMsg);
|
|
201
|
-
throw new Error(errorMsg);
|
|
202
|
-
}
|
|
400
|
+
// Ensure publisher channel exists and is open (auto-recreates if closed)
|
|
401
|
+
await this._ensurePublisherChannel();
|
|
203
402
|
|
|
204
403
|
// Log publish attempt for debugging
|
|
205
|
-
console.log(`[RabbitMQClient] [mq-client-core] [PUBLISH] Attempting to publish to queue "${queue}"
|
|
404
|
+
console.log(`[RabbitMQClient] [mq-client-core] [PUBLISH] Attempting to publish to queue "${queue}"`);
|
|
206
405
|
|
|
207
406
|
const exchange = this._config.exchange || '';
|
|
208
407
|
const routingKey = options.routingKey || queue;
|
|
@@ -217,21 +416,8 @@ class RabbitMQClient extends EventEmitter {
|
|
|
217
416
|
// If queue doesn't exist (404), we should NOT auto-create it - infrastructure queues must be created explicitly
|
|
218
417
|
// This prevents creating queues with wrong arguments (no TTL) which causes 406 errors later
|
|
219
418
|
try {
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
// Recreate queueChannel if closed
|
|
223
|
-
if (this._connection && !this._connection.closed) {
|
|
224
|
-
this._queueChannel = await this._connection.createChannel();
|
|
225
|
-
this._queueChannel.on('error', (err) => {
|
|
226
|
-
console.warn('[RabbitMQClient] Queue channel error:', err.message);
|
|
227
|
-
});
|
|
228
|
-
this._queueChannel.on('close', () => {
|
|
229
|
-
console.warn('[RabbitMQClient] Queue channel closed');
|
|
230
|
-
});
|
|
231
|
-
} else {
|
|
232
|
-
throw new Error('Cannot publish: connection is closed');
|
|
233
|
-
}
|
|
234
|
-
}
|
|
419
|
+
// Ensure queue channel exists and is open (auto-recreates if closed)
|
|
420
|
+
await this._ensureQueueChannel();
|
|
235
421
|
await this._queueChannel.checkQueue(queue);
|
|
236
422
|
// Queue exists - proceed to publish
|
|
237
423
|
} catch (checkErr) {
|
|
@@ -266,23 +452,13 @@ class RabbitMQClient extends EventEmitter {
|
|
|
266
452
|
}
|
|
267
453
|
}
|
|
268
454
|
// Publish to queue using ConfirmChannel (for publisher confirms)
|
|
269
|
-
//
|
|
270
|
-
if (this._channel.closed) {
|
|
271
|
-
throw new Error(`Cannot publish to queue "${queue}": channel closed during operation (after queue check)`);
|
|
272
|
-
}
|
|
273
|
-
|
|
455
|
+
// Channel is guaranteed to be open (ensured above)
|
|
274
456
|
console.log(`[RabbitMQClient] [mq-client-core] [PUBLISH] Sending message to queue "${queue}" (size: ${buffer.length} bytes)`);
|
|
275
457
|
|
|
276
|
-
// Use callback-based confirmation
|
|
458
|
+
// Use callback-based confirmation - kanály jsou spolehlivé, takže callback vždy dorazí
|
|
277
459
|
const confirmPromise = new Promise((resolve, reject) => {
|
|
278
|
-
// Set up timeout
|
|
279
|
-
const timeout = setTimeout(() => {
|
|
280
|
-
reject(new Error(`Publisher confirm timeout after 5 seconds for queue "${queue}"`));
|
|
281
|
-
}, 5000);
|
|
282
|
-
|
|
283
460
|
// Send message with callback
|
|
284
|
-
|
|
285
|
-
clearTimeout(timeout);
|
|
461
|
+
this._channel.sendToQueue(queue, buffer, { persistent, headers }, (err, ok) => {
|
|
286
462
|
if (err) {
|
|
287
463
|
console.error(`[RabbitMQClient] [mq-client-core] [PUBLISH] Send callback error for queue "${queue}":`, err.message);
|
|
288
464
|
reject(err);
|
|
@@ -291,42 +467,17 @@ class RabbitMQClient extends EventEmitter {
|
|
|
291
467
|
resolve();
|
|
292
468
|
}
|
|
293
469
|
});
|
|
294
|
-
|
|
295
|
-
// If sendToQueue returns false, channel is full - wait for drain
|
|
296
|
-
if (!sent) {
|
|
297
|
-
console.log(`[RabbitMQClient] [mq-client-core] [PUBLISH] Channel full, waiting for drain for queue "${queue}"`);
|
|
298
|
-
this._channel.once('drain', () => {
|
|
299
|
-
console.log(`[RabbitMQClient] [mq-client-core] [PUBLISH] Channel drained for queue "${queue}"`);
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
470
|
});
|
|
303
471
|
|
|
304
|
-
|
|
305
|
-
await confirmPromise;
|
|
306
|
-
} catch (confirmErr) {
|
|
307
|
-
// Check if channel was closed during confirmation
|
|
308
|
-
if (this._channel.closed) {
|
|
309
|
-
const errorMsg = `Publisher confirm failed for queue "${queue}": channel closed during confirmation. Original error: ${confirmErr.message}`;
|
|
310
|
-
console.error('[RabbitMQClient] [mq-client-core] [PUBLISH]', errorMsg);
|
|
311
|
-
throw new Error(errorMsg);
|
|
312
|
-
}
|
|
313
|
-
throw confirmErr;
|
|
314
|
-
}
|
|
472
|
+
await confirmPromise;
|
|
315
473
|
} else {
|
|
316
474
|
// If exchange is specified, assert exchange and publish to it
|
|
317
|
-
|
|
318
|
-
throw new Error('Cannot publish: channel closed during operation');
|
|
319
|
-
}
|
|
475
|
+
// Channel is guaranteed to be open (ensured above)
|
|
320
476
|
await this._channel.assertExchange(exchange, 'direct', { durable: this._config.durable });
|
|
321
477
|
|
|
322
|
-
// Use callback-based confirmation
|
|
478
|
+
// Use callback-based confirmation - kanály jsou spolehlivé
|
|
323
479
|
const confirmPromise = new Promise((resolve, reject) => {
|
|
324
|
-
|
|
325
|
-
reject(new Error(`Publisher confirm timeout after 5 seconds for exchange "${exchange}"`));
|
|
326
|
-
}, 5000);
|
|
327
|
-
|
|
328
|
-
const sent = this._channel.publish(exchange, routingKey, buffer, { persistent, headers }, (err, ok) => {
|
|
329
|
-
clearTimeout(timeout);
|
|
480
|
+
this._channel.publish(exchange, routingKey, buffer, { persistent, headers }, (err, ok) => {
|
|
330
481
|
if (err) {
|
|
331
482
|
console.error(`[RabbitMQClient] [mq-client-core] [PUBLISH] Exchange publish callback error:`, err.message);
|
|
332
483
|
reject(err);
|
|
@@ -335,28 +486,22 @@ class RabbitMQClient extends EventEmitter {
|
|
|
335
486
|
resolve();
|
|
336
487
|
}
|
|
337
488
|
});
|
|
338
|
-
|
|
339
|
-
if (!sent) {
|
|
340
|
-
this._channel.once('drain', () => {
|
|
341
|
-
console.log(`[RabbitMQClient] [mq-client-core] [PUBLISH] Channel drained for exchange "${exchange}"`);
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
489
|
});
|
|
345
490
|
|
|
346
|
-
|
|
347
|
-
await confirmPromise;
|
|
348
|
-
} catch (confirmErr) {
|
|
349
|
-
if (this._channel.closed) {
|
|
350
|
-
throw new Error(`Publisher confirm failed for exchange "${exchange}": channel closed during confirmation. Original error: ${confirmErr.message}`);
|
|
351
|
-
}
|
|
352
|
-
throw confirmErr;
|
|
353
|
-
}
|
|
491
|
+
await confirmPromise;
|
|
354
492
|
}
|
|
355
493
|
} catch (err) {
|
|
356
|
-
// If channel was closed,
|
|
494
|
+
// If channel was closed, try to recreate and retry once
|
|
357
495
|
if (err.message && (err.message.includes('Channel closed') || err.message.includes('channel is closed') || this._channel?.closed)) {
|
|
358
|
-
console.warn('[RabbitMQClient] [mq-client-core] [PUBLISH] Channel closed during publish,
|
|
359
|
-
|
|
496
|
+
console.warn('[RabbitMQClient] [mq-client-core] [PUBLISH] Channel closed during publish, recreating and retrying...');
|
|
497
|
+
try {
|
|
498
|
+
await this._ensurePublisherChannel();
|
|
499
|
+
// Retry publish once
|
|
500
|
+
return await this.publish(queue, buffer, options);
|
|
501
|
+
} catch (retryErr) {
|
|
502
|
+
console.error('[RabbitMQClient] [mq-client-core] [PUBLISH] Retry failed:', retryErr.message);
|
|
503
|
+
throw retryErr;
|
|
504
|
+
}
|
|
360
505
|
}
|
|
361
506
|
this.emit('error', err);
|
|
362
507
|
throw err;
|
|
@@ -372,41 +517,8 @@ class RabbitMQClient extends EventEmitter {
|
|
|
372
517
|
* @throws {Error} If consume setup fails or channel is not available.
|
|
373
518
|
*/
|
|
374
519
|
async consume(queue, onMessage, options = {}) {
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
if (!this._consumerChannel) {
|
|
378
|
-
// Recreate consumer channel if closed
|
|
379
|
-
if (this._connection && !this._connection.closed) {
|
|
380
|
-
this._consumerChannel = await this._connection.createChannel();
|
|
381
|
-
this._consumerChannel.on('error', (err) => {
|
|
382
|
-
console.warn('[RabbitMQClient] Consumer channel error:', err.message);
|
|
383
|
-
this.emit('error', err);
|
|
384
|
-
});
|
|
385
|
-
this._consumerChannel.on('close', () => {
|
|
386
|
-
console.warn('[RabbitMQClient] Consumer channel closed');
|
|
387
|
-
this.emit('error', new Error('RabbitMQ consumer channel closed unexpectedly'));
|
|
388
|
-
});
|
|
389
|
-
} else {
|
|
390
|
-
throw new Error('Cannot consume: consumer channel is not initialized and connection is closed');
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (this._consumerChannel.closed) {
|
|
395
|
-
// Recreate consumer channel if closed
|
|
396
|
-
if (this._connection && !this._connection.closed) {
|
|
397
|
-
this._consumerChannel = await this._connection.createChannel();
|
|
398
|
-
this._consumerChannel.on('error', (err) => {
|
|
399
|
-
console.warn('[RabbitMQClient] Consumer channel error:', err.message);
|
|
400
|
-
this.emit('error', err);
|
|
401
|
-
});
|
|
402
|
-
this._consumerChannel.on('close', () => {
|
|
403
|
-
console.warn('[RabbitMQClient] Consumer channel closed');
|
|
404
|
-
this.emit('error', new Error('RabbitMQ consumer channel closed unexpectedly'));
|
|
405
|
-
});
|
|
406
|
-
} else {
|
|
407
|
-
throw new Error('Cannot consume: consumer channel is closed and connection is closed');
|
|
408
|
-
}
|
|
409
|
-
}
|
|
520
|
+
// Ensure consumer channel exists and is open (auto-recreates if closed and re-registers consumers)
|
|
521
|
+
await this._ensureConsumerChannel();
|
|
410
522
|
|
|
411
523
|
const durable = options.durable !== undefined ? options.durable : this._config.durable;
|
|
412
524
|
const prefetch = options.prefetch !== undefined ? options.prefetch : this._config.prefetch;
|
|
@@ -516,25 +628,36 @@ class RabbitMQClient extends EventEmitter {
|
|
|
516
628
|
|
|
517
629
|
// Use dedicated consumer channel for consume operations
|
|
518
630
|
// This prevents conflicts with ConfirmChannel used for publish operations
|
|
631
|
+
const messageHandler = async (msg) => {
|
|
632
|
+
if (msg === null) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
try {
|
|
636
|
+
await onMessage(msg);
|
|
637
|
+
if (!noAck) {
|
|
638
|
+
this._consumerChannel.ack(msg);
|
|
639
|
+
}
|
|
640
|
+
} catch (handlerErr) {
|
|
641
|
+
// Negative acknowledge and requeue by default
|
|
642
|
+
this._consumerChannel.nack(msg, false, true);
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
519
646
|
const consumeResult = await this._consumerChannel.consume(
|
|
520
647
|
queue,
|
|
521
|
-
|
|
522
|
-
if (msg === null) {
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
try {
|
|
526
|
-
await onMessage(msg);
|
|
527
|
-
if (!noAck) {
|
|
528
|
-
this._consumerChannel.ack(msg);
|
|
529
|
-
}
|
|
530
|
-
} catch (handlerErr) {
|
|
531
|
-
// Negative acknowledge and requeue by default
|
|
532
|
-
this._consumerChannel.nack(msg, false, true);
|
|
533
|
-
}
|
|
534
|
-
},
|
|
648
|
+
messageHandler,
|
|
535
649
|
{ noAck }
|
|
536
650
|
);
|
|
537
651
|
|
|
652
|
+
// Track consumer for auto re-registration if channel closes
|
|
653
|
+
this._activeConsumers.set(queue, {
|
|
654
|
+
handler: messageHandler,
|
|
655
|
+
options: { noAck, prefetch, durable, queueOptions },
|
|
656
|
+
consumerTag: consumeResult.consumerTag
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
console.log(`[RabbitMQClient] [mq-client-core] [CONSUMER] ✓ Consumer registered for queue "${queue}" (consumerTag: ${consumeResult.consumerTag})`);
|
|
660
|
+
|
|
538
661
|
// Return consumer tag for cancellation
|
|
539
662
|
return consumeResult.consumerTag;
|
|
540
663
|
} catch (err) {
|