@onlineapps/conn-infra-mq 1.1.36 → 1.1.38
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 +120 -54
- 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}`);
|
|
@@ -330,63 +365,94 @@ class QueueManager {
|
|
|
330
365
|
}
|
|
331
366
|
}
|
|
332
367
|
|
|
333
|
-
// CRITICAL: Use
|
|
334
|
-
//
|
|
335
|
-
//
|
|
368
|
+
// CRITICAL: Use the SAME pattern as ensureQueue() - checkQueue() FIRST, then assertQueue() only if 404
|
|
369
|
+
// This prevents channel closure on 406 PRECONDITION-FAILED
|
|
370
|
+
// Pattern: checkQueue() -> if 404, assertQueue() -> if 406, use existing queue (channel stays open)
|
|
336
371
|
let queueCreated = false;
|
|
372
|
+
|
|
373
|
+
// Track operation on channel for debugging
|
|
374
|
+
if (channel && channel._lastOperation !== undefined) {
|
|
375
|
+
channel._lastOperation = `About to checkQueue ${queueName}`;
|
|
376
|
+
}
|
|
377
|
+
|
|
337
378
|
try {
|
|
338
|
-
console.log(`[QueueManager] DEBUG: About to
|
|
379
|
+
console.log(`[QueueManager] DEBUG: About to checkQueue ${queueName} (same pattern as ensureQueue)`);
|
|
380
|
+
console.log(`[QueueManager] DEBUG: Channel state: exists=${!!channel}, closed=${channel ? (channel.closed === true ? 'true' : channel.closed === false ? 'false' : 'undefined') : 'N/A'}`);
|
|
339
381
|
|
|
340
|
-
|
|
382
|
+
// CRITICAL: Use checkQueue() FIRST (same as ensureQueue() pattern)
|
|
383
|
+
// This prevents channel closure on 406 - if queue exists with different args, checkQueue() returns 406 but channel stays open
|
|
384
|
+
await channel.checkQueue(queueName);
|
|
341
385
|
|
|
342
|
-
|
|
386
|
+
// Queue exists - use it
|
|
387
|
+
console.log(`[QueueManager] DEBUG: checkQueue succeeded - queue ${queueName} exists`);
|
|
388
|
+
if (channel && channel._lastOperation !== undefined) {
|
|
389
|
+
channel._lastOperation = `checkQueue ${queueName} succeeded`;
|
|
390
|
+
}
|
|
343
391
|
queues[queueInfo.type] = queueName;
|
|
344
392
|
queueCreated = true;
|
|
345
|
-
console.info(`[QueueManager] ✓
|
|
346
|
-
} catch (
|
|
347
|
-
console.
|
|
348
|
-
console.
|
|
393
|
+
console.info(`[QueueManager] ✓ Business queue exists: ${queueName}`);
|
|
394
|
+
} catch (checkErr) {
|
|
395
|
+
console.log(`[QueueManager] DEBUG: checkQueue failed for ${queueName}:`, checkErr.message);
|
|
396
|
+
console.log(`[QueueManager] DEBUG: Error code: ${checkErr.code}`);
|
|
397
|
+
|
|
398
|
+
// Check if channel is still open
|
|
399
|
+
if (!channel || channel.closed) {
|
|
400
|
+
throw new Error('Channel closed during checkQueue - cannot verify queue');
|
|
401
|
+
}
|
|
349
402
|
|
|
350
|
-
// If
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
console.warn(`[QueueManager] Queue ${queueName} exists with different arguments (406), channel will be closed. Recreating channel...`);
|
|
403
|
+
// If queue doesn't exist (404), create it using assertQueue
|
|
404
|
+
if (checkErr.code === 404) {
|
|
405
|
+
console.log(`[QueueManager] DEBUG: Queue ${queueName} doesn't exist (404), creating with assertQueue...`);
|
|
354
406
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
// Wait a tiny bit for channel close event to propagate
|
|
358
|
-
await new Promise(resolve => setImmediate(resolve));
|
|
359
|
-
channel = await transport._connection.createChannel();
|
|
360
|
-
transport.queueChannel = channel;
|
|
361
|
-
console.log(`[QueueManager] DEBUG: Channel recreated after 406 for ${queueName}`);
|
|
362
|
-
} catch (recreateErr) {
|
|
363
|
-
throw new Error(`Failed to recreate channel after 406 for ${queueName}: ${recreateErr.message}`);
|
|
407
|
+
if (channel && channel._lastOperation !== undefined) {
|
|
408
|
+
channel._lastOperation = `About to assertQueue ${queueName} (queue doesn't exist)`;
|
|
364
409
|
}
|
|
365
410
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
} catch (recreateErr) {
|
|
380
|
-
throw new Error(`Channel closed during assertQueue for ${queueName} and failed to recreate: ${recreateErr.message}`);
|
|
411
|
+
try {
|
|
412
|
+
await channel.assertQueue(queueName, queueOptions);
|
|
413
|
+
console.log(`[QueueManager] DEBUG: assertQueue succeeded for ${queueName}`);
|
|
414
|
+
if (channel && channel._lastOperation !== undefined) {
|
|
415
|
+
channel._lastOperation = `assertQueue ${queueName} succeeded`;
|
|
416
|
+
}
|
|
417
|
+
queues[queueInfo.type] = queueName;
|
|
418
|
+
queueCreated = true;
|
|
419
|
+
console.info(`[QueueManager] ✓ Created business queue: ${queueName}`);
|
|
420
|
+
} catch (assertErr) {
|
|
421
|
+
// If channel closed during assertQueue, it's a real error
|
|
422
|
+
if (!channel || channel.closed) {
|
|
423
|
+
throw new Error(`Channel closed during assertQueue for ${queueName} - check channel management`);
|
|
381
424
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
425
|
+
|
|
426
|
+
// If 406 PRECONDITION-FAILED, queue exists with different args (race condition?)
|
|
427
|
+
if (assertErr.code === 406) {
|
|
428
|
+
console.warn(`[QueueManager] Queue ${queueName} exists with different arguments (406 from assertQueue):`, assertErr.message);
|
|
429
|
+
// Channel should still be open - try checkQueue to verify
|
|
430
|
+
if (!channel || channel.closed) {
|
|
431
|
+
throw new Error('Channel closed - cannot check existing queue');
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
await channel.checkQueue(queueName);
|
|
435
|
+
queues[queueInfo.type] = queueName;
|
|
436
|
+
queueCreated = true;
|
|
437
|
+
console.info(`[QueueManager] ✓ Business queue exists (verified): ${queueName}`);
|
|
438
|
+
} catch (verifyErr) {
|
|
439
|
+
throw new Error(`Failed to verify existing queue ${queueName}: ${verifyErr.message}`);
|
|
440
|
+
}
|
|
441
|
+
} else {
|
|
442
|
+
// Other errors - rethrow
|
|
443
|
+
throw assertErr;
|
|
385
444
|
}
|
|
386
|
-
} else {
|
|
387
|
-
// Other error - critical, cannot continue
|
|
388
|
-
throw new Error(`Failed to create business queue ${queueName}: ${assertErr.message}`);
|
|
389
445
|
}
|
|
446
|
+
} else if (checkErr.code === 406) {
|
|
447
|
+
// Queue exists with different args (406 from checkQueue) - use it as-is
|
|
448
|
+
// CRITICAL: checkQueue() on 406 does NOT close channel (unlike assertQueue() on 406)
|
|
449
|
+
console.warn(`[QueueManager] Queue ${queueName} exists with different arguments (406 from checkQueue), using as-is:`, checkErr.message);
|
|
450
|
+
queues[queueInfo.type] = queueName;
|
|
451
|
+
queueCreated = true;
|
|
452
|
+
console.info(`[QueueManager] ✓ Business queue exists (different args): ${queueName}`);
|
|
453
|
+
} else {
|
|
454
|
+
// Other error - rethrow
|
|
455
|
+
throw checkErr;
|
|
390
456
|
}
|
|
391
457
|
}
|
|
392
458
|
|
|
@@ -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) {
|