@onlineapps/mq-client-core 1.0.79 → 1.0.80
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/config.js +3 -0
- package/src/defaults.js +3 -0
- package/src/transports/rabbitmqClient.js +79 -27
package/package.json
CHANGED
package/src/config.js
CHANGED
package/src/defaults.js
CHANGED
|
@@ -1552,16 +1552,17 @@ class RabbitMQClient extends EventEmitter {
|
|
|
1552
1552
|
|
|
1553
1553
|
if (queueConfig) {
|
|
1554
1554
|
if (isInfraQueue) {
|
|
1555
|
-
// Infrastructure queue - use central config
|
|
1555
|
+
// Infrastructure queue - use central config for expected arguments,
|
|
1556
|
+
// but DO NOT create it here. Ownership rule: infra queues are created by their owning infra service.
|
|
1556
1557
|
try {
|
|
1557
1558
|
const infraConfig = queueConfig.getInfrastructureQueueConfig(queue);
|
|
1558
1559
|
queueOptions = {
|
|
1559
1560
|
durable: infraConfig.durable !== false,
|
|
1560
1561
|
arguments: { ...infraConfig.arguments }
|
|
1561
1562
|
};
|
|
1562
|
-
console.log(`[RabbitMQClient] [mq-client-core] [CONSUMER]
|
|
1563
|
+
console.log(`[RabbitMQClient] [mq-client-core] [CONSUMER] Checking infrastructure queue ${queue} exists (no auto-create)`);
|
|
1563
1564
|
} catch (configErr) {
|
|
1564
|
-
console.warn(`[RabbitMQClient] [mq-client-core] [CONSUMER] Infrastructure queue config not found for ${queue},
|
|
1565
|
+
console.warn(`[RabbitMQClient] [mq-client-core] [CONSUMER] Infrastructure queue config not found for ${queue}, will still require it to exist:`, configErr.message);
|
|
1565
1566
|
}
|
|
1566
1567
|
} else if (isBusinessQueue) {
|
|
1567
1568
|
// Business queue - use central config
|
|
@@ -1600,28 +1601,45 @@ class RabbitMQClient extends EventEmitter {
|
|
|
1600
1601
|
}
|
|
1601
1602
|
}
|
|
1602
1603
|
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1604
|
+
// Ensure queue channel is available
|
|
1605
|
+
await this._ensureQueueChannel();
|
|
1606
|
+
if (!this._queueChannel) {
|
|
1607
|
+
throw new Error('Queue channel is not available (connection may be closed)');
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
if (isInfraQueue) {
|
|
1611
|
+
// IMPORTANT: Do NOT auto-create infrastructure queues in consumers.
|
|
1612
|
+
// If missing, fail-fast. The owning infra service must recreate on startup.
|
|
1613
|
+
try {
|
|
1614
|
+
this._trackChannelOperation(this._queueChannel, `checkQueue ${queue}`);
|
|
1615
|
+
await this._queueChannel.checkQueue(queue);
|
|
1616
|
+
console.log(`[RabbitMQClient] [mq-client-core] [CONSUMER] ✓ Infrastructure queue ${queue} exists (consumer will proceed)`);
|
|
1617
|
+
} catch (checkErr) {
|
|
1618
|
+
if (checkErr.code === 404) {
|
|
1619
|
+
throw new Error(
|
|
1620
|
+
`Infrastructure queue ${queue} is missing. ` +
|
|
1621
|
+
'Ownership rule: infrastructure queues must be created by their owning infrastructure service via initInfrastructureQueues().'
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1624
|
+
throw checkErr;
|
|
1610
1625
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1626
|
+
} else {
|
|
1627
|
+
// Business queue (or unknown) - assert with canonical parameters to prevent 406 drift.
|
|
1628
|
+
try {
|
|
1629
|
+
console.log(`[RabbitMQClient] [mq-client-core] [CONSUMER] About to call assertQueue(${queue}, ${JSON.stringify(queueOptions)})`);
|
|
1630
|
+
this._trackChannelOperation(this._queueChannel, `assertQueue ${queue}`);
|
|
1631
|
+
await this._queueChannel.assertQueue(queue, queueOptions);
|
|
1632
|
+
console.log(`[RabbitMQClient] [mq-client-core] [CONSUMER] ✓ Queue ${queue} asserted successfully`);
|
|
1633
|
+
} catch (assertErr) {
|
|
1634
|
+
// If queue exists with different arguments (406), this is a CRITICAL ERROR
|
|
1635
|
+
// We should NOT proceed - the root cause must be fixed
|
|
1636
|
+
if (assertErr.code === 406) {
|
|
1637
|
+
console.error(`[RabbitMQClient] [mq-client-core] [CONSUMER] ✗ CRITICAL: Queue ${queue} exists with different arguments!`);
|
|
1638
|
+
console.error(`[RabbitMQClient] [mq-client-core] [CONSUMER] Error:`, assertErr.message);
|
|
1639
|
+
console.error(`[RabbitMQClient] [mq-client-core] [CONSUMER] Expected options:`, JSON.stringify(queueOptions, null, 2));
|
|
1640
|
+
console.error(`[RabbitMQClient] [mq-client-core] [CONSUMER] This means assertQueue() was called without parameters somewhere else. Root cause must be fixed!`);
|
|
1641
|
+
throw new Error(`Cannot assertQueue ${queue}: queue exists with different arguments. Root cause: assertQueue() was called without parameters. Fix the root cause instead of proceeding.`);
|
|
1642
|
+
}
|
|
1625
1643
|
// Other error - rethrow
|
|
1626
1644
|
throw assertErr;
|
|
1627
1645
|
}
|
|
@@ -1663,6 +1681,14 @@ class RabbitMQClient extends EventEmitter {
|
|
|
1663
1681
|
if (msg === null) {
|
|
1664
1682
|
return; // Consumer cancellation
|
|
1665
1683
|
}
|
|
1684
|
+
|
|
1685
|
+
// IMPORTANT:
|
|
1686
|
+
// `msg` MUST be acked/nacked on the SAME channel instance that delivered it.
|
|
1687
|
+
// During reconnects, `this._consumerChannel` may be replaced while a handler is running.
|
|
1688
|
+
// If we ack/nack a message using a different (new) channel, RabbitMQ will close the channel with:
|
|
1689
|
+
// 406 (PRECONDITION-FAILED) unknown delivery tag
|
|
1690
|
+
// which cascades into "Channel closed" errors across the service.
|
|
1691
|
+
const channelForMsg = this._consumerChannel;
|
|
1666
1692
|
|
|
1667
1693
|
// Structured log: message received
|
|
1668
1694
|
const msgHeaders = msg.properties?.headers || {};
|
|
@@ -1690,7 +1716,23 @@ class RabbitMQClient extends EventEmitter {
|
|
|
1690
1716
|
await onMessage(msg);
|
|
1691
1717
|
// Acknowledge message after successful processing
|
|
1692
1718
|
if (!noAck) {
|
|
1693
|
-
|
|
1719
|
+
// Only ack on the original channel instance; if it's gone/closed, do not attempt ack.
|
|
1720
|
+
if (!channelForMsg || channelForMsg.closed) {
|
|
1721
|
+
console.warn('[RabbitMQClient] [mq-client-core] [CONSUMER] Cannot ack - consumer channel is closed/recreated (message will be requeued by broker)');
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
try {
|
|
1725
|
+
channelForMsg.ack(msg);
|
|
1726
|
+
} catch (ackErr) {
|
|
1727
|
+
// If the channel was closed/recreated during processing, delivery tag becomes invalid.
|
|
1728
|
+
// Do NOT send further acks that would close the channel with 406.
|
|
1729
|
+
const m = ackErr && ackErr.message ? ackErr.message : '';
|
|
1730
|
+
if (m.includes('unknown delivery tag') || m.includes('PRECONDITION_FAILED') || m.includes('Channel closed')) {
|
|
1731
|
+
console.warn('[RabbitMQClient] [mq-client-core] [CONSUMER] Ack failed due to invalid delivery tag / closed channel (message delivery will be handled by broker)', { error: m });
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
throw ackErr;
|
|
1735
|
+
}
|
|
1694
1736
|
// Structured log: message acknowledged
|
|
1695
1737
|
this._log.output(wfId, 'MSG_ACKED', {
|
|
1696
1738
|
handler: 'consume',
|
|
@@ -1708,9 +1750,9 @@ class RabbitMQClient extends EventEmitter {
|
|
|
1708
1750
|
} catch (handlerErr) {
|
|
1709
1751
|
// Negative acknowledge and requeue by default
|
|
1710
1752
|
// Check if channel is still valid before nacking
|
|
1711
|
-
if (
|
|
1753
|
+
if (channelForMsg && !channelForMsg.closed) {
|
|
1712
1754
|
try {
|
|
1713
|
-
|
|
1755
|
+
channelForMsg.nack(msg, false, true);
|
|
1714
1756
|
} catch (nackErr) {
|
|
1715
1757
|
// Channel may have closed during nack - ignore
|
|
1716
1758
|
console.warn(`[RabbitMQClient] [mq-client-core] Failed to nack message (channel may be closed): ${nackErr.message}`);
|
|
@@ -2182,6 +2224,11 @@ class RabbitMQClient extends EventEmitter {
|
|
|
2182
2224
|
try {
|
|
2183
2225
|
this._consumerChannel.ack(msg);
|
|
2184
2226
|
} catch (err) {
|
|
2227
|
+
const m = err && err.message ? err.message : '';
|
|
2228
|
+
if (m.includes('unknown delivery tag') || m.includes('PRECONDITION_FAILED') || m.includes('Channel closed')) {
|
|
2229
|
+
console.warn('[RabbitMQClient] [mq-client-core] Cannot ack - consumer channel is closed/recreated (delivery tag invalid)', { error: m });
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2185
2232
|
this.emit('error', err);
|
|
2186
2233
|
throw err;
|
|
2187
2234
|
}
|
|
@@ -2200,6 +2247,11 @@ class RabbitMQClient extends EventEmitter {
|
|
|
2200
2247
|
try {
|
|
2201
2248
|
this._consumerChannel.nack(msg, false, requeue);
|
|
2202
2249
|
} catch (err) {
|
|
2250
|
+
const m = err && err.message ? err.message : '';
|
|
2251
|
+
if (m.includes('unknown delivery tag') || m.includes('PRECONDITION_FAILED') || m.includes('Channel closed')) {
|
|
2252
|
+
console.warn('[RabbitMQClient] [mq-client-core] Cannot nack - consumer channel is closed/recreated (delivery tag invalid)', { error: m });
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2203
2255
|
this.emit('error', err);
|
|
2204
2256
|
throw err;
|
|
2205
2257
|
}
|