@onlineapps/conn-infra-mq 1.1.36 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-infra-mq",
3
- "version": "1.1.36",
3
+ "version": "1.1.37",
4
4
  "description": "A promise-based, broker-agnostic client for sending and receiving messages via RabbitMQ",
5
5
  "main": "src/index.js",
6
6
  "repository": {
@@ -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
- // amqplib's channel.closed may be undefined, so we test by trying a no-op operation
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
- // Test if channel is usable by trying a no-op (checkQueue on a known queue)
260
- // This will throw if channel is closed
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
- // Try to check a queue that definitely exists (workflow.init) - this tests channel usability
264
- // If channel is closed, this will throw immediately
265
- await channel.checkQueue('workflow.init');
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 is usable before ${queueInfo.name}`);
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 test failed before ${queueInfo.name} (${testErr.message}), recreating...`);
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
- channel = await transport._connection.createChannel();
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 (test failed) before ${queueInfo.name}`);
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: Catch 406, recreate channel immediately, and continue
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} with options:`, JSON.stringify(queueOptions, null, 2));
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 will be closed. Recreating channel...`);
402
+ console.warn(`[QueueManager] Queue ${queueName} exists with different arguments (406), channel WILL BE CLOSED. Recreating channel IMMEDIATELY...`);
354
403
 
355
- // Recreate channel IMMEDIATELY - 406 closes it synchronously
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
- channel = await transport._connection.createChannel();
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
- // Other error - check if it's a channel closed error
371
- if (assertErr.message && assertErr.message.includes('Channel closed')) {
372
- console.error(`[QueueManager] DEBUG: Channel was closed during assertQueue for ${queueName}, recreating...`);
373
- // Try to recreate channel
374
- try {
375
- await new Promise(resolve => setImmediate(resolve));
376
- channel = await transport._connection.createChannel();
377
- transport.queueChannel = channel;
378
- console.log(`[QueueManager] DEBUG: Channel recreated after channel closed error for ${queueName}`);
379
- } catch (recreateErr) {
380
- throw new Error(`Channel closed during assertQueue for ${queueName} and failed to recreate: ${recreateErr.message}`);
381
- }
382
- // Retry assertQueue with new channel (but only if it's not 406)
383
- if (assertErr.code !== 406) {
384
- throw new Error(`Failed to create business queue ${queueName}: ${assertErr.message}`);
385
- }
386
- } else {
387
- // Other error - critical, cannot continue
388
- throw new Error(`Failed to create business queue ${queueName}: ${assertErr.message}`);
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) {