@onlineapps/mq-client-core 4.0.1 → 4.2.0

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.
@@ -1416,6 +1416,70 @@ class RabbitMQClient extends EventEmitter {
1416
1416
  }
1417
1417
  }
1418
1418
 
1419
+ /**
1420
+ * Race an `amqp.connect()` handshake against its `connectTimeout`, and own the
1421
+ * handshake the timeout abandons.
1422
+ *
1423
+ * When the timeout wins, the race rejects but the handshake does not stop: it
1424
+ * can still complete into a full AMQP connection that no field of this client
1425
+ * points at, so `disconnect()` never closes it and it keeps the process alive
1426
+ * (INFRA 359, measured with a proxy delaying the handshake past the timeout).
1427
+ * The rejection the caller receives is unchanged; what changes is that the
1428
+ * abandoned handshake is closed the moment it completes.
1429
+ *
1430
+ * @private
1431
+ * @param {Promise<Object>} connectPromise - the handshake from `amqp.connect()`
1432
+ * @param {Promise<never>} timeoutPromise - rejects with the timeout error
1433
+ * @param {string} phase - 'connect' or 'reconnect', named in the log lines
1434
+ * @returns {Promise<Object>} the connection, when the handshake won the race
1435
+ */
1436
+ async _raceHandshake(connectPromise, timeoutPromise, phase) {
1437
+ let abandoned = false;
1438
+ const timeoutLeg = timeoutPromise.catch((err) => {
1439
+ abandoned = true;
1440
+ throw err;
1441
+ });
1442
+ try {
1443
+ return await Promise.race([connectPromise, timeoutLeg]);
1444
+ } catch (err) {
1445
+ if (abandoned) {
1446
+ this._closeAbandonedHandshake(connectPromise, phase);
1447
+ }
1448
+ throw err;
1449
+ }
1450
+ }
1451
+
1452
+ /**
1453
+ * Close the connection an abandoned handshake produces, whenever it completes.
1454
+ * A handshake that fails after its timeout opened nothing; its error adds
1455
+ * nothing to the timeout the caller already received.
1456
+ * @private
1457
+ * @param {Promise<Object>} connectPromise - the handshake the timeout abandoned
1458
+ * @param {string} phase - 'connect' or 'reconnect'
1459
+ */
1460
+ _closeAbandonedHandshake(connectPromise, phase) {
1461
+ connectPromise.then(
1462
+ async (late) => {
1463
+ this._logger.warn(
1464
+ `[RabbitMQClient] Handshake completed after the ${phase} timeout (${this._connectTimeout} ms) - `
1465
+ + 'closing that connection, nothing in this client owns it'
1466
+ );
1467
+ try {
1468
+ await late.close();
1469
+ } catch (closeErr) {
1470
+ this._logger.warn(
1471
+ `[RabbitMQClient] Error closing the connection whose handshake outlived the ${phase} timeout: ${closeErr.message}`
1472
+ );
1473
+ }
1474
+ },
1475
+ (lateErr) => {
1476
+ this._logger.debug(
1477
+ `[RabbitMQClient] Handshake abandoned by the ${phase} timeout failed afterwards: ${lateErr.message}`
1478
+ );
1479
+ }
1480
+ );
1481
+ }
1482
+
1419
1483
  /**
1420
1484
  * Clear all active timers
1421
1485
  * @private
@@ -1459,7 +1523,7 @@ class RabbitMQClient extends EventEmitter {
1459
1523
  }
1460
1524
  });
1461
1525
  this._logger.debug('[RabbitMQClient] Starting connection race...');
1462
- this._connection = await Promise.race([connectPromise, timeoutPromise]);
1526
+ this._connection = await this._raceHandshake(connectPromise, timeoutPromise, 'connect');
1463
1527
  if (connectTimeoutTimer) {
1464
1528
  this._clearTimeout(connectTimeoutTimer);
1465
1529
  connectTimeoutTimer = null;
@@ -3437,9 +3501,11 @@ class RabbitMQClient extends EventEmitter {
3437
3501
  // leaves exactly those without a route of their own, because a dead-letter
3438
3502
  // route on a dead-letter queue is a loop or a chain with no end
3439
3503
  // (`mq-consumer-contract` 002 point 2, 003 point 2). Until d.452 the gate
3440
- // asked only the first question, so the operator's own queue was
3441
- // unconsumable and its reader — the delivery dispatcher — could not attach
3442
- // at all. The second question is derived from the templates
3504
+ // asked only the first question, so the operator's own queue could not be
3505
+ // consumed through this library at all. Who reads such a queue is that
3506
+ // service's own fact, not this file's —
3507
+ // @see api/infra/api_monitoring/src/consumer/deadLetterPeek.js
3508
+ // The second question is derived from the templates
3443
3509
  // (`queueConfig.isDeadLetterTarget()`), never from a list of names, so it
3444
3510
  // cannot fall behind them; every other queue with no route is refused
3445
3511
  // exactly as before, with the same sentence.
@@ -3939,7 +4005,7 @@ class RabbitMQClient extends EventEmitter {
3939
4005
  )), this._connectTimeout);
3940
4006
  });
3941
4007
 
3942
- const revived = await Promise.race([connectPromise, timeoutPromise]);
4008
+ const revived = await this._raceHandshake(connectPromise, timeoutPromise, 'reconnect');
3943
4009
 
3944
4010
  // The caller may have disposed of this client while that handshake was
3945
4011
  // in flight. The socket is up, and it belongs to nobody: keeping it