@autofleet/rabbit 3.3.0-beta.10 → 3.3.0-beta.2
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 +13 -34
- package/dist/index.js +124 -498
- package/dist/lib/consts.d.ts +4 -2
- package/dist/lib/consts.js +6 -3
- package/dist/lib/types.d.ts +7 -1
- package/package.json +1 -1
- package/src/index.ts +159 -597
- package/src/lib/consts.ts +5 -2
- package/src/lib/types.ts +8 -2
package/src/index.ts
CHANGED
|
@@ -20,8 +20,7 @@ import getRedisInstance, { RedisConfig } from './lib/redis';
|
|
|
20
20
|
import { assertExchangeFanout, rand, wrapSetImmediate } from './lib/utils';
|
|
21
21
|
import {
|
|
22
22
|
AUTOMATION_ID_HEADER,
|
|
23
|
-
|
|
24
|
-
CONNECTION_FAILED_CONST,
|
|
23
|
+
ConnectionPurpose,
|
|
25
24
|
DEFAULT_LOCK_TIMEOUT,
|
|
26
25
|
DEFAULT_OPTIONS,
|
|
27
26
|
RETRY_HEADER,
|
|
@@ -40,13 +39,13 @@ import {
|
|
|
40
39
|
CONSUMER_DEFAULT_OPTIONS,
|
|
41
40
|
QueueSetupPromisesDictionary,
|
|
42
41
|
AssertExchangePromisesDictionary,
|
|
42
|
+
ConnectionData,
|
|
43
43
|
} from './lib/types';
|
|
44
44
|
|
|
45
45
|
// const debug = nodeDebug('af-rabbitmq')
|
|
46
46
|
const debug = logger.debug.bind(logger);
|
|
47
47
|
|
|
48
48
|
const PUBLISH_TIMEOUT = 1000 * 10;
|
|
49
|
-
|
|
50
49
|
export interface IAfRabbitMq {
|
|
51
50
|
ack: any;
|
|
52
51
|
nack: any;
|
|
@@ -86,11 +85,13 @@ type newChannelOpts = {
|
|
|
86
85
|
name?: string;
|
|
87
86
|
onClose?: null | ((args: any | null) => void);
|
|
88
87
|
options?: CreateChannelOpts | undefined;
|
|
88
|
+
connectionPurpose?: ConnectionPurpose,
|
|
89
89
|
};
|
|
90
90
|
|
|
91
91
|
type assertChannelOpts = {
|
|
92
92
|
channelName?: string;
|
|
93
93
|
force?: boolean;
|
|
94
|
+
connectionPurpose?: ConnectionPurpose;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
type AfConsumer = {
|
|
@@ -102,7 +103,7 @@ type AfConsumer = {
|
|
|
102
103
|
const HEARTBEAT = '60';
|
|
103
104
|
|
|
104
105
|
class RabbitMq implements IAfRabbitMq {
|
|
105
|
-
static parseMsg(msg: any)
|
|
106
|
+
static parseMsg(msg: any): any {
|
|
106
107
|
let { content } = msg;
|
|
107
108
|
content = content.toString();
|
|
108
109
|
|
|
@@ -143,18 +144,19 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
143
144
|
|
|
144
145
|
RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
|
|
145
146
|
|
|
146
|
-
|
|
147
|
+
publishChannel: ChannelWrapper | null;
|
|
147
148
|
|
|
148
149
|
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
149
150
|
|
|
150
|
-
blockReconnect: boolean | null | undefined
|
|
151
|
+
blockReconnect: boolean | null | undefined;
|
|
151
152
|
|
|
152
|
-
|
|
153
|
+
connectionsMap: {
|
|
154
|
+
[ConnectionPurpose.Consume]: ConnectionData;
|
|
155
|
+
[ConnectionPurpose.Publish]: ConnectionData;
|
|
156
|
+
};
|
|
153
157
|
|
|
154
158
|
em: EventEmitter;
|
|
155
159
|
|
|
156
|
-
creatingConnection: boolean;
|
|
157
|
-
|
|
158
160
|
exchanges: ExchangesCache;
|
|
159
161
|
|
|
160
162
|
queues: QueuesCache;
|
|
@@ -174,48 +176,30 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
174
176
|
|
|
175
177
|
private consumers: Array<AfConsumer> = [];
|
|
176
178
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
private vhost = 'quorum-vhost';
|
|
180
|
-
|
|
181
|
-
// TODO:[QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
|
|
182
|
-
oldChannel: ChannelWrapper | null;
|
|
183
|
-
|
|
184
|
-
oldPublishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
185
|
-
|
|
186
|
-
oldBlockReconnect: boolean | null | undefined
|
|
187
|
-
|
|
188
|
-
oldConnection: AmqpConnectionManager | null | undefined
|
|
189
|
-
|
|
190
|
-
oldEm: EventEmitter;
|
|
191
|
-
|
|
192
|
-
oldCreatingConnection: boolean;
|
|
193
|
-
|
|
194
|
-
oldExchanges: ExchangesCache;
|
|
195
|
-
|
|
196
|
-
oldQueues: QueuesCache;
|
|
197
|
-
|
|
198
|
-
oldQueueSetupPromises: QueueSetupPromisesDictionary;
|
|
199
|
-
|
|
200
|
-
oldAssertExchangePromises: AssertExchangePromisesDictionary;
|
|
201
|
-
|
|
202
|
-
oldConsumersTags: Array<[ConfirmChannel, string]>;
|
|
203
|
-
|
|
204
|
-
private oldConsumers: Array<AfConsumer> = [];
|
|
205
|
-
|
|
206
|
-
constructor(options: AfRabbitOptions = {}, redisConfig?: RedisConfig) {
|
|
179
|
+
constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig) {
|
|
207
180
|
this.em = new EventEmitter();
|
|
208
|
-
this.
|
|
181
|
+
this.publishChannel = null;
|
|
209
182
|
this.publishChannelSetupPromise = null;
|
|
210
|
-
this.
|
|
211
|
-
|
|
183
|
+
this.connectionsMap = {
|
|
184
|
+
[ConnectionPurpose.Consume]: {
|
|
185
|
+
connection: null,
|
|
186
|
+
creatingConnection: false,
|
|
187
|
+
connectionCreatedEventName: 'consumeConnectionCreated',
|
|
188
|
+
connectionFailedEventName: 'consumeConnectionFailed',
|
|
189
|
+
},
|
|
190
|
+
[ConnectionPurpose.Publish]: {
|
|
191
|
+
connection: null,
|
|
192
|
+
creatingConnection: false,
|
|
193
|
+
connectionCreatedEventName: 'publishConnectionCreated',
|
|
194
|
+
connectionFailedEventName: 'publishConnectionFailed',
|
|
195
|
+
},
|
|
196
|
+
};
|
|
212
197
|
this.exchanges = {};
|
|
213
198
|
this.queues = {};
|
|
214
199
|
this.queueSetupPromises = {};
|
|
215
200
|
this.assertExchangePromises = {};
|
|
216
201
|
this.consumers = [];
|
|
217
202
|
this.options = options;
|
|
218
|
-
|
|
219
203
|
this.redisClient = redisConfig && getRedisInstance(redisConfig);
|
|
220
204
|
if (this.redisClient) {
|
|
221
205
|
this.redisLock = promisify(RedisLock(this.redisClient)) as RedisLockType;
|
|
@@ -230,74 +214,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
230
214
|
await this.gracefulShutdown('SIGINT');
|
|
231
215
|
});
|
|
232
216
|
}
|
|
233
|
-
|
|
234
|
-
// TODO: [QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
|
|
235
|
-
this.oldEm = new EventEmitter();
|
|
236
|
-
this.oldChannel = null;
|
|
237
|
-
this.oldPublishChannelSetupPromise = null;
|
|
238
|
-
this.oldConnection = null;
|
|
239
|
-
this.oldCreatingConnection = false;
|
|
240
|
-
this.oldExchanges = {};
|
|
241
|
-
this.oldQueues = {};
|
|
242
|
-
this.oldQueueSetupPromises = {};
|
|
243
|
-
this.oldAssertExchangePromises = {};
|
|
244
|
-
this.oldConsumers = [];
|
|
245
|
-
this.oldConsumersTags = [];
|
|
246
217
|
}
|
|
247
218
|
|
|
248
|
-
private assertVHost = async () => {
|
|
249
|
-
if (this.doesVHostExist) {
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const username = process.env.RABBITMQ_USERNAME || 'guest';
|
|
254
|
-
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
255
|
-
const credentials = Buffer.from(`${username}:${password}`).toString('base64');
|
|
256
|
-
const headers = {
|
|
257
|
-
Authorization: `Basic ${credentials}`,
|
|
258
|
-
'Content-Type': 'application/json',
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
const rabbitHost = `http://${(this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost').split(':')[0]}:15672`;
|
|
262
|
-
|
|
263
|
-
const url = `${rabbitHost}/api/vhosts/${encodeURIComponent(this.vhost)}`;
|
|
264
|
-
|
|
265
|
-
try {
|
|
266
|
-
const response = await fetch(url, {
|
|
267
|
-
method: 'GET',
|
|
268
|
-
headers,
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
if (response.status === 200) {
|
|
272
|
-
this.doesVHostExist = true;
|
|
273
|
-
logger.info('Vhost exists', { vhost: this.vhost });
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (response.status !== 404) {
|
|
278
|
-
logger.error('Failed to check vhost', { response });
|
|
279
|
-
throw new RabbitError('Failed to check vhost');
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const createResponse = await fetch(url, {
|
|
283
|
-
method: 'PUT',
|
|
284
|
-
headers,
|
|
285
|
-
body: JSON.stringify({ default_queue_type: 'quorum' }),
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
if (!createResponse.ok) {
|
|
289
|
-
logger.error('Failed to create vhost', { response: createResponse });
|
|
290
|
-
throw new RabbitError('Failed to create vhost');
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
this.doesVHostExist = true;
|
|
294
|
-
logger.info('Vhost created', { vhost: this.vhost });
|
|
295
|
-
} catch (error) {
|
|
296
|
-
logger.error('Failed to check or create vhost', { error });
|
|
297
|
-
throw error;
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
|
|
301
219
|
private shouldConsumeMessageByTimestamp = async (msg: ConsumeMessageOrNull) => {
|
|
302
220
|
if (msg) {
|
|
303
221
|
const { properties: { headers } } = msg;
|
|
@@ -312,7 +230,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
312
230
|
return false;
|
|
313
231
|
}
|
|
314
232
|
|
|
315
|
-
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage)
|
|
233
|
+
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
|
|
316
234
|
if (msg) {
|
|
317
235
|
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
318
236
|
await channel.ack(msg);
|
|
@@ -338,20 +256,20 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
338
256
|
userMsg: ConsumeMessageOrNull,
|
|
339
257
|
{
|
|
340
258
|
skipRetry = false,
|
|
341
|
-
}: NackOptions = {
|
|
342
|
-
)
|
|
259
|
+
}: NackOptions = {},
|
|
260
|
+
): Promise<any> => {
|
|
343
261
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
344
262
|
if (channel && msg) {
|
|
345
263
|
if (
|
|
346
264
|
!skipRetry
|
|
347
265
|
&& (
|
|
348
|
-
!msg.properties.headers
|
|
349
|
-
|| parseInt(msg.properties.headers
|
|
266
|
+
!msg.properties.headers[RETRY_HEADER]
|
|
267
|
+
|| parseInt(msg.properties.headers[RETRY_HEADER], 10) < options.retries
|
|
350
268
|
)
|
|
351
269
|
) {
|
|
352
270
|
await this.sendToQueue(queue, RabbitMq.parseMsg(msg).content, options, {
|
|
353
271
|
...msg.properties.headers,
|
|
354
|
-
[RETRY_HEADER]: msg.properties.headers
|
|
272
|
+
[RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
|
|
355
273
|
? msg.properties.headers[RETRY_HEADER] + 1
|
|
356
274
|
: 1,
|
|
357
275
|
});
|
|
@@ -359,7 +277,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
359
277
|
const deadQueue = `${queue}-dead`;
|
|
360
278
|
await this.sendToQueue(deadQueue, RabbitMq.parseMsg(msg).content, deadQueueOptions, {
|
|
361
279
|
...msg.properties.headers,
|
|
362
|
-
[RETRY_HEADER]: msg.properties.headers
|
|
280
|
+
[RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
|
|
363
281
|
? msg.properties.headers[RETRY_HEADER] + 1
|
|
364
282
|
: 1,
|
|
365
283
|
});
|
|
@@ -373,30 +291,36 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
373
291
|
}
|
|
374
292
|
}
|
|
375
293
|
|
|
376
|
-
async getConnection() {
|
|
377
|
-
return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
|
|
294
|
+
async getConnection(connectionPurpose: ConnectionPurpose) {
|
|
295
|
+
return new Promise<AmqpConnectionManager | undefined | null>(async (resolve, reject) => {
|
|
296
|
+
const {
|
|
297
|
+
connection,
|
|
298
|
+
creatingConnection,
|
|
299
|
+
connectionCreatedEventName,
|
|
300
|
+
connectionFailedEventName,
|
|
301
|
+
} = this.connectionsMap[connectionPurpose];
|
|
378
302
|
if (this.blockReconnect) {
|
|
379
303
|
debug('rabbit: block reconnect');
|
|
380
304
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
381
305
|
// @ts-ignore
|
|
382
306
|
return resolve();
|
|
383
307
|
}
|
|
384
|
-
if (
|
|
385
|
-
if (this.options?.disableReconnect ||
|
|
308
|
+
if (connection !== null) {
|
|
309
|
+
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
386
310
|
debug('rabbit: connection - is connected');
|
|
387
311
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
388
312
|
// @ts-ignore
|
|
389
|
-
return resolve(
|
|
313
|
+
return resolve(connection);
|
|
390
314
|
}
|
|
391
315
|
debug('rabbit: connection - reconnecting');
|
|
392
316
|
}
|
|
393
|
-
if (
|
|
317
|
+
if (creatingConnection) {
|
|
394
318
|
debug('rabbit: creating connection emi');
|
|
395
|
-
this.em.once(
|
|
396
|
-
this.em.once(
|
|
319
|
+
this.em.once(connectionCreatedEventName, resolve);
|
|
320
|
+
this.em.once(connectionFailedEventName, reject);
|
|
397
321
|
return;
|
|
398
322
|
}
|
|
399
|
-
this.creatingConnection = true;
|
|
323
|
+
this.connectionsMap[connectionPurpose].creatingConnection = true;
|
|
400
324
|
let isResolved = false;
|
|
401
325
|
|
|
402
326
|
// It is import to use it as a function and not as a variable
|
|
@@ -408,36 +332,38 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
408
332
|
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
409
333
|
|
|
410
334
|
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
411
|
-
|
|
335
|
+
|
|
336
|
+
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
412
337
|
};
|
|
413
338
|
|
|
414
339
|
const defaultUrls = findServers();
|
|
415
|
-
const
|
|
340
|
+
const newConnection: AmqpConnectionManager = await connect(defaultUrls, {
|
|
416
341
|
findServers,
|
|
417
342
|
});
|
|
418
343
|
|
|
419
|
-
this.connection =
|
|
420
|
-
|
|
344
|
+
this.connectionsMap[connectionPurpose].connection = newConnection;
|
|
345
|
+
logger.info(`rabbit: created new connection ${connectionPurpose}`);
|
|
346
|
+
|
|
347
|
+
newConnection.on('error', (err) => {
|
|
421
348
|
logger.error('rabbit: connection error', { err });
|
|
422
349
|
if (!isResolved) {
|
|
423
350
|
isResolved = true;
|
|
424
351
|
reject(err);
|
|
425
|
-
this.em.emit(
|
|
352
|
+
this.em.emit(connectionFailedEventName, err);
|
|
426
353
|
}
|
|
427
354
|
});
|
|
428
355
|
|
|
429
|
-
|
|
356
|
+
newConnection.on('connectFailed', (err) => {
|
|
430
357
|
this.consumersTags = [];
|
|
431
|
-
logger.error('rabbit: connection connectFailed', { err
|
|
358
|
+
logger.error('rabbit: connection connectFailed', { err });
|
|
432
359
|
if (!isResolved) {
|
|
433
360
|
isResolved = true;
|
|
434
361
|
reject(err);
|
|
435
|
-
this.em.emit(
|
|
362
|
+
this.em.emit(connectionFailedEventName, err);
|
|
436
363
|
}
|
|
437
364
|
});
|
|
438
365
|
|
|
439
|
-
|
|
440
|
-
// this.channel = null;
|
|
366
|
+
newConnection.on('disconnect', ({ err }) => {
|
|
441
367
|
this.consumersTags = [];
|
|
442
368
|
debug('rabbit: connection closed');
|
|
443
369
|
if (this.options?.disableReconnect) {
|
|
@@ -447,25 +373,28 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
447
373
|
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
448
374
|
}
|
|
449
375
|
});
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
this.creatingConnection = false;
|
|
454
|
-
this.em.emit(CONNECTION_CREATED_CONST, connection);
|
|
376
|
+
newConnection.once('connect', async () => {
|
|
377
|
+
this.connectionsMap[connectionPurpose].creatingConnection = false;
|
|
378
|
+
this.em.emit(connectionCreatedEventName, newConnection);
|
|
455
379
|
isResolved = true;
|
|
456
|
-
resolve(
|
|
380
|
+
resolve(newConnection);
|
|
457
381
|
});
|
|
458
382
|
});
|
|
459
383
|
}
|
|
460
384
|
|
|
461
|
-
async getNewChannel({
|
|
462
|
-
|
|
385
|
+
async getNewChannel({
|
|
386
|
+
name = rand().toString(), onClose = null, options = {}, connectionPurpose = ConnectionPurpose.Consume,
|
|
387
|
+
}: newChannelOpts): Promise<ChannelWrapper> {
|
|
388
|
+
let connection!: AmqpConnectionManager | undefined | null;
|
|
463
389
|
try {
|
|
464
|
-
connection = await this.getConnection();
|
|
390
|
+
connection = await this.getConnection(connectionPurpose);
|
|
465
391
|
} catch (e) {
|
|
466
392
|
logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
467
393
|
throw e;
|
|
468
394
|
}
|
|
395
|
+
if (!connection) {
|
|
396
|
+
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
397
|
+
}
|
|
469
398
|
const channel = connection.createChannel({ ...options });
|
|
470
399
|
once(channel, 'close').then((args) => {
|
|
471
400
|
logger.error(`rabbit: channel ${name} closed`);
|
|
@@ -481,19 +410,23 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
481
410
|
}
|
|
482
411
|
}
|
|
483
412
|
|
|
484
|
-
async assertChannel({ force = false }
|
|
413
|
+
async assertChannel({ force = false, connectionPurpose = ConnectionPurpose.Consume }: assertChannelOpts): Promise<ChannelWrapper> {
|
|
414
|
+
debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.publishChannel });
|
|
485
415
|
if (!this.publishChannelSetupPromise) {
|
|
486
416
|
this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
|
|
487
|
-
if (this.
|
|
488
|
-
return resolve(this.
|
|
417
|
+
if (this.publishChannel && !force) {
|
|
418
|
+
return resolve(this.publishChannel);
|
|
489
419
|
}
|
|
490
420
|
|
|
491
421
|
try {
|
|
492
|
-
const channel = await this.getNewChannel({});
|
|
422
|
+
const channel = await this.getNewChannel({ connectionPurpose });
|
|
423
|
+
debug('rabbit: new channel got', { connectionPurpose, channel: this.publishChannel });
|
|
493
424
|
channel.on('error', (err) => {
|
|
494
425
|
logger.error('rabbit: channel error', { err });
|
|
495
426
|
});
|
|
496
|
-
|
|
427
|
+
if (connectionPurpose === ConnectionPurpose.Publish) {
|
|
428
|
+
this.publishChannel = channel;
|
|
429
|
+
}
|
|
497
430
|
resolve(channel);
|
|
498
431
|
} catch (e) {
|
|
499
432
|
reject(e);
|
|
@@ -503,9 +436,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
503
436
|
return this.publishChannelSetupPromise;
|
|
504
437
|
}
|
|
505
438
|
|
|
506
|
-
async assertExchange(exchangeName: string, options
|
|
507
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
508
|
-
|
|
439
|
+
async assertExchange(exchangeName: string, options: any = { connectionPurpose: ConnectionPurpose.Consume }) {
|
|
440
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
|
|
509
441
|
if (this.exchanges[exchangeName]) {
|
|
510
442
|
delete this.assertExchangePromises[exchangeName];
|
|
511
443
|
return this.exchanges[exchangeName];
|
|
@@ -520,58 +452,57 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
520
452
|
return this.exchanges[exchangeName];
|
|
521
453
|
}
|
|
522
454
|
|
|
523
|
-
|
|
524
|
-
async getQueueLength(queue: string) {
|
|
455
|
+
async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
|
|
525
456
|
RabbitMq.validateName('queue', queue);
|
|
526
|
-
const {
|
|
527
|
-
|
|
528
|
-
|
|
457
|
+
const { connection } = this.connectionsMap[connectionPurpose];
|
|
458
|
+
const { publishChannel } = this;
|
|
459
|
+
if (!publishChannel) {
|
|
460
|
+
throw new Error('channel is not defined');
|
|
529
461
|
}
|
|
530
|
-
debug('rabbit: getting queue length', { queue, connected:
|
|
531
|
-
return
|
|
462
|
+
debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
|
|
463
|
+
return publishChannel?.checkQueue(queue);
|
|
532
464
|
}
|
|
533
465
|
|
|
534
|
-
private async deleteQueue(queue: string) {
|
|
466
|
+
private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
|
|
535
467
|
RabbitMq.validateName('queue', queue);
|
|
536
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
468
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
537
469
|
logger.info('rabbit: deleting queue', { queue });
|
|
538
470
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
539
471
|
debug('queue deleted', deleteQueueRes);
|
|
540
472
|
return deleteQueueRes;
|
|
541
473
|
}
|
|
542
474
|
|
|
543
|
-
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
544
475
|
async bindQueue(queue: string, exchange: string) {
|
|
545
|
-
const channel: ChannelWrapper = await this.
|
|
476
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
|
|
546
477
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
547
478
|
return channel.bindQueue(queue, exchange, '');
|
|
548
479
|
}
|
|
549
480
|
|
|
550
481
|
async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
551
482
|
let queue: Replies.AssertQueue;
|
|
483
|
+
const connectionPurpose = ConnectionPurpose.Publish;
|
|
484
|
+
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
552
485
|
const localeOptions = {
|
|
553
486
|
...options,
|
|
554
487
|
durable: true,
|
|
555
488
|
arguments: {
|
|
556
489
|
...options?.arguments,
|
|
557
490
|
'x-consumer-timeout': 1000 * 60 * 60 * 24,
|
|
558
|
-
'x-queue-type': 'quorum',
|
|
491
|
+
'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
|
|
559
492
|
},
|
|
560
493
|
};
|
|
561
494
|
try {
|
|
562
|
-
const channel: ChannelWrapper = await this.assertChannel();
|
|
495
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
|
|
563
496
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
564
|
-
await channel.addSetup(
|
|
565
|
-
await setupChannel.assertQueue(queueName, localeOptions);
|
|
566
|
-
});
|
|
497
|
+
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
567
498
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
568
499
|
queue = await channel.assertQueue(queueName, localeOptions);
|
|
569
500
|
} catch (e) {
|
|
570
501
|
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
571
502
|
if (!this.options?.dontRetryAssert) {
|
|
572
503
|
debug('retrying assertQueue', { queueName });
|
|
573
|
-
const channel = await this.assertChannel({ force: true });
|
|
574
|
-
await this.deleteQueue(queueName);
|
|
504
|
+
const channel = await this.assertChannel({ force: true, connectionPurpose });
|
|
505
|
+
await this.deleteQueue(queueName, connectionPurpose);
|
|
575
506
|
|
|
576
507
|
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
577
508
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
@@ -581,11 +512,11 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
581
512
|
throw e;
|
|
582
513
|
}
|
|
583
514
|
}
|
|
515
|
+
|
|
584
516
|
this.queues[queueName] = queue;
|
|
585
517
|
return queue;
|
|
586
518
|
}
|
|
587
519
|
|
|
588
|
-
// TODO: [QUORUM-PHASE-3] Can be deleted after deleting the old consumers and publishers because all the queues are created us quorum queues
|
|
589
520
|
static shouldUseQuorum(queueName: string): boolean {
|
|
590
521
|
const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
|
|
591
522
|
|
|
@@ -601,7 +532,8 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
601
532
|
return false;
|
|
602
533
|
}
|
|
603
534
|
|
|
604
|
-
async assertQueue(queueName: string, options?: Options.AssertQueue)
|
|
535
|
+
async assertQueue(queueName: string, options?: Options.AssertQueue) {
|
|
536
|
+
debug('rabbit: start assert queue', { queueName });
|
|
605
537
|
RabbitMq.validateName('queue', queueName);
|
|
606
538
|
if (this.queues[queueName]) {
|
|
607
539
|
delete this.queueSetupPromises[queueName];
|
|
@@ -613,11 +545,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
613
545
|
}
|
|
614
546
|
|
|
615
547
|
this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
|
|
548
|
+
debug('rabbit: done assert queue', { queueName });
|
|
616
549
|
return this.queueSetupPromises[queueName];
|
|
617
550
|
}
|
|
618
551
|
|
|
619
552
|
private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
|
|
620
|
-
const isConsumerExist
|
|
553
|
+
const isConsumerExist: boolean = this.consumers.some((consumer) => consumer.queue === queue);
|
|
621
554
|
if (!isConsumerExist) {
|
|
622
555
|
logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
623
556
|
this.consumers.push({
|
|
@@ -628,27 +561,10 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
628
561
|
}
|
|
629
562
|
}
|
|
630
563
|
|
|
631
|
-
// Used by the microservices to consume messages from the queue
|
|
632
564
|
async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
633
|
-
// TODO: [QUORUM-PHASE-3] Use only the implementation of consumeNew and delete consumeNew and consumeOld
|
|
634
|
-
if (options?.isQuorumQueue !== false) {
|
|
635
|
-
await this.assertVHost();
|
|
636
|
-
await this.consumeNew(queue, callback, options);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
await this.consumeOld(queue, callback, options);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// TODO: [QUORUM-PHASE-3] Delete consumeNew we do not use it anymore
|
|
643
|
-
async consumeNew(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
644
565
|
await this.consumeFromRabbit(queue, callback, options);
|
|
645
566
|
}
|
|
646
567
|
|
|
647
|
-
// TODO: [QUORUM-PHASE-3] Delete consumeOld we do not use it anymore
|
|
648
|
-
async consumeOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
649
|
-
await this.consumeFromRabbitOld(queue, callback, options);
|
|
650
|
-
}
|
|
651
|
-
|
|
652
568
|
private async lockRedisIfNeeded(msg: any, options: any) {
|
|
653
569
|
const { properties: { headers } } = msg;
|
|
654
570
|
const timestamp = headers?.creationTimestamp;
|
|
@@ -679,13 +595,12 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
679
595
|
} = optionsWithDefaults;
|
|
680
596
|
if (useConsumeWithLock) {
|
|
681
597
|
if (!this.redisLock) {
|
|
682
|
-
throw new
|
|
598
|
+
throw new Error('Usage of consumeWithLock requires RedisInstance');
|
|
683
599
|
}
|
|
684
600
|
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
685
601
|
}
|
|
686
|
-
const channel = await this.getNewChannel({});
|
|
602
|
+
const channel = await this.getNewChannel({ connectionPurpose: ConnectionPurpose.Consume });
|
|
687
603
|
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
688
|
-
logger.info(`rabbit: channel.addSetup before ${queue} assertQueueOld`);
|
|
689
604
|
const q = await this.assertQueue(queue, optionsWithDefaults);
|
|
690
605
|
await confirmChannel.prefetch(limit, false);
|
|
691
606
|
const { consumerTag } = await confirmChannel.consume(
|
|
@@ -695,9 +610,9 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
695
610
|
return null;
|
|
696
611
|
}
|
|
697
612
|
|
|
698
|
-
const traceId = msg.properties.headers
|
|
699
|
-
const userId = msg.properties.headers
|
|
700
|
-
const automationId = msg.properties.headers
|
|
613
|
+
const traceId = msg.properties.headers[TRACING_HEADER];
|
|
614
|
+
const userId = msg.properties.headers[USER_TRACING_HEADER];
|
|
615
|
+
const automationId = msg.properties.headers[AUTOMATION_ID_HEADER];
|
|
701
616
|
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
702
617
|
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
703
618
|
const trace = newTrace(traceTypes.RABBIT);
|
|
@@ -774,45 +689,22 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
774
689
|
});
|
|
775
690
|
}
|
|
776
691
|
|
|
777
|
-
|
|
778
|
-
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
|
|
692
|
+
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
779
693
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
780
694
|
RabbitMq.validateName('exchange', exchange);
|
|
781
695
|
RabbitMq.validateName('queue', queue);
|
|
782
696
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
await this.assertVHost();
|
|
786
|
-
await this.saveConsumer(queue, callback, options);
|
|
787
|
-
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
|
|
788
|
-
|
|
789
|
-
await channel.addSetup(async (c: ConfirmChannel) => {
|
|
790
|
-
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
791
|
-
await c.assertQueue(queue);
|
|
792
|
-
this.exchanges[exchange] = assertExchange;
|
|
793
|
-
await c.prefetch(limit, false);
|
|
794
|
-
return Promise.all([
|
|
795
|
-
c.bindQueue(queue, exchange, ''),
|
|
796
|
-
this.consumeNew(
|
|
797
|
-
queue,
|
|
798
|
-
callback,
|
|
799
|
-
options,
|
|
800
|
-
),
|
|
801
|
-
]);
|
|
802
|
-
});
|
|
803
|
-
}
|
|
697
|
+
await this.saveConsumer(queue, callback, options);
|
|
698
|
+
const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
|
|
804
699
|
|
|
805
|
-
|
|
806
|
-
await this.saveConsumerOld(queue, callback, options);
|
|
807
|
-
const channelOld: ChannelWrapper = await this.getNewChannelOld({ name: `consume-exchange-${exchange}-queue-${queue}-old` });
|
|
808
|
-
await channelOld.addSetup(async (c: ConfirmChannel) => {
|
|
700
|
+
return channel.addSetup(async (c: ConfirmChannel) => {
|
|
809
701
|
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
810
|
-
|
|
811
|
-
this.
|
|
702
|
+
await c.assertQueue(queue);
|
|
703
|
+
this.exchanges[exchange] = assertExchange;
|
|
812
704
|
await c.prefetch(limit, false);
|
|
813
705
|
return Promise.all([
|
|
814
|
-
|
|
815
|
-
this.
|
|
706
|
+
c.bindQueue(queue, exchange, ''),
|
|
707
|
+
this.consume(
|
|
816
708
|
queue,
|
|
817
709
|
callback,
|
|
818
710
|
options,
|
|
@@ -821,21 +713,18 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
821
713
|
});
|
|
822
714
|
}
|
|
823
715
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
|
|
716
|
+
async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
|
|
717
|
+
debug('rabbit: start publish msg');
|
|
827
718
|
return wrapSetImmediate(async () => {
|
|
828
719
|
RabbitMq.validateName('exchange', exchange);
|
|
829
|
-
const channel: ChannelWrapper = await this.
|
|
830
|
-
await this.
|
|
720
|
+
const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
|
|
721
|
+
await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
|
|
831
722
|
await channel.publish(exchange, '',
|
|
832
723
|
Buffer.from(JSON.stringify(content)),
|
|
833
724
|
RabbitMq.getPublishOptions(customHeaders));
|
|
834
725
|
});
|
|
835
726
|
}
|
|
836
727
|
|
|
837
|
-
// Used by the microservices to send messages to the queue
|
|
838
|
-
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
839
728
|
async sendToQueue(
|
|
840
729
|
queue: string,
|
|
841
730
|
content: any,
|
|
@@ -843,7 +732,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
843
732
|
customHeaders?: any,
|
|
844
733
|
): Promise<boolean | undefined> {
|
|
845
734
|
try {
|
|
846
|
-
await this.
|
|
735
|
+
await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
|
|
847
736
|
} catch (e) {
|
|
848
737
|
logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
849
738
|
throw e;
|
|
@@ -851,57 +740,62 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
851
740
|
|
|
852
741
|
try {
|
|
853
742
|
RabbitMq.validateName('queue', queue);
|
|
854
|
-
await this.
|
|
743
|
+
await this.assertQueue(queue, options);
|
|
855
744
|
} catch (e) {
|
|
856
745
|
logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
857
746
|
throw e;
|
|
858
747
|
}
|
|
859
748
|
|
|
860
749
|
try {
|
|
861
|
-
const res = await this.
|
|
750
|
+
const res = await this.publishChannel?.sendToQueue(queue,
|
|
862
751
|
Buffer.from(JSON.stringify(content)),
|
|
863
752
|
RabbitMq.getPublishOptions(customHeaders));
|
|
864
753
|
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
865
754
|
return res;
|
|
866
755
|
} catch (e) {
|
|
867
|
-
|
|
868
|
-
logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
756
|
+
logger.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
|
|
869
757
|
throw e;
|
|
870
758
|
}
|
|
871
759
|
}
|
|
872
760
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
761
|
+
async isConnected(): Promise<boolean> {
|
|
762
|
+
debug('rabbit: start is connected');
|
|
763
|
+
const isEachConnectionConnected = await Promise.all(
|
|
764
|
+
Object.entries(this.connectionsMap).map(async ([connectionPurpose, connectionData]) => {
|
|
765
|
+
const { connection } = connectionData;
|
|
766
|
+
const isConnected = connection?.isConnected();
|
|
767
|
+
if (!isConnected) {
|
|
768
|
+
logger.error('rabbit: isConnected - false', { connectionPurpose });
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
logger.info('rabbit: isConnected - true', { connectionPurpose });
|
|
772
|
+
|
|
773
|
+
if (connectionPurpose === ConnectionPurpose.Publish) {
|
|
774
|
+
const channel: any = await this.assertChannel({ connectionPurpose: connectionPurpose as ConnectionPurpose });
|
|
775
|
+
try {
|
|
776
|
+
await Promise.all([
|
|
777
|
+
channel.waitForConnect(),
|
|
778
|
+
...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
|
|
779
|
+
]);
|
|
780
|
+
} catch (e) {
|
|
781
|
+
logger.error('rabbit: isConnected - false');
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
return true;
|
|
787
|
+
}),
|
|
788
|
+
);
|
|
789
|
+
return isEachConnectionConnected.every((isConnected) => isConnected === true);
|
|
893
790
|
}
|
|
894
791
|
|
|
895
|
-
async gracefulShutdown(signal: string)
|
|
896
|
-
|
|
897
|
-
const tagsNumber = this.consumersTags.length + this.oldConsumersTags.length;
|
|
792
|
+
async gracefulShutdown(signal: string): Promise<void> {
|
|
793
|
+
const tagsNumber = this.consumersTags.length;
|
|
898
794
|
logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
899
795
|
const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
900
|
-
const cancelTagPromisesOld = this.oldConsumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
901
796
|
// Clean the array to avoid race
|
|
902
797
|
this.consumersTags = [];
|
|
903
|
-
|
|
904
|
-
const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
|
|
798
|
+
const results = await Promise.allSettled(cancelTagPromises);
|
|
905
799
|
const rejected = results.filter((p) => p.status === 'rejected');
|
|
906
800
|
if (rejected.length > 0) {
|
|
907
801
|
logger.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
|
|
@@ -909,338 +803,6 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
909
803
|
logger.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
|
|
910
804
|
}
|
|
911
805
|
}
|
|
912
|
-
|
|
913
|
-
private async consumeFromRabbitOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
|
|
914
|
-
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
915
|
-
RabbitMq.validateName('queue', queue);
|
|
916
|
-
this.saveConsumerOld(queue, callback, options);
|
|
917
|
-
const uniqueId = randomUUID();
|
|
918
|
-
const {
|
|
919
|
-
limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace,
|
|
920
|
-
} = optionsWithDefaults;
|
|
921
|
-
if (useConsumeWithLock) {
|
|
922
|
-
if (!this.redisLock) {
|
|
923
|
-
throw new RabbitError('Usage of consumeWithLock requires RedisInstance');
|
|
924
|
-
}
|
|
925
|
-
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
926
|
-
}
|
|
927
|
-
const channel = await this.getNewChannelOld({});
|
|
928
|
-
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
929
|
-
const q = await this.assertQueueOld(queue, optionsWithDefaults);
|
|
930
|
-
await confirmChannel.prefetch(limit, false);
|
|
931
|
-
const { consumerTag } = await confirmChannel.consume(
|
|
932
|
-
queue,
|
|
933
|
-
async (msg: ConsumeMessageOrNull) => {
|
|
934
|
-
if (!msg) {
|
|
935
|
-
return null;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
const traceId = msg.properties.headers?.[TRACING_HEADER];
|
|
939
|
-
const userId = msg.properties.headers?.[USER_TRACING_HEADER];
|
|
940
|
-
const automationId = msg.properties.headers?.[AUTOMATION_ID_HEADER];
|
|
941
|
-
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
942
|
-
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
943
|
-
const trace = newTrace(traceTypes.RABBIT);
|
|
944
|
-
// setting also outbreak trace as part of legacy code
|
|
945
|
-
const outbreakTrace = outbreak.newTrace(traceTypes.RABBIT);
|
|
946
|
-
// enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
|
|
947
|
-
// and we don't want to fail the flow because of it
|
|
948
|
-
if (userId && enableRabbitTrace) {
|
|
949
|
-
try {
|
|
950
|
-
await Promise.all([
|
|
951
|
-
createOrSetRabbitTrace(trace, userId),
|
|
952
|
-
createOrSetRabbitTrace(outbreakTrace, userId),
|
|
953
|
-
]);
|
|
954
|
-
} catch (e) {
|
|
955
|
-
logger.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
956
|
-
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
if (traceId) {
|
|
961
|
-
(trace as any)?.context?.set(TRACING_HEADER, traceId);
|
|
962
|
-
(outbreakTrace as any)?.context.set(TRACING_HEADER, traceId);
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
if (auditContext) {
|
|
966
|
-
await auditContext(queue, {
|
|
967
|
-
userId,
|
|
968
|
-
automationId,
|
|
969
|
-
});
|
|
970
|
-
}
|
|
971
|
-
const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
|
|
972
|
-
if (!shouldConsume) {
|
|
973
|
-
await this.unlockRedisIfNeeded(releaseLock);
|
|
974
|
-
return this.ack(confirmChannel, msg)(msg);
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
let messageAcked = false;
|
|
978
|
-
// setting the localAck function to be used in the callback
|
|
979
|
-
|
|
980
|
-
const localAck = async () => {
|
|
981
|
-
if (messageAcked) {
|
|
982
|
-
return;
|
|
983
|
-
}
|
|
984
|
-
messageAcked = true;
|
|
985
|
-
return this.ack(confirmChannel, msg, true, releaseLock)(msg);
|
|
986
|
-
};
|
|
987
|
-
|
|
988
|
-
const localNack = async (_: ConsumeMessageOrNull, nackOptions: NackOptions = {}) => {
|
|
989
|
-
if (messageAcked) {
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
|
|
993
|
-
messageAcked = true;
|
|
994
|
-
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
|
|
995
|
-
};
|
|
996
|
-
|
|
997
|
-
try {
|
|
998
|
-
await callback(
|
|
999
|
-
parsedMessage,
|
|
1000
|
-
localAck,
|
|
1001
|
-
localNack,
|
|
1002
|
-
);
|
|
1003
|
-
} catch (e) {
|
|
1004
|
-
await localNack(msg);
|
|
1005
|
-
}
|
|
1006
|
-
}, CONSUMER_DEFAULT_OPTIONS,
|
|
1007
|
-
);
|
|
1008
|
-
if (!consumerTag) {
|
|
1009
|
-
logger.error(`rabbit: failed to consume from queue ${queue}`);
|
|
1010
|
-
} else {
|
|
1011
|
-
logger.info(`rabbit: adding tag ${consumerTag} to the array.`);
|
|
1012
|
-
this.oldConsumersTags.push([confirmChannel, consumerTag]);
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// TODO: [QUORUM-PHASE-3] Delete all the function under this line (getNewChannelOld, getConnectionOld, assertQueueOld, setupQueueOld, saveConsumerOld)
|
|
1018
|
-
async getNewChannelOld({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
|
|
1019
|
-
let connection!: AmqpConnectionManager;
|
|
1020
|
-
try {
|
|
1021
|
-
connection = await this.getConnectionOld();
|
|
1022
|
-
} catch (e) {
|
|
1023
|
-
logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
1024
|
-
throw e;
|
|
1025
|
-
}
|
|
1026
|
-
const channel = connection.createChannel({ ...options });
|
|
1027
|
-
once(channel, 'close').then((args) => {
|
|
1028
|
-
logger.error(`rabbit: channel ${name} closed`);
|
|
1029
|
-
onClose?.(args);
|
|
1030
|
-
});
|
|
1031
|
-
try {
|
|
1032
|
-
await once(channel, 'connect');
|
|
1033
|
-
debug(`rabbit: channel ${name} CONNECTED`);
|
|
1034
|
-
return channel;
|
|
1035
|
-
} catch (err) {
|
|
1036
|
-
logger.error(`rabbit: channel error ${name} error`, { err });
|
|
1037
|
-
throw err;
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
async getConnectionOld() {
|
|
1042
|
-
return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
|
|
1043
|
-
if (this.oldBlockReconnect) {
|
|
1044
|
-
debug('rabbit: block reconnect');
|
|
1045
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1046
|
-
// @ts-ignore
|
|
1047
|
-
return resolve();
|
|
1048
|
-
}
|
|
1049
|
-
if (this.oldConnection !== null) {
|
|
1050
|
-
if (this.options?.disableReconnect || this.oldConnection?.isConnected()) {
|
|
1051
|
-
debug('rabbit: connection - is connected');
|
|
1052
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1053
|
-
// @ts-ignore
|
|
1054
|
-
return resolve(this.oldConnection);
|
|
1055
|
-
}
|
|
1056
|
-
debug('rabbit: connection - reconnecting');
|
|
1057
|
-
}
|
|
1058
|
-
if (this.oldCreatingConnection) {
|
|
1059
|
-
debug('rabbit: creating connection emi');
|
|
1060
|
-
this.oldEm.once(CONNECTION_CREATED_CONST, resolve);
|
|
1061
|
-
this.oldEm.once(CONNECTION_FAILED_CONST, reject);
|
|
1062
|
-
return;
|
|
1063
|
-
}
|
|
1064
|
-
this.oldCreatingConnection = true;
|
|
1065
|
-
let isResolved = false;
|
|
1066
|
-
|
|
1067
|
-
// It is import to use it as a function and not as a variable
|
|
1068
|
-
// because of k8s changes the env variables
|
|
1069
|
-
// and we want to use the new values
|
|
1070
|
-
const findServers = () => {
|
|
1071
|
-
const userName = process.env.RABBITMQ_USERNAME || 'guest';
|
|
1072
|
-
const password = process.env.RABBITMQ_PASSWORD || 'guest';
|
|
1073
|
-
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
1074
|
-
|
|
1075
|
-
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
1076
|
-
|
|
1077
|
-
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
1078
|
-
};
|
|
1079
|
-
|
|
1080
|
-
const defaultUrls = findServers();
|
|
1081
|
-
const connection: AmqpConnectionManager = await connect(defaultUrls, {
|
|
1082
|
-
findServers,
|
|
1083
|
-
});
|
|
1084
|
-
|
|
1085
|
-
this.oldConnection = connection;
|
|
1086
|
-
this.oldConnection.on('error', (err) => {
|
|
1087
|
-
logger.error('rabbit: connection error', { err });
|
|
1088
|
-
if (!isResolved) {
|
|
1089
|
-
isResolved = true;
|
|
1090
|
-
reject(err);
|
|
1091
|
-
this.oldEm.emit(CONNECTION_FAILED_CONST, err);
|
|
1092
|
-
}
|
|
1093
|
-
});
|
|
1094
|
-
|
|
1095
|
-
this.oldConnection.on('connectFailed', (err) => {
|
|
1096
|
-
this.oldConsumersTags = [];
|
|
1097
|
-
logger.error('rabbit: connection connectFailed', { err });
|
|
1098
|
-
if (!isResolved) {
|
|
1099
|
-
isResolved = true;
|
|
1100
|
-
reject(err);
|
|
1101
|
-
this.oldEm.emit(CONNECTION_FAILED_CONST, err);
|
|
1102
|
-
}
|
|
1103
|
-
});
|
|
1104
|
-
|
|
1105
|
-
this.oldConnection.on('disconnect', ({ err }) => {
|
|
1106
|
-
this.oldConsumersTags = [];
|
|
1107
|
-
debug('rabbit: connection closed');
|
|
1108
|
-
if (this.options?.disableReconnect) {
|
|
1109
|
-
logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
|
|
1110
|
-
this.oldBlockReconnect = true;
|
|
1111
|
-
} else {
|
|
1112
|
-
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
1113
|
-
}
|
|
1114
|
-
});
|
|
1115
|
-
|
|
1116
|
-
this.oldConnection.once('connect', async () => {
|
|
1117
|
-
debug('rabbit: connection established');
|
|
1118
|
-
this.oldCreatingConnection = false;
|
|
1119
|
-
this.oldEm.emit(CONNECTION_CREATED_CONST, connection);
|
|
1120
|
-
isResolved = true;
|
|
1121
|
-
resolve(connection);
|
|
1122
|
-
});
|
|
1123
|
-
});
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
private saveConsumerOld(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
|
|
1127
|
-
const isConsumerExist :boolean = this.oldConsumers.some((consumer) => consumer.queue === queue);
|
|
1128
|
-
if (!isConsumerExist) {
|
|
1129
|
-
logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
1130
|
-
this.oldConsumers.push({
|
|
1131
|
-
queue,
|
|
1132
|
-
callback,
|
|
1133
|
-
options,
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
async assertQueueOld(queueName: string, options?: Options.AssertQueue): Promise<any> {
|
|
1139
|
-
RabbitMq.validateName('queue', queueName);
|
|
1140
|
-
if (this.oldQueues[queueName]) {
|
|
1141
|
-
delete this.oldQueueSetupPromises[queueName];
|
|
1142
|
-
return this.oldQueues[queueName];
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
if (this.oldQueueSetupPromises[queueName]) {
|
|
1146
|
-
return this.oldQueueSetupPromises[queueName];
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
this.oldQueueSetupPromises[queueName] = this.setupQueueOld(queueName, options);
|
|
1150
|
-
return this.oldQueueSetupPromises[queueName];
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
async setupQueueOld(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
1154
|
-
// let queue: Replies.AssertQueue;
|
|
1155
|
-
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
1156
|
-
const localeOptions = {
|
|
1157
|
-
...options,
|
|
1158
|
-
durable: true,
|
|
1159
|
-
arguments: {
|
|
1160
|
-
...options?.arguments,
|
|
1161
|
-
'x-consumer-timeout': 1000 * 60 * 60 * 24,
|
|
1162
|
-
'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
|
|
1163
|
-
},
|
|
1164
|
-
};
|
|
1165
|
-
try {
|
|
1166
|
-
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
1167
|
-
debug('assertQueue->channel.addSetup', { queueName });
|
|
1168
|
-
// await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
1169
|
-
debug('assertQueue->channel.assertQueue', { queueName });
|
|
1170
|
-
// queue = await channel.assertQueue(queueName, localeOptions);
|
|
1171
|
-
} catch (e) {
|
|
1172
|
-
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
1173
|
-
if (!this.options?.dontRetryAssert) {
|
|
1174
|
-
debug('retrying assertQueue', { queueName });
|
|
1175
|
-
const channel = await this.assertChannelOld({ force: true });
|
|
1176
|
-
await this.deleteQueueOld(queueName);
|
|
1177
|
-
|
|
1178
|
-
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
1179
|
-
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
1180
|
-
debug('retrying assertQueue->channel.assertQueue', { queueName });
|
|
1181
|
-
// queue = await channel.assertQueue(queueName, localeOptions);
|
|
1182
|
-
} else {
|
|
1183
|
-
throw e;
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
const queue = {
|
|
1188
|
-
queue: 'bla bla queue',
|
|
1189
|
-
messageCount: 0,
|
|
1190
|
-
consumerCount: 2,
|
|
1191
|
-
};
|
|
1192
|
-
|
|
1193
|
-
this.oldQueues[queueName] = queue;
|
|
1194
|
-
return queue;
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
async assertChannelOld({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
|
|
1198
|
-
if (!this.oldPublishChannelSetupPromise) {
|
|
1199
|
-
this.oldPublishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
|
|
1200
|
-
if (this.oldChannel && !force) {
|
|
1201
|
-
return resolve(this.oldChannel);
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
try {
|
|
1205
|
-
const channel = await this.getNewChannelOld({});
|
|
1206
|
-
channel.on('error', (err) => {
|
|
1207
|
-
logger.error('rabbit: channel error', { err });
|
|
1208
|
-
});
|
|
1209
|
-
this.oldChannel = channel;
|
|
1210
|
-
resolve(channel);
|
|
1211
|
-
} catch (e) {
|
|
1212
|
-
reject(e);
|
|
1213
|
-
}
|
|
1214
|
-
});
|
|
1215
|
-
}
|
|
1216
|
-
return this.oldPublishChannelSetupPromise;
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
private async deleteQueueOld(queue: string) {
|
|
1220
|
-
RabbitMq.validateName('queue', queue);
|
|
1221
|
-
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
1222
|
-
logger.info('rabbit: deleting queue', { queue });
|
|
1223
|
-
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
1224
|
-
debug('queue deleted', deleteQueueRes);
|
|
1225
|
-
return deleteQueueRes;
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
async assertExchangeOld(exchangeName: string, options?: any) {
|
|
1229
|
-
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
1230
|
-
|
|
1231
|
-
if (this.oldExchanges[exchangeName]) {
|
|
1232
|
-
delete this.oldAssertExchangePromises[exchangeName];
|
|
1233
|
-
return this.oldExchanges[exchangeName];
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
if (this.oldAssertExchangePromises[exchangeName]) {
|
|
1237
|
-
return this.oldAssertExchangePromises[exchangeName];
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
this.oldAssertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
|
|
1241
|
-
this.oldExchanges[exchangeName] = await this.oldAssertExchangePromises[exchangeName];
|
|
1242
|
-
return this.oldExchanges[exchangeName];
|
|
1243
|
-
}
|
|
1244
806
|
}
|
|
1245
807
|
|
|
1246
808
|
export default RabbitMq;
|