@autofleet/rabbit 3.3.2 → 3.3.22-connection-test-beta
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/.nvmrc +1 -1
- package/dist/index.d.ts +1 -29
- package/dist/index.js +97 -458
- package/dist/lib/consts.js +0 -1
- package/dist/lib/rabbitError.js +0 -1
- package/dist/lib/redis.js +0 -1
- package/dist/lib/types.d.ts +0 -1
- package/dist/lib/types.js +0 -1
- package/dist/lib/utils.js +0 -1
- package/dist/logger.js +0 -1
- package/dist/{mock/index.d.ts → mock.d.ts} +1 -1
- package/dist/{mock/index.js → mock.js} +1 -2
- package/package.json +17 -27
- package/src/index.ts +115 -538
- package/src/lib/types.ts +0 -2
- package/src/{mock/index.ts → mock.ts} +2 -2
- package/tsconfig.json +2 -2
- package/dist/index.js.map +0 -1
- package/dist/lib/celery.d.ts +0 -9
- package/dist/lib/celery.js +0 -55
- package/dist/lib/celery.js.map +0 -1
- package/dist/lib/consts.js.map +0 -1
- package/dist/lib/rabbitError.js.map +0 -1
- package/dist/lib/redis.js.map +0 -1
- package/dist/lib/types.js.map +0 -1
- package/dist/lib/utils.js.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/mock/index.js.map +0 -1
- package/dist/mock/vitest.d.ts +0 -13
- package/dist/mock/vitest.js +0 -18
- package/dist/mock/vitest.js.map +0 -1
- package/src/lib/celery.ts +0 -89
- package/src/mock/vitest.ts +0 -24
- package/tsconfig.build.json +0 -5
- package/vitest.config.ts +0 -16
package/dist/index.js
CHANGED
|
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.sendCeleryTaskViaHttp = void 0;
|
|
8
7
|
const events_1 = require("events");
|
|
9
8
|
const util_1 = require("util");
|
|
10
9
|
const moment_1 = __importDefault(require("moment"));
|
|
@@ -22,6 +21,38 @@ const types_1 = require("./lib/types");
|
|
|
22
21
|
const debug = logger_1.default.debug.bind(logger_1.default);
|
|
23
22
|
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
24
23
|
const HEARTBEAT = '60';
|
|
24
|
+
const withTimeout = async (promise, timeoutMs) => new Promise((resolve, reject) => {
|
|
25
|
+
const timer = setTimeout(() => {
|
|
26
|
+
reject(new Error(`Promise timed out after ${timeoutMs} ms`));
|
|
27
|
+
}, timeoutMs);
|
|
28
|
+
promise
|
|
29
|
+
.then((value) => {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
resolve(value);
|
|
32
|
+
})
|
|
33
|
+
.catch((err) => {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
reject(err);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
const withRetry = async (operation, retries, timeoutMs) => new Promise(async (resolve, reject) => {
|
|
39
|
+
let lastError;
|
|
40
|
+
for (let attempt = 1; attempt <= retries; attempt += 1) {
|
|
41
|
+
try {
|
|
42
|
+
// eslint-disable-next-line no-await-in-loop
|
|
43
|
+
const result = await withTimeout(operation(), timeoutMs);
|
|
44
|
+
return resolve(result);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
lastError = err;
|
|
48
|
+
logger_1.default.error(`Attempt ${attempt} failed: ${err}`);
|
|
49
|
+
if (attempt < retries) {
|
|
50
|
+
logger_1.default.info(`Retrying (${attempt}/${retries})...`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
reject(lastError);
|
|
55
|
+
});
|
|
25
56
|
class RabbitMq {
|
|
26
57
|
static parseMsg(msg) {
|
|
27
58
|
let { content } = msg;
|
|
@@ -56,57 +87,10 @@ class RabbitMq {
|
|
|
56
87
|
},
|
|
57
88
|
};
|
|
58
89
|
}
|
|
59
|
-
constructor(options
|
|
90
|
+
constructor(options, redisConfig) {
|
|
60
91
|
this.DISCONNECT_MSG = 'rabbit: connection disconnect';
|
|
61
92
|
this.RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
|
|
62
93
|
this.consumers = [];
|
|
63
|
-
this.doesVHostExist = false;
|
|
64
|
-
this.vhost = 'quorum-vhost';
|
|
65
|
-
this.oldConsumers = [];
|
|
66
|
-
this.assertVHost = async () => {
|
|
67
|
-
if (this.doesVHostExist) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const username = process.env.RABBITMQ_USERNAME || 'guest';
|
|
71
|
-
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
72
|
-
const credentials = Buffer.from(`${username}:${password}`).toString('base64');
|
|
73
|
-
const headers = {
|
|
74
|
-
Authorization: `Basic ${credentials}`,
|
|
75
|
-
'Content-Type': 'application/json',
|
|
76
|
-
};
|
|
77
|
-
const rabbitHost = `http://${(this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost').split(':')[0]}:15672`;
|
|
78
|
-
const url = `${rabbitHost}/api/vhosts/${encodeURIComponent(this.vhost)}`;
|
|
79
|
-
try {
|
|
80
|
-
const response = await fetch(url, {
|
|
81
|
-
method: 'GET',
|
|
82
|
-
headers,
|
|
83
|
-
});
|
|
84
|
-
if (response.status === 200) {
|
|
85
|
-
this.doesVHostExist = true;
|
|
86
|
-
logger_1.default.info('Vhost exists', { vhost: this.vhost });
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
if (response.status !== 404) {
|
|
90
|
-
logger_1.default.error('Failed to check vhost', { response });
|
|
91
|
-
throw new rabbitError_1.default('Failed to check vhost');
|
|
92
|
-
}
|
|
93
|
-
const createResponse = await fetch(url, {
|
|
94
|
-
method: 'PUT',
|
|
95
|
-
headers,
|
|
96
|
-
body: JSON.stringify({ default_queue_type: 'quorum' }),
|
|
97
|
-
});
|
|
98
|
-
if (!createResponse.ok) {
|
|
99
|
-
logger_1.default.error('Failed to create vhost', { response: createResponse });
|
|
100
|
-
throw new rabbitError_1.default('Failed to create vhost');
|
|
101
|
-
}
|
|
102
|
-
this.doesVHostExist = true;
|
|
103
|
-
logger_1.default.info('Vhost created', { vhost: this.vhost });
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
logger_1.default.error('Failed to check or create vhost', { error });
|
|
107
|
-
throw error;
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
94
|
this.shouldConsumeMessageByTimestamp = async (msg) => {
|
|
111
95
|
if (msg) {
|
|
112
96
|
const { properties: { headers } } = msg;
|
|
@@ -136,11 +120,11 @@ class RabbitMq {
|
|
|
136
120
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
137
121
|
if (channel && msg) {
|
|
138
122
|
if (!skipRetry
|
|
139
|
-
&& (!msg.properties.headers
|
|
123
|
+
&& (!msg.properties.headers[consts_1.RETRY_HEADER]
|
|
140
124
|
|| parseInt(msg.properties.headers[consts_1.RETRY_HEADER], 10) < options.retries)) {
|
|
141
125
|
await this.sendToQueue(queue, RabbitMq.parseMsg(msg).content, options, {
|
|
142
126
|
...msg.properties.headers,
|
|
143
|
-
[consts_1.RETRY_HEADER]: msg.properties.headers
|
|
127
|
+
[consts_1.RETRY_HEADER]: msg.properties.headers[consts_1.RETRY_HEADER]
|
|
144
128
|
? msg.properties.headers[consts_1.RETRY_HEADER] + 1
|
|
145
129
|
: 1,
|
|
146
130
|
});
|
|
@@ -149,7 +133,7 @@ class RabbitMq {
|
|
|
149
133
|
const deadQueue = `${queue}-dead`;
|
|
150
134
|
await this.sendToQueue(deadQueue, RabbitMq.parseMsg(msg).content, deadQueueOptions, {
|
|
151
135
|
...msg.properties.headers,
|
|
152
|
-
[consts_1.RETRY_HEADER]: msg.properties.headers
|
|
136
|
+
[consts_1.RETRY_HEADER]: msg.properties.headers[consts_1.RETRY_HEADER]
|
|
153
137
|
? msg.properties.headers[consts_1.RETRY_HEADER] + 1
|
|
154
138
|
: 1,
|
|
155
139
|
});
|
|
@@ -163,17 +147,6 @@ class RabbitMq {
|
|
|
163
147
|
});
|
|
164
148
|
}
|
|
165
149
|
};
|
|
166
|
-
this.maskURL = (url) => {
|
|
167
|
-
try {
|
|
168
|
-
const urlObj = new URL(url);
|
|
169
|
-
urlObj.username = '***';
|
|
170
|
-
urlObj.password = '***';
|
|
171
|
-
return urlObj.toString();
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
return url;
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
150
|
this.em = new events_1.EventEmitter();
|
|
178
151
|
this.channel = null;
|
|
179
152
|
this.publishChannelSetupPromise = null;
|
|
@@ -199,18 +172,6 @@ class RabbitMq {
|
|
|
199
172
|
await this.gracefulShutdown('SIGINT');
|
|
200
173
|
});
|
|
201
174
|
}
|
|
202
|
-
// TODO: [QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
|
|
203
|
-
this.oldEm = new events_1.EventEmitter();
|
|
204
|
-
this.oldChannel = null;
|
|
205
|
-
this.oldPublishChannelSetupPromise = null;
|
|
206
|
-
this.oldConnection = null;
|
|
207
|
-
this.oldCreatingConnection = false;
|
|
208
|
-
this.oldExchanges = {};
|
|
209
|
-
this.oldQueues = {};
|
|
210
|
-
this.oldQueueSetupPromises = {};
|
|
211
|
-
this.oldAssertExchangePromises = {};
|
|
212
|
-
this.oldConsumers = [];
|
|
213
|
-
this.oldConsumersTags = [];
|
|
214
175
|
}
|
|
215
176
|
async getConnection() {
|
|
216
177
|
return new Promise(async (resolve, reject) => {
|
|
@@ -245,7 +206,7 @@ class RabbitMq {
|
|
|
245
206
|
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
246
207
|
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
247
208
|
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
248
|
-
return [`amqp://${userName}:${password}@${host}
|
|
209
|
+
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
249
210
|
};
|
|
250
211
|
const defaultUrls = findServers();
|
|
251
212
|
const connection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
|
|
@@ -262,10 +223,7 @@ class RabbitMq {
|
|
|
262
223
|
});
|
|
263
224
|
this.connection.on('connectFailed', (err) => {
|
|
264
225
|
this.consumersTags = [];
|
|
265
|
-
|
|
266
|
-
err.url = this.maskURL(err.url);
|
|
267
|
-
}
|
|
268
|
-
logger_1.default.error('rabbit: connection connectFailed', { err, advice: 'Check if the vhost exist', vhost: this.vhost });
|
|
226
|
+
logger_1.default.error('rabbit: connection connectFailed', { err });
|
|
269
227
|
if (!isResolved) {
|
|
270
228
|
isResolved = true;
|
|
271
229
|
reject(err);
|
|
@@ -351,12 +309,11 @@ class RabbitMq {
|
|
|
351
309
|
this.exchanges[exchangeName] = await this.assertExchangePromises[exchangeName];
|
|
352
310
|
return this.exchanges[exchangeName];
|
|
353
311
|
}
|
|
354
|
-
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
355
312
|
async getQueueLength(queue) {
|
|
356
313
|
RabbitMq.validateName('queue', queue);
|
|
357
|
-
const {
|
|
314
|
+
const { channel } = this;
|
|
358
315
|
if (!channel) {
|
|
359
|
-
throw new
|
|
316
|
+
throw new Error('channel is not defined');
|
|
360
317
|
}
|
|
361
318
|
debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
|
|
362
319
|
return channel?.checkQueue(queue);
|
|
@@ -369,29 +326,27 @@ class RabbitMq {
|
|
|
369
326
|
debug('queue deleted', deleteQueueRes);
|
|
370
327
|
return deleteQueueRes;
|
|
371
328
|
}
|
|
372
|
-
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
373
329
|
async bindQueue(queue, exchange) {
|
|
374
|
-
const channel = await this.
|
|
330
|
+
const channel = await this.assertChannel();
|
|
375
331
|
await channel.addSetup((setupChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
376
332
|
return channel.bindQueue(queue, exchange, '');
|
|
377
333
|
}
|
|
378
334
|
async setupQueue(queueName, options) {
|
|
379
335
|
let queue;
|
|
336
|
+
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
380
337
|
const localeOptions = {
|
|
381
338
|
...options,
|
|
382
339
|
durable: true,
|
|
383
340
|
arguments: {
|
|
384
341
|
...options?.arguments,
|
|
385
342
|
'x-consumer-timeout': 1000 * 60 * 60 * 24,
|
|
386
|
-
'x-queue-type': 'quorum',
|
|
343
|
+
'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
|
|
387
344
|
},
|
|
388
345
|
};
|
|
389
346
|
try {
|
|
390
347
|
const channel = await this.assertChannel();
|
|
391
348
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
392
|
-
await channel.addSetup(
|
|
393
|
-
await setupChannel.assertQueue(queueName, localeOptions);
|
|
394
|
-
});
|
|
349
|
+
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
395
350
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
396
351
|
queue = await channel.assertQueue(queueName, localeOptions);
|
|
397
352
|
}
|
|
@@ -413,7 +368,6 @@ class RabbitMq {
|
|
|
413
368
|
this.queues[queueName] = queue;
|
|
414
369
|
return queue;
|
|
415
370
|
}
|
|
416
|
-
// TODO: [QUORUM-PHASE-3] Can be deleted after deleting the old consumers and publishers because all the queues are created us quorum queues
|
|
417
371
|
static shouldUseQuorum(queueName) {
|
|
418
372
|
const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
|
|
419
373
|
if (envQuorumQueuesWhitelist === '*') {
|
|
@@ -448,23 +402,9 @@ class RabbitMq {
|
|
|
448
402
|
});
|
|
449
403
|
}
|
|
450
404
|
}
|
|
451
|
-
// Used by the microservices to consume messages from the queue
|
|
452
405
|
async consume(queue, callback, options) {
|
|
453
|
-
// TODO: [QUORUM-PHASE-3] Use only the implementation of consumeNew and delete consumeNew and consumeOld
|
|
454
|
-
if (options?.isQuorumQueue !== false) {
|
|
455
|
-
await this.assertVHost();
|
|
456
|
-
await this.consumeNew(queue, callback, options);
|
|
457
|
-
}
|
|
458
|
-
await this.consumeOld(queue, callback, options);
|
|
459
|
-
}
|
|
460
|
-
// TODO: [QUORUM-PHASE-3] Delete consumeNew we do not use it anymore
|
|
461
|
-
async consumeNew(queue, callback, options) {
|
|
462
406
|
await this.consumeFromRabbit(queue, callback, options);
|
|
463
407
|
}
|
|
464
|
-
// TODO: [QUORUM-PHASE-3] Delete consumeOld we do not use it anymore
|
|
465
|
-
async consumeOld(queue, callback, options) {
|
|
466
|
-
await this.consumeFromRabbitOld(queue, callback, options);
|
|
467
|
-
}
|
|
468
408
|
async lockRedisIfNeeded(msg, options) {
|
|
469
409
|
const { properties: { headers } } = msg;
|
|
470
410
|
const timestamp = headers?.creationTimestamp;
|
|
@@ -487,7 +427,7 @@ class RabbitMq {
|
|
|
487
427
|
const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
|
|
488
428
|
if (useConsumeWithLock) {
|
|
489
429
|
if (!this.redisLock) {
|
|
490
|
-
throw new
|
|
430
|
+
throw new Error('Usage of consumeWithLock requires RedisInstance');
|
|
491
431
|
}
|
|
492
432
|
logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
493
433
|
}
|
|
@@ -569,57 +509,35 @@ class RabbitMq {
|
|
|
569
509
|
}
|
|
570
510
|
});
|
|
571
511
|
}
|
|
572
|
-
// Used by the microservices to consume messages from the exchange
|
|
573
512
|
async consumeFromExchange(queue, exchange, callback, options) {
|
|
574
513
|
const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
|
|
575
514
|
RabbitMq.validateName('exchange', exchange);
|
|
576
515
|
RabbitMq.validateName('queue', queue);
|
|
577
516
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
await this.saveConsumer(queue, callback, options);
|
|
582
|
-
const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
|
|
583
|
-
await channel.addSetup(async (c) => {
|
|
584
|
-
const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
|
|
585
|
-
await c.assertQueue(queue);
|
|
586
|
-
this.exchanges[exchange] = assertExchange;
|
|
587
|
-
await c.prefetch(limit, false);
|
|
588
|
-
return Promise.all([
|
|
589
|
-
c.bindQueue(queue, exchange, ''),
|
|
590
|
-
this.consumeNew(queue, callback, options),
|
|
591
|
-
]);
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
// TODO: [QUORUM-PHASE-3] Delete the old implementation
|
|
595
|
-
await this.saveConsumerOld(queue, callback, options);
|
|
596
|
-
const channelOld = await this.getNewChannelOld({ name: `consume-exchange-${exchange}-queue-${queue}-old` });
|
|
597
|
-
await channelOld.addSetup(async (c) => {
|
|
517
|
+
await this.saveConsumer(queue, callback, options);
|
|
518
|
+
const channel = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
|
|
519
|
+
return channel.addSetup(async (c) => {
|
|
598
520
|
const assertExchange = await (0, utils_1.assertExchangeFanout)(c, exchange);
|
|
599
521
|
await c.assertQueue(queue);
|
|
600
|
-
this.
|
|
601
|
-
await c.prefetch(limit,
|
|
522
|
+
this.exchanges[exchange] = assertExchange;
|
|
523
|
+
await c.prefetch(limit, true);
|
|
602
524
|
return Promise.all([
|
|
603
525
|
c.bindQueue(queue, exchange, ''),
|
|
604
|
-
this.
|
|
526
|
+
this.consume(queue, callback, options),
|
|
605
527
|
]);
|
|
606
528
|
});
|
|
607
529
|
}
|
|
608
|
-
// Used by the microservices to publish messages to the exchange
|
|
609
|
-
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
610
530
|
async publish(exchange, content, customHeaders) {
|
|
611
531
|
return (0, utils_1.wrapSetImmediate)(async () => {
|
|
612
532
|
RabbitMq.validateName('exchange', exchange);
|
|
613
|
-
const channel = await this.
|
|
614
|
-
await this.
|
|
533
|
+
const channel = await this.assertChannel();
|
|
534
|
+
await this.assertExchange(exchange);
|
|
615
535
|
await channel.publish(exchange, '', Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
616
536
|
});
|
|
617
537
|
}
|
|
618
|
-
// Used by the microservices to send messages to the queue
|
|
619
|
-
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
620
538
|
async sendToQueue(queue, content, options, customHeaders) {
|
|
621
539
|
try {
|
|
622
|
-
await this.
|
|
540
|
+
await this.assertChannel();
|
|
623
541
|
}
|
|
624
542
|
catch (e) {
|
|
625
543
|
logger_1.default.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
@@ -627,14 +545,14 @@ class RabbitMq {
|
|
|
627
545
|
}
|
|
628
546
|
try {
|
|
629
547
|
RabbitMq.validateName('queue', queue);
|
|
630
|
-
await this.
|
|
548
|
+
await this.assertQueue(queue, options);
|
|
631
549
|
}
|
|
632
550
|
catch (e) {
|
|
633
551
|
logger_1.default.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
634
552
|
throw e;
|
|
635
553
|
}
|
|
636
554
|
try {
|
|
637
|
-
const res = await this.
|
|
555
|
+
const res = await this.channel?.sendToQueue(queue, Buffer.from(JSON.stringify(content)), RabbitMq.getPublishOptions(customHeaders));
|
|
638
556
|
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
639
557
|
return res;
|
|
640
558
|
}
|
|
@@ -644,38 +562,59 @@ class RabbitMq {
|
|
|
644
562
|
throw e;
|
|
645
563
|
}
|
|
646
564
|
}
|
|
647
|
-
// TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
|
|
648
565
|
async isConnected() {
|
|
649
|
-
const connection = await this.getConnectionOld();
|
|
650
|
-
const isConnected = connection.isConnected();
|
|
651
|
-
if (!isConnected) {
|
|
652
|
-
logger_1.default.error('rabbit: isConnected - false');
|
|
653
|
-
return false;
|
|
654
|
-
}
|
|
655
|
-
const channel = await this.assertChannelOld();
|
|
656
566
|
try {
|
|
657
|
-
await
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
567
|
+
const connection = await this.getConnection();
|
|
568
|
+
if (!connection.isConnected()) {
|
|
569
|
+
logger_1.default.error('rabbit: isConnected - false');
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
const channel = await this.assertChannel();
|
|
573
|
+
const timeoutMs = 5000;
|
|
574
|
+
const retries = 2;
|
|
575
|
+
const chunkSize = 10;
|
|
576
|
+
const processChunks = async (consumers) => {
|
|
577
|
+
for (let i = 0; i < consumers.length; i += chunkSize) {
|
|
578
|
+
const chunk = consumers.slice(i, i + chunkSize);
|
|
579
|
+
// eslint-disable-next-line no-await-in-loop
|
|
580
|
+
const chunkResults = await Promise.all(chunk.map((c) => withRetry(() => channel.checkQueue(c.queue), retries, timeoutMs).catch((err) => {
|
|
581
|
+
logger_1.default.error(`rabbit: Error in isConnected (checkQueue) - ${err.message}`);
|
|
582
|
+
return null;
|
|
583
|
+
})));
|
|
584
|
+
if (chunkResults.some((res) => res === null)) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return true;
|
|
589
|
+
};
|
|
590
|
+
const waitForConnectResult = await withRetry(() => channel.waitForConnect(), retries, timeoutMs).catch((err) => {
|
|
591
|
+
logger_1.default.error(`rabbit: Error in isConnected (waitForConnect) - ${err.message}`);
|
|
592
|
+
return null;
|
|
593
|
+
});
|
|
594
|
+
if (waitForConnectResult === null) {
|
|
595
|
+
logger_1.default.error('rabbit: isConnected - false due to failed waitForConnect');
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
const allConsumersConnected = await processChunks(this.consumers);
|
|
599
|
+
if (!allConsumersConnected) {
|
|
600
|
+
logger_1.default.error('rabbit: isConnected - false due to failed consumer checks');
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
logger_1.default.info('rabbit: isConnected - true');
|
|
604
|
+
return true;
|
|
661
605
|
}
|
|
662
606
|
catch (e) {
|
|
663
|
-
logger_1.default.error(
|
|
607
|
+
logger_1.default.error(`rabbit: isConnected - Exception occurred: ${e}`);
|
|
664
608
|
return false;
|
|
665
609
|
}
|
|
666
|
-
logger_1.default.info('rabbit: isConnected - true');
|
|
667
|
-
return true;
|
|
668
610
|
}
|
|
669
611
|
async gracefulShutdown(signal) {
|
|
670
|
-
|
|
671
|
-
const tagsNumber = this.consumersTags.length + this.oldConsumersTags.length;
|
|
612
|
+
const tagsNumber = this.consumersTags.length;
|
|
672
613
|
logger_1.default.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
673
614
|
const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
674
|
-
const cancelTagPromisesOld = this.oldConsumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
675
615
|
// Clean the array to avoid race
|
|
676
616
|
this.consumersTags = [];
|
|
677
|
-
|
|
678
|
-
const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
|
|
617
|
+
const results = await Promise.allSettled(cancelTagPromises);
|
|
679
618
|
const rejected = results.filter((p) => p.status === 'rejected');
|
|
680
619
|
if (rejected.length > 0) {
|
|
681
620
|
logger_1.default.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
|
|
@@ -684,305 +623,5 @@ class RabbitMq {
|
|
|
684
623
|
logger_1.default.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
|
|
685
624
|
}
|
|
686
625
|
}
|
|
687
|
-
async consumeFromRabbitOld(queue, callback, options) {
|
|
688
|
-
const optionsWithDefaults = { ...consts_1.DEFAULT_OPTIONS, ...options };
|
|
689
|
-
RabbitMq.validateName('queue', queue);
|
|
690
|
-
this.saveConsumerOld(queue, callback, options);
|
|
691
|
-
const uniqueId = (0, node_crypto_1.randomUUID)();
|
|
692
|
-
const { limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace, } = optionsWithDefaults;
|
|
693
|
-
if (useConsumeWithLock) {
|
|
694
|
-
if (!this.redisLock) {
|
|
695
|
-
throw new rabbitError_1.default('Usage of consumeWithLock requires RedisInstance');
|
|
696
|
-
}
|
|
697
|
-
logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
698
|
-
}
|
|
699
|
-
const channel = await this.getNewChannelOld({});
|
|
700
|
-
return channel.addSetup(async (confirmChannel) => {
|
|
701
|
-
const q = await this.assertQueueOld(queue, optionsWithDefaults);
|
|
702
|
-
await confirmChannel.prefetch(limit, false);
|
|
703
|
-
const { consumerTag } = await confirmChannel.consume(queue, async (msg) => {
|
|
704
|
-
if (!msg) {
|
|
705
|
-
return null;
|
|
706
|
-
}
|
|
707
|
-
const traceId = msg.properties.headers[consts_1.TRACING_HEADER];
|
|
708
|
-
const userId = msg.properties.headers[consts_1.USER_TRACING_HEADER];
|
|
709
|
-
const automationId = msg.properties.headers[consts_1.AUTOMATION_ID_HEADER];
|
|
710
|
-
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
711
|
-
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
712
|
-
const trace = (0, zehut_1.newTrace)(zehut_1.traceTypes.RABBIT);
|
|
713
|
-
// setting also outbreak trace as part of legacy code
|
|
714
|
-
const outbreakTrace = zehut_1.outbreak.newTrace(zehut_1.traceTypes.RABBIT);
|
|
715
|
-
// enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
|
|
716
|
-
// and we don't want to fail the flow because of it
|
|
717
|
-
if (userId && enableRabbitTrace) {
|
|
718
|
-
try {
|
|
719
|
-
await Promise.all([
|
|
720
|
-
(0, zehut_1.createOrSetRabbitTrace)(trace, userId),
|
|
721
|
-
(0, zehut_1.createOrSetRabbitTrace)(outbreakTrace, userId),
|
|
722
|
-
]);
|
|
723
|
-
}
|
|
724
|
-
catch (e) {
|
|
725
|
-
logger_1.default.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
726
|
-
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
if (traceId) {
|
|
730
|
-
trace?.context?.set(consts_1.TRACING_HEADER, traceId);
|
|
731
|
-
outbreakTrace?.context.set(consts_1.TRACING_HEADER, traceId);
|
|
732
|
-
}
|
|
733
|
-
if (auditContext) {
|
|
734
|
-
await auditContext(queue, {
|
|
735
|
-
userId,
|
|
736
|
-
automationId,
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
740
|
-
if (!shouldConsume) {
|
|
741
|
-
await this.unlockRedisIfNeeded(releaseLock);
|
|
742
|
-
return this.ack(confirmChannel, msg)(msg);
|
|
743
|
-
}
|
|
744
|
-
let messageAcked = false;
|
|
745
|
-
// setting the localAck function to be used in the callback
|
|
746
|
-
const localAck = async () => {
|
|
747
|
-
if (messageAcked) {
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
messageAcked = true;
|
|
751
|
-
return this.ack(confirmChannel, msg, true, releaseLock)(msg);
|
|
752
|
-
};
|
|
753
|
-
const localNack = async (_, nackOptions = {}) => {
|
|
754
|
-
if (messageAcked) {
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
758
|
-
messageAcked = true;
|
|
759
|
-
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
|
|
760
|
-
};
|
|
761
|
-
try {
|
|
762
|
-
await callback(parsedMessage, localAck, localNack);
|
|
763
|
-
}
|
|
764
|
-
catch (e) {
|
|
765
|
-
await localNack(msg);
|
|
766
|
-
}
|
|
767
|
-
}, types_1.CONSUMER_DEFAULT_OPTIONS);
|
|
768
|
-
if (!consumerTag) {
|
|
769
|
-
logger_1.default.error(`rabbit: failed to consume from queue ${queue}`);
|
|
770
|
-
}
|
|
771
|
-
else {
|
|
772
|
-
logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
|
|
773
|
-
this.oldConsumersTags.push([confirmChannel, consumerTag]);
|
|
774
|
-
}
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
// TODO: [QUORUM-PHASE-3] Delete all the function under this line (getNewChannelOld, getConnectionOld, assertQueueOld, setupQueueOld, saveConsumerOld)
|
|
778
|
-
async getNewChannelOld({ name = (0, utils_1.rand)().toString(), onClose = null, options = {} } = {}) {
|
|
779
|
-
let connection;
|
|
780
|
-
try {
|
|
781
|
-
connection = await this.getConnectionOld();
|
|
782
|
-
}
|
|
783
|
-
catch (e) {
|
|
784
|
-
logger_1.default.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
785
|
-
throw e;
|
|
786
|
-
}
|
|
787
|
-
const channel = connection.createChannel({ ...options });
|
|
788
|
-
(0, events_1.once)(channel, 'close').then((args) => {
|
|
789
|
-
logger_1.default.error(`rabbit: channel ${name} closed`);
|
|
790
|
-
onClose?.(args);
|
|
791
|
-
});
|
|
792
|
-
try {
|
|
793
|
-
await (0, events_1.once)(channel, 'connect');
|
|
794
|
-
debug(`rabbit: channel ${name} CONNECTED`);
|
|
795
|
-
return channel;
|
|
796
|
-
}
|
|
797
|
-
catch (err) {
|
|
798
|
-
logger_1.default.error(`rabbit: channel error ${name} error`, { err });
|
|
799
|
-
throw err;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
async getConnectionOld() {
|
|
803
|
-
return new Promise(async (resolve, reject) => {
|
|
804
|
-
if (this.oldBlockReconnect) {
|
|
805
|
-
debug('rabbit: block reconnect');
|
|
806
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
807
|
-
// @ts-ignore
|
|
808
|
-
return resolve();
|
|
809
|
-
}
|
|
810
|
-
if (this.oldConnection !== null) {
|
|
811
|
-
if (this.options?.disableReconnect || this.oldConnection?.isConnected()) {
|
|
812
|
-
debug('rabbit: connection - is connected');
|
|
813
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
814
|
-
// @ts-ignore
|
|
815
|
-
return resolve(this.oldConnection);
|
|
816
|
-
}
|
|
817
|
-
debug('rabbit: connection - reconnecting');
|
|
818
|
-
}
|
|
819
|
-
if (this.oldCreatingConnection) {
|
|
820
|
-
debug('rabbit: creating connection emi');
|
|
821
|
-
this.oldEm.once(consts_1.CONNECTION_CREATED_CONST, resolve);
|
|
822
|
-
this.oldEm.once(consts_1.CONNECTION_FAILED_CONST, reject);
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
this.oldCreatingConnection = true;
|
|
826
|
-
let isResolved = false;
|
|
827
|
-
// It is import to use it as a function and not as a variable
|
|
828
|
-
// because of k8s changes the env variables
|
|
829
|
-
// and we want to use the new values
|
|
830
|
-
const findServers = () => {
|
|
831
|
-
const userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
832
|
-
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
833
|
-
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
834
|
-
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
835
|
-
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
836
|
-
};
|
|
837
|
-
const defaultUrls = findServers();
|
|
838
|
-
const connection = await (0, amqp_connection_manager_1.connect)(defaultUrls, {
|
|
839
|
-
findServers,
|
|
840
|
-
});
|
|
841
|
-
this.oldConnection = connection;
|
|
842
|
-
this.oldConnection.on('error', (err) => {
|
|
843
|
-
logger_1.default.error('rabbit: connection error', { err });
|
|
844
|
-
if (!isResolved) {
|
|
845
|
-
isResolved = true;
|
|
846
|
-
reject(err);
|
|
847
|
-
this.oldEm.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
848
|
-
}
|
|
849
|
-
});
|
|
850
|
-
this.oldConnection.on('connectFailed', (err) => {
|
|
851
|
-
this.oldConsumersTags = [];
|
|
852
|
-
if (typeof err.url === 'string') {
|
|
853
|
-
err.url = this.maskURL(err.url);
|
|
854
|
-
}
|
|
855
|
-
logger_1.default.error('rabbit: connection connectFailed', { err });
|
|
856
|
-
if (!isResolved) {
|
|
857
|
-
isResolved = true;
|
|
858
|
-
reject(err);
|
|
859
|
-
this.oldEm.emit(consts_1.CONNECTION_FAILED_CONST, err);
|
|
860
|
-
}
|
|
861
|
-
});
|
|
862
|
-
this.oldConnection.on('disconnect', ({ err }) => {
|
|
863
|
-
this.oldConsumersTags = [];
|
|
864
|
-
debug('rabbit: connection closed');
|
|
865
|
-
if (this.options?.disableReconnect) {
|
|
866
|
-
logger_1.default.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
867
|
-
this.oldBlockReconnect = true;
|
|
868
|
-
}
|
|
869
|
-
else {
|
|
870
|
-
logger_1.default.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
871
|
-
}
|
|
872
|
-
});
|
|
873
|
-
this.oldConnection.once('connect', async () => {
|
|
874
|
-
debug('rabbit: connection established');
|
|
875
|
-
this.oldCreatingConnection = false;
|
|
876
|
-
this.oldEm.emit(consts_1.CONNECTION_CREATED_CONST, connection);
|
|
877
|
-
isResolved = true;
|
|
878
|
-
resolve(connection);
|
|
879
|
-
});
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
saveConsumerOld(queue, callback, options) {
|
|
883
|
-
const isConsumerExist = this.oldConsumers.some((consumer) => consumer.queue === queue);
|
|
884
|
-
if (!isConsumerExist) {
|
|
885
|
-
logger_1.default.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
886
|
-
this.oldConsumers.push({
|
|
887
|
-
queue,
|
|
888
|
-
callback,
|
|
889
|
-
options,
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
async assertQueueOld(queueName, options) {
|
|
894
|
-
RabbitMq.validateName('queue', queueName);
|
|
895
|
-
if (this.oldQueues[queueName]) {
|
|
896
|
-
delete this.oldQueueSetupPromises[queueName];
|
|
897
|
-
return this.oldQueues[queueName];
|
|
898
|
-
}
|
|
899
|
-
if (this.oldQueueSetupPromises[queueName]) {
|
|
900
|
-
return this.oldQueueSetupPromises[queueName];
|
|
901
|
-
}
|
|
902
|
-
this.oldQueueSetupPromises[queueName] = this.setupQueueOld(queueName, options);
|
|
903
|
-
return this.oldQueueSetupPromises[queueName];
|
|
904
|
-
}
|
|
905
|
-
async setupQueueOld(queueName, options) {
|
|
906
|
-
let queue;
|
|
907
|
-
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
908
|
-
const localeOptions = {
|
|
909
|
-
...options,
|
|
910
|
-
durable: true,
|
|
911
|
-
arguments: {
|
|
912
|
-
...options?.arguments,
|
|
913
|
-
'x-consumer-timeout': 1000 * 60 * 60 * 24,
|
|
914
|
-
'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
|
|
915
|
-
},
|
|
916
|
-
};
|
|
917
|
-
try {
|
|
918
|
-
const channel = await this.assertChannelOld();
|
|
919
|
-
debug('assertQueue->channel.addSetup', { queueName });
|
|
920
|
-
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
921
|
-
debug('assertQueue->channel.assertQueue', { queueName });
|
|
922
|
-
queue = await channel.assertQueue(queueName, localeOptions);
|
|
923
|
-
}
|
|
924
|
-
catch (e) {
|
|
925
|
-
logger_1.default.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
926
|
-
if (!this.options?.dontRetryAssert) {
|
|
927
|
-
debug('retrying assertQueue', { queueName });
|
|
928
|
-
const channel = await this.assertChannelOld({ force: true });
|
|
929
|
-
await this.deleteQueueOld(queueName);
|
|
930
|
-
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
931
|
-
await channel.addSetup((setupChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
932
|
-
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
933
|
-
queue = await channel.assertQueue(queueName, localeOptions);
|
|
934
|
-
}
|
|
935
|
-
else {
|
|
936
|
-
throw e;
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
this.oldQueues[queueName] = queue;
|
|
940
|
-
return queue;
|
|
941
|
-
}
|
|
942
|
-
async assertChannelOld({ force = false } = {}) {
|
|
943
|
-
if (!this.oldPublishChannelSetupPromise) {
|
|
944
|
-
this.oldPublishChannelSetupPromise = new Promise(async (resolve, reject) => {
|
|
945
|
-
if (this.oldChannel && !force) {
|
|
946
|
-
return resolve(this.oldChannel);
|
|
947
|
-
}
|
|
948
|
-
try {
|
|
949
|
-
const channel = await this.getNewChannelOld({});
|
|
950
|
-
channel.on('error', (err) => {
|
|
951
|
-
logger_1.default.error('rabbit: channel error', { err });
|
|
952
|
-
});
|
|
953
|
-
this.oldChannel = channel;
|
|
954
|
-
resolve(channel);
|
|
955
|
-
}
|
|
956
|
-
catch (e) {
|
|
957
|
-
reject(e);
|
|
958
|
-
}
|
|
959
|
-
});
|
|
960
|
-
}
|
|
961
|
-
return this.oldPublishChannelSetupPromise;
|
|
962
|
-
}
|
|
963
|
-
async deleteQueueOld(queue) {
|
|
964
|
-
RabbitMq.validateName('queue', queue);
|
|
965
|
-
const channel = await this.assertChannelOld();
|
|
966
|
-
logger_1.default.info('rabbit: deleting queue', { queue });
|
|
967
|
-
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
968
|
-
debug('queue deleted', deleteQueueRes);
|
|
969
|
-
return deleteQueueRes;
|
|
970
|
-
}
|
|
971
|
-
async assertExchangeOld(exchangeName, options) {
|
|
972
|
-
const channel = await this.assertChannelOld();
|
|
973
|
-
if (this.oldExchanges[exchangeName]) {
|
|
974
|
-
delete this.oldAssertExchangePromises[exchangeName];
|
|
975
|
-
return this.oldExchanges[exchangeName];
|
|
976
|
-
}
|
|
977
|
-
if (this.oldAssertExchangePromises[exchangeName]) {
|
|
978
|
-
return this.oldAssertExchangePromises[exchangeName];
|
|
979
|
-
}
|
|
980
|
-
this.oldAssertExchangePromises[exchangeName] = (0, utils_1.assertExchangeFanout)(channel, exchangeName);
|
|
981
|
-
this.oldExchanges[exchangeName] = await this.oldAssertExchangePromises[exchangeName];
|
|
982
|
-
return this.oldExchanges[exchangeName];
|
|
983
|
-
}
|
|
984
626
|
}
|
|
985
627
|
exports.default = RabbitMq;
|
|
986
|
-
var celery_1 = require("./lib/celery");
|
|
987
|
-
Object.defineProperty(exports, "sendCeleryTaskViaHttp", { enumerable: true, get: function () { return celery_1.sendCeleryTaskViaHttp; } });
|
|
988
|
-
//# sourceMappingURL=index.js.map
|