@onlineapps/conn-infra-mq 1.1.35 → 1.1.37
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/layers/QueueManager.js +118 -36
- package/src/transports/rabbitmqClient.js +13 -0
package/package.json
CHANGED
|
@@ -249,29 +249,64 @@ class QueueManager {
|
|
|
249
249
|
for (const queueInfo of queueNames) {
|
|
250
250
|
// CRITICAL: Check if channel is closed before each queue operation (may have been closed by previous 406)
|
|
251
251
|
// Channel closes SYNCHRONOUSLY on 406, so we must check before each assertQueue
|
|
252
|
-
//
|
|
252
|
+
// We check channel state directly - if it's null or closed, recreate it
|
|
253
253
|
if (!channel) {
|
|
254
254
|
console.warn(`[QueueManager] DEBUG: Channel is null before ${queueInfo.name}, recreating...`);
|
|
255
255
|
channel = await transport._connection.createChannel();
|
|
256
|
+
channel._createdAt = new Date().toISOString();
|
|
257
|
+
channel._closeReason = null;
|
|
258
|
+
channel._lastOperation = `Recreated (was null) before ${queueInfo.name}`;
|
|
259
|
+
|
|
260
|
+
channel.on('error', (err) => {
|
|
261
|
+
console.error('[RabbitMQClient] Recreated queue channel error:', err.message);
|
|
262
|
+
channel._closeReason = `Error: ${err.message} (code: ${err.code})`;
|
|
263
|
+
});
|
|
264
|
+
channel.on('close', () => {
|
|
265
|
+
console.error('[RabbitMQClient] Recreated queue channel closed');
|
|
266
|
+
console.error('[RabbitMQClient] Close reason:', channel._closeReason || 'Unknown');
|
|
267
|
+
});
|
|
268
|
+
|
|
256
269
|
transport.queueChannel = channel;
|
|
257
270
|
console.log(`[QueueManager] DEBUG: Channel recreated (was null) before ${queueInfo.name}`);
|
|
258
271
|
} else {
|
|
259
|
-
//
|
|
260
|
-
//
|
|
272
|
+
// Check if channel is closed - amqplib may not set closed property immediately
|
|
273
|
+
// We check by trying to access channel properties - if it throws, channel is closed
|
|
261
274
|
let channelUsable = false;
|
|
262
275
|
try {
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
276
|
+
// Check if channel has closed property set to true
|
|
277
|
+
if (channel.closed === true) {
|
|
278
|
+
console.warn(`[QueueManager] DEBUG: Channel.closed === true before ${queueInfo.name}, recreating...`);
|
|
279
|
+
throw new Error('Channel closed property is true');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Channel exists and closed is not true - assume it's usable
|
|
283
|
+
// We don't test with checkQueue() because that can also close channel on 404
|
|
266
284
|
channelUsable = true;
|
|
267
|
-
console.log(`[QueueManager] DEBUG: Channel
|
|
285
|
+
console.log(`[QueueManager] DEBUG: Channel appears usable before ${queueInfo.name} (closed=${channel.closed})`);
|
|
268
286
|
} catch (testErr) {
|
|
269
287
|
// Channel is closed or unusable - recreate it
|
|
270
|
-
console.warn(`[QueueManager] DEBUG: Channel
|
|
288
|
+
console.warn(`[QueueManager] DEBUG: Channel check failed before ${queueInfo.name} (${testErr.message}), recreating...`);
|
|
289
|
+
console.warn(`[QueueManager] DEBUG: Channel close reason: ${channel._closeReason || 'N/A'}`);
|
|
290
|
+
console.warn(`[QueueManager] DEBUG: Channel last operation: ${channel._lastOperation || 'N/A'}`);
|
|
291
|
+
|
|
271
292
|
try {
|
|
272
|
-
|
|
293
|
+
const newChannel = await transport._connection.createChannel();
|
|
294
|
+
newChannel._createdAt = new Date().toISOString();
|
|
295
|
+
newChannel._closeReason = null;
|
|
296
|
+
newChannel._lastOperation = `Recreated (check failed) before ${queueInfo.name}`;
|
|
297
|
+
|
|
298
|
+
newChannel.on('error', (err) => {
|
|
299
|
+
console.error('[RabbitMQClient] Recreated queue channel error:', err.message);
|
|
300
|
+
newChannel._closeReason = `Error: ${err.message} (code: ${err.code})`;
|
|
301
|
+
});
|
|
302
|
+
newChannel.on('close', () => {
|
|
303
|
+
console.error('[RabbitMQClient] Recreated queue channel closed');
|
|
304
|
+
console.error('[RabbitMQClient] Close reason:', newChannel._closeReason || 'Unknown');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
channel = newChannel;
|
|
273
308
|
transport.queueChannel = channel;
|
|
274
|
-
console.log(`[QueueManager] DEBUG: Channel recreated (
|
|
309
|
+
console.log(`[QueueManager] DEBUG: Channel recreated (check failed) before ${queueInfo.name}`);
|
|
275
310
|
channelUsable = true;
|
|
276
311
|
} catch (recreateErr) {
|
|
277
312
|
throw new Error(`Failed to recreate channel before ${queueInfo.name}: ${recreateErr.message}`);
|
|
@@ -332,61 +367,108 @@ class QueueManager {
|
|
|
332
367
|
|
|
333
368
|
// CRITICAL: Use assertQueue directly - if queue exists with different args (406), that's OK
|
|
334
369
|
// BUT: 406 PRECONDITION-FAILED closes the channel in amqplib!
|
|
335
|
-
// Solution:
|
|
370
|
+
// Solution: Track channel state, catch 406, recreate channel immediately, and continue
|
|
336
371
|
let queueCreated = false;
|
|
372
|
+
|
|
373
|
+
// CRITICAL: Track operation on channel for debugging
|
|
374
|
+
if (channel && channel._lastOperation !== undefined) {
|
|
375
|
+
channel._lastOperation = `About to assertQueue ${queueName}`;
|
|
376
|
+
}
|
|
377
|
+
|
|
337
378
|
try {
|
|
338
|
-
console.log(`[QueueManager] DEBUG: About to assertQueue ${queueName}
|
|
379
|
+
console.log(`[QueueManager] DEBUG: About to assertQueue ${queueName}`);
|
|
380
|
+
console.log(`[QueueManager] DEBUG: Channel state: exists=${!!channel}, closed=${channel ? (channel.closed === true ? 'true' : channel.closed === false ? 'false' : 'undefined') : 'N/A'}`);
|
|
381
|
+
console.log(`[QueueManager] DEBUG: Channel created at: ${channel?._createdAt || 'N/A'}`);
|
|
382
|
+
console.log(`[QueueManager] DEBUG: Queue options:`, JSON.stringify(queueOptions, null, 2));
|
|
339
383
|
|
|
340
384
|
await channel.assertQueue(queueName, queueOptions);
|
|
341
385
|
|
|
342
386
|
console.log(`[QueueManager] DEBUG: assertQueue succeeded for ${queueName}`);
|
|
387
|
+
if (channel && channel._lastOperation !== undefined) {
|
|
388
|
+
channel._lastOperation = `assertQueue ${queueName} succeeded`;
|
|
389
|
+
}
|
|
343
390
|
queues[queueInfo.type] = queueName;
|
|
344
391
|
queueCreated = true;
|
|
345
392
|
console.info(`[QueueManager] ✓ Created business queue: ${queueName}`);
|
|
346
393
|
} catch (assertErr) {
|
|
347
394
|
console.error(`[QueueManager] DEBUG: assertQueue failed for ${queueName}:`, assertErr.message);
|
|
348
395
|
console.error(`[QueueManager] DEBUG: Error code: ${assertErr.code}`);
|
|
396
|
+
console.error(`[QueueManager] DEBUG: Channel state after error: exists=${!!channel}, closed=${channel ? (channel.closed === true ? 'true' : channel.closed === false ? 'false' : 'undefined') : 'N/A'}`);
|
|
397
|
+
console.error(`[QueueManager] DEBUG: Channel close reason: ${channel?._closeReason || 'N/A'}`);
|
|
349
398
|
|
|
350
399
|
// If 406 PRECONDITION-FAILED, queue exists with different args - channel is closed, recreate it IMMEDIATELY
|
|
351
|
-
// CRITICAL: amqplib closes channel on 406, so we MUST recreate it to continue
|
|
400
|
+
// CRITICAL: amqplib closes channel on 406 SYNCHRONOUSLY, so we MUST recreate it to continue
|
|
352
401
|
if (assertErr.code === 406) {
|
|
353
|
-
console.warn(`[QueueManager] Queue ${queueName} exists with different arguments (406), channel
|
|
402
|
+
console.warn(`[QueueManager] Queue ${queueName} exists with different arguments (406), channel WILL BE CLOSED. Recreating channel IMMEDIATELY...`);
|
|
354
403
|
|
|
355
|
-
//
|
|
404
|
+
// CRITICAL: Channel is already closed by amqplib at this point
|
|
405
|
+
// We MUST create a new channel before continuing
|
|
356
406
|
try {
|
|
357
407
|
// Wait a tiny bit for channel close event to propagate
|
|
358
408
|
await new Promise(resolve => setImmediate(resolve));
|
|
359
|
-
|
|
409
|
+
|
|
410
|
+
// Create new channel
|
|
411
|
+
const newChannel = await transport._connection.createChannel();
|
|
412
|
+
newChannel._createdAt = new Date().toISOString();
|
|
413
|
+
newChannel._closeReason = null;
|
|
414
|
+
newChannel._lastOperation = `Recreated after 406 for ${queueName}`;
|
|
415
|
+
|
|
416
|
+
// Set up error/close handlers for new channel
|
|
417
|
+
newChannel.on('error', (err) => {
|
|
418
|
+
console.error('[RabbitMQClient] New queue channel error:', err.message);
|
|
419
|
+
newChannel._closeReason = `Error: ${err.message} (code: ${err.code})`;
|
|
420
|
+
});
|
|
421
|
+
newChannel.on('close', () => {
|
|
422
|
+
console.error('[RabbitMQClient] New queue channel closed');
|
|
423
|
+
console.error('[RabbitMQClient] Close reason:', newChannel._closeReason || 'Unknown');
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Update references
|
|
427
|
+
channel = newChannel;
|
|
360
428
|
transport.queueChannel = channel;
|
|
429
|
+
|
|
361
430
|
console.log(`[QueueManager] DEBUG: Channel recreated after 406 for ${queueName}`);
|
|
431
|
+
console.log(`[QueueManager] DEBUG: New channel created at: ${channel._createdAt}`);
|
|
362
432
|
} catch (recreateErr) {
|
|
433
|
+
console.error(`[QueueManager] DEBUG: Failed to recreate channel after 406:`, recreateErr.message);
|
|
363
434
|
throw new Error(`Failed to recreate channel after 406 for ${queueName}: ${recreateErr.message}`);
|
|
364
435
|
}
|
|
365
436
|
|
|
366
437
|
// Queue exists (just with different args) - accept it
|
|
367
438
|
queues[queueInfo.type] = queueName;
|
|
368
439
|
queueCreated = true;
|
|
369
|
-
} else {
|
|
370
|
-
//
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
440
|
+
} else if (assertErr.message && assertErr.message.includes('Channel closed')) {
|
|
441
|
+
// Channel closed error (not 406) - recreate channel
|
|
442
|
+
console.error(`[QueueManager] DEBUG: Channel was closed during assertQueue for ${queueName} (not 406), recreating...`);
|
|
443
|
+
try {
|
|
444
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
445
|
+
|
|
446
|
+
const newChannel = await transport._connection.createChannel();
|
|
447
|
+
newChannel._createdAt = new Date().toISOString();
|
|
448
|
+
newChannel._closeReason = null;
|
|
449
|
+
newChannel._lastOperation = `Recreated after channel closed error for ${queueName}`;
|
|
450
|
+
|
|
451
|
+
newChannel.on('error', (err) => {
|
|
452
|
+
console.error('[RabbitMQClient] New queue channel error:', err.message);
|
|
453
|
+
newChannel._closeReason = `Error: ${err.message} (code: ${err.code})`;
|
|
454
|
+
});
|
|
455
|
+
newChannel.on('close', () => {
|
|
456
|
+
console.error('[RabbitMQClient] New queue channel closed');
|
|
457
|
+
console.error('[RabbitMQClient] Close reason:', newChannel._closeReason || 'Unknown');
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
channel = newChannel;
|
|
461
|
+
transport.queueChannel = channel;
|
|
462
|
+
|
|
463
|
+
console.log(`[QueueManager] DEBUG: Channel recreated after channel closed error for ${queueName}`);
|
|
464
|
+
} catch (recreateErr) {
|
|
465
|
+
throw new Error(`Channel closed during assertQueue for ${queueName} and failed to recreate: ${recreateErr.message}`);
|
|
389
466
|
}
|
|
467
|
+
// Don't set queueCreated - this is a real error, not a 406
|
|
468
|
+
throw new Error(`Failed to create business queue ${queueName}: ${assertErr.message}`);
|
|
469
|
+
} else {
|
|
470
|
+
// Other error - critical, cannot continue
|
|
471
|
+
throw new Error(`Failed to create business queue ${queueName}: ${assertErr.message}`);
|
|
390
472
|
}
|
|
391
473
|
}
|
|
392
474
|
|
|
@@ -78,13 +78,26 @@ class RabbitMQClient extends EventEmitter {
|
|
|
78
78
|
// Create a separate regular channel for queue operations (assertQueue, checkQueue)
|
|
79
79
|
// This avoids RPC reply queue issues with ConfirmChannel.assertQueue()
|
|
80
80
|
this._queueChannel = await this._connection.createChannel();
|
|
81
|
+
|
|
82
|
+
// CRITICAL: Track channel state and close reason
|
|
83
|
+
this._queueChannel._createdAt = new Date().toISOString();
|
|
84
|
+
this._queueChannel._closeReason = null;
|
|
85
|
+
this._queueChannel._lastOperation = null;
|
|
86
|
+
|
|
81
87
|
this._queueChannel.on('error', (err) => {
|
|
82
88
|
// Log but don't emit - queue channel errors are less critical
|
|
83
89
|
console.error('[RabbitMQClient] Queue channel error:', err.message);
|
|
90
|
+
console.error('[RabbitMQClient] Error code:', err.code);
|
|
91
|
+
console.error('[RabbitMQClient] Channel created at:', this._queueChannel._createdAt);
|
|
92
|
+
console.error('[RabbitMQClient] Last operation:', this._queueChannel._lastOperation);
|
|
84
93
|
console.error('[RabbitMQClient] Stack trace:', new Error().stack);
|
|
94
|
+
this._queueChannel._closeReason = `Error: ${err.message} (code: ${err.code})`;
|
|
85
95
|
});
|
|
86
96
|
this._queueChannel.on('close', () => {
|
|
87
97
|
console.error('[RabbitMQClient] Queue channel closed');
|
|
98
|
+
console.error('[RabbitMQClient] Channel created at:', this._queueChannel._createdAt);
|
|
99
|
+
console.error('[RabbitMQClient] Close reason:', this._queueChannel._closeReason || 'Unknown');
|
|
100
|
+
console.error('[RabbitMQClient] Last operation:', this._queueChannel._lastOperation);
|
|
88
101
|
console.error('[RabbitMQClient] Stack trace at close:', new Error().stack);
|
|
89
102
|
});
|
|
90
103
|
} catch (err) {
|