@autofleet/rabbit 3.3.0-beta.1 → 3.3.0-beta.10
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 +34 -13
- package/dist/index.js +498 -129
- package/dist/lib/consts.d.ts +2 -4
- package/dist/lib/consts.js +3 -6
- package/dist/lib/types.d.ts +1 -7
- package/package.json +1 -1
- package/src/index.ts +596 -161
- package/src/lib/consts.ts +2 -5
- package/src/lib/types.ts +2 -8
package/src/index.ts
CHANGED
|
@@ -20,7 +20,8 @@ import getRedisInstance, { RedisConfig } from './lib/redis';
|
|
|
20
20
|
import { assertExchangeFanout, rand, wrapSetImmediate } from './lib/utils';
|
|
21
21
|
import {
|
|
22
22
|
AUTOMATION_ID_HEADER,
|
|
23
|
-
|
|
23
|
+
CONNECTION_CREATED_CONST,
|
|
24
|
+
CONNECTION_FAILED_CONST,
|
|
24
25
|
DEFAULT_LOCK_TIMEOUT,
|
|
25
26
|
DEFAULT_OPTIONS,
|
|
26
27
|
RETRY_HEADER,
|
|
@@ -39,13 +40,13 @@ import {
|
|
|
39
40
|
CONSUMER_DEFAULT_OPTIONS,
|
|
40
41
|
QueueSetupPromisesDictionary,
|
|
41
42
|
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
|
+
|
|
49
50
|
export interface IAfRabbitMq {
|
|
50
51
|
ack: any;
|
|
51
52
|
nack: any;
|
|
@@ -85,13 +86,11 @@ type newChannelOpts = {
|
|
|
85
86
|
name?: string;
|
|
86
87
|
onClose?: null | ((args: any | null) => void);
|
|
87
88
|
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;
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
type AfConsumer = {
|
|
@@ -103,7 +102,7 @@ type AfConsumer = {
|
|
|
103
102
|
const HEARTBEAT = '60';
|
|
104
103
|
|
|
105
104
|
class RabbitMq implements IAfRabbitMq {
|
|
106
|
-
static parseMsg(msg: any): any {
|
|
105
|
+
static parseMsg(msg: any) : any {
|
|
107
106
|
let { content } = msg;
|
|
108
107
|
content = content.toString();
|
|
109
108
|
|
|
@@ -144,19 +143,18 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
144
143
|
|
|
145
144
|
RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
|
|
146
145
|
|
|
147
|
-
|
|
146
|
+
channel: ChannelWrapper | null;
|
|
148
147
|
|
|
149
148
|
publishChannelSetupPromise: Promise<ChannelWrapper> | null;
|
|
150
149
|
|
|
151
|
-
blockReconnect: boolean | null | undefined
|
|
150
|
+
blockReconnect: boolean | null | undefined
|
|
152
151
|
|
|
153
|
-
|
|
154
|
-
[ConnectionPurpose.Consume]: ConnectionData;
|
|
155
|
-
[ConnectionPurpose.Publish]: ConnectionData;
|
|
156
|
-
};
|
|
152
|
+
connection: AmqpConnectionManager | null | undefined
|
|
157
153
|
|
|
158
154
|
em: EventEmitter;
|
|
159
155
|
|
|
156
|
+
creatingConnection: boolean;
|
|
157
|
+
|
|
160
158
|
exchanges: ExchangesCache;
|
|
161
159
|
|
|
162
160
|
queues: QueuesCache;
|
|
@@ -176,30 +174,48 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
176
174
|
|
|
177
175
|
private consumers: Array<AfConsumer> = [];
|
|
178
176
|
|
|
179
|
-
|
|
177
|
+
private doesVHostExist = false;
|
|
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) {
|
|
180
207
|
this.em = new EventEmitter();
|
|
181
|
-
this.
|
|
208
|
+
this.channel = null;
|
|
182
209
|
this.publishChannelSetupPromise = null;
|
|
183
|
-
this.
|
|
184
|
-
|
|
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
|
-
};
|
|
210
|
+
this.connection = null;
|
|
211
|
+
this.creatingConnection = false;
|
|
197
212
|
this.exchanges = {};
|
|
198
213
|
this.queues = {};
|
|
199
214
|
this.queueSetupPromises = {};
|
|
200
215
|
this.assertExchangePromises = {};
|
|
201
216
|
this.consumers = [];
|
|
202
217
|
this.options = options;
|
|
218
|
+
|
|
203
219
|
this.redisClient = redisConfig && getRedisInstance(redisConfig);
|
|
204
220
|
if (this.redisClient) {
|
|
205
221
|
this.redisLock = promisify(RedisLock(this.redisClient)) as RedisLockType;
|
|
@@ -214,8 +230,74 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
214
230
|
await this.gracefulShutdown('SIGINT');
|
|
215
231
|
});
|
|
216
232
|
}
|
|
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 = [];
|
|
217
246
|
}
|
|
218
247
|
|
|
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
|
+
|
|
219
301
|
private shouldConsumeMessageByTimestamp = async (msg: ConsumeMessageOrNull) => {
|
|
220
302
|
if (msg) {
|
|
221
303
|
const { properties: { headers } } = msg;
|
|
@@ -230,7 +312,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
230
312
|
return false;
|
|
231
313
|
}
|
|
232
314
|
|
|
233
|
-
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
|
|
315
|
+
public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage) : Promise<any> => {
|
|
234
316
|
if (msg) {
|
|
235
317
|
debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
|
|
236
318
|
await channel.ack(msg);
|
|
@@ -256,20 +338,20 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
256
338
|
userMsg: ConsumeMessageOrNull,
|
|
257
339
|
{
|
|
258
340
|
skipRetry = false,
|
|
259
|
-
}: NackOptions = {},
|
|
260
|
-
): Promise<any> => {
|
|
341
|
+
}: NackOptions = { },
|
|
342
|
+
) : Promise<any> => {
|
|
261
343
|
await this.unlockRedisIfNeeded(releaseLock);
|
|
262
344
|
if (channel && msg) {
|
|
263
345
|
if (
|
|
264
346
|
!skipRetry
|
|
265
347
|
&& (
|
|
266
|
-
!msg.properties.headers[RETRY_HEADER]
|
|
267
|
-
|| parseInt(msg.properties.headers[RETRY_HEADER], 10) < options.retries
|
|
348
|
+
!msg.properties.headers?.[RETRY_HEADER]
|
|
349
|
+
|| parseInt(msg.properties.headers?.[RETRY_HEADER], 10) < options.retries
|
|
268
350
|
)
|
|
269
351
|
) {
|
|
270
352
|
await this.sendToQueue(queue, RabbitMq.parseMsg(msg).content, options, {
|
|
271
353
|
...msg.properties.headers,
|
|
272
|
-
[RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
|
|
354
|
+
[RETRY_HEADER]: msg.properties.headers?.[RETRY_HEADER]
|
|
273
355
|
? msg.properties.headers[RETRY_HEADER] + 1
|
|
274
356
|
: 1,
|
|
275
357
|
});
|
|
@@ -277,7 +359,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
277
359
|
const deadQueue = `${queue}-dead`;
|
|
278
360
|
await this.sendToQueue(deadQueue, RabbitMq.parseMsg(msg).content, deadQueueOptions, {
|
|
279
361
|
...msg.properties.headers,
|
|
280
|
-
[RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
|
|
362
|
+
[RETRY_HEADER]: msg.properties.headers?.[RETRY_HEADER]
|
|
281
363
|
? msg.properties.headers[RETRY_HEADER] + 1
|
|
282
364
|
: 1,
|
|
283
365
|
});
|
|
@@ -291,36 +373,30 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
291
373
|
}
|
|
292
374
|
}
|
|
293
375
|
|
|
294
|
-
async getConnection(
|
|
295
|
-
return new Promise<AmqpConnectionManager
|
|
296
|
-
const {
|
|
297
|
-
connection,
|
|
298
|
-
creatingConnection,
|
|
299
|
-
connectionCreatedEventName,
|
|
300
|
-
connectionFailedEventName,
|
|
301
|
-
} = this.connectionsMap[connectionPurpose];
|
|
376
|
+
async getConnection() {
|
|
377
|
+
return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
|
|
302
378
|
if (this.blockReconnect) {
|
|
303
379
|
debug('rabbit: block reconnect');
|
|
304
380
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
305
381
|
// @ts-ignore
|
|
306
382
|
return resolve();
|
|
307
383
|
}
|
|
308
|
-
if (connection !== null) {
|
|
309
|
-
if (this.options?.disableReconnect || connection?.isConnected()) {
|
|
384
|
+
if (this.connection !== null) {
|
|
385
|
+
if (this.options?.disableReconnect || this.connection?.isConnected()) {
|
|
310
386
|
debug('rabbit: connection - is connected');
|
|
311
387
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
312
388
|
// @ts-ignore
|
|
313
|
-
return resolve(connection);
|
|
389
|
+
return resolve(this.connection);
|
|
314
390
|
}
|
|
315
391
|
debug('rabbit: connection - reconnecting');
|
|
316
392
|
}
|
|
317
|
-
if (creatingConnection) {
|
|
393
|
+
if (this.creatingConnection) {
|
|
318
394
|
debug('rabbit: creating connection emi');
|
|
319
|
-
this.em.once(
|
|
320
|
-
this.em.once(
|
|
395
|
+
this.em.once(CONNECTION_CREATED_CONST, resolve);
|
|
396
|
+
this.em.once(CONNECTION_FAILED_CONST, reject);
|
|
321
397
|
return;
|
|
322
398
|
}
|
|
323
|
-
this.
|
|
399
|
+
this.creatingConnection = true;
|
|
324
400
|
let isResolved = false;
|
|
325
401
|
|
|
326
402
|
// It is import to use it as a function and not as a variable
|
|
@@ -332,38 +408,36 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
332
408
|
const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
|
|
333
409
|
|
|
334
410
|
debug('rabbit: creating connection', { host, userName, HEARTBEAT });
|
|
335
|
-
|
|
336
|
-
return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
|
|
411
|
+
return [`amqp://${userName}:${password}@${host}/${this.vhost}?heartbeat=${HEARTBEAT}`];
|
|
337
412
|
};
|
|
338
413
|
|
|
339
414
|
const defaultUrls = findServers();
|
|
340
|
-
const
|
|
415
|
+
const connection: AmqpConnectionManager = await connect(defaultUrls, {
|
|
341
416
|
findServers,
|
|
342
417
|
});
|
|
343
418
|
|
|
344
|
-
this.
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
newConnection.on('error', (err) => {
|
|
419
|
+
this.connection = connection;
|
|
420
|
+
this.connection.on('error', (err) => {
|
|
348
421
|
logger.error('rabbit: connection error', { err });
|
|
349
422
|
if (!isResolved) {
|
|
350
423
|
isResolved = true;
|
|
351
424
|
reject(err);
|
|
352
|
-
this.em.emit(
|
|
425
|
+
this.em.emit(CONNECTION_FAILED_CONST, err);
|
|
353
426
|
}
|
|
354
427
|
});
|
|
355
428
|
|
|
356
|
-
|
|
429
|
+
this.connection.on('connectFailed', (err) => {
|
|
357
430
|
this.consumersTags = [];
|
|
358
|
-
logger.error('rabbit: connection connectFailed', { err });
|
|
431
|
+
logger.error('rabbit: connection connectFailed', { err, advice: 'Check if the vhost exist', vhost: this.vhost });
|
|
359
432
|
if (!isResolved) {
|
|
360
433
|
isResolved = true;
|
|
361
434
|
reject(err);
|
|
362
|
-
this.em.emit(
|
|
435
|
+
this.em.emit(CONNECTION_FAILED_CONST, err);
|
|
363
436
|
}
|
|
364
437
|
});
|
|
365
438
|
|
|
366
|
-
|
|
439
|
+
this.connection.on('disconnect', ({ err }) => {
|
|
440
|
+
// this.channel = null;
|
|
367
441
|
this.consumersTags = [];
|
|
368
442
|
debug('rabbit: connection closed');
|
|
369
443
|
if (this.options?.disableReconnect) {
|
|
@@ -373,28 +447,25 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
373
447
|
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
374
448
|
}
|
|
375
449
|
});
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
450
|
+
|
|
451
|
+
this.connection.once('connect', async () => {
|
|
452
|
+
debug('rabbit: connection established');
|
|
453
|
+
this.creatingConnection = false;
|
|
454
|
+
this.em.emit(CONNECTION_CREATED_CONST, connection);
|
|
379
455
|
isResolved = true;
|
|
380
|
-
resolve(
|
|
456
|
+
resolve(connection);
|
|
381
457
|
});
|
|
382
458
|
});
|
|
383
459
|
}
|
|
384
460
|
|
|
385
|
-
async getNewChannel({
|
|
386
|
-
|
|
387
|
-
}: newChannelOpts): Promise<ChannelWrapper> {
|
|
388
|
-
let connection!: AmqpConnectionManager | undefined | null;
|
|
461
|
+
async getNewChannel({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
|
|
462
|
+
let connection!: AmqpConnectionManager;
|
|
389
463
|
try {
|
|
390
|
-
connection = await this.getConnection(
|
|
464
|
+
connection = await this.getConnection();
|
|
391
465
|
} catch (e) {
|
|
392
466
|
logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
|
|
393
467
|
throw e;
|
|
394
468
|
}
|
|
395
|
-
if (!connection) {
|
|
396
|
-
throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
|
|
397
|
-
}
|
|
398
469
|
const channel = connection.createChannel({ ...options });
|
|
399
470
|
once(channel, 'close').then((args) => {
|
|
400
471
|
logger.error(`rabbit: channel ${name} closed`);
|
|
@@ -410,23 +481,19 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
410
481
|
}
|
|
411
482
|
}
|
|
412
483
|
|
|
413
|
-
async assertChannel({ force = false
|
|
414
|
-
debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.publishChannel });
|
|
484
|
+
async assertChannel({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
|
|
415
485
|
if (!this.publishChannelSetupPromise) {
|
|
416
486
|
this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
|
|
417
|
-
if (this.
|
|
418
|
-
return resolve(this.
|
|
487
|
+
if (this.channel && !force) {
|
|
488
|
+
return resolve(this.channel);
|
|
419
489
|
}
|
|
420
490
|
|
|
421
491
|
try {
|
|
422
|
-
const channel = await this.getNewChannel({
|
|
423
|
-
debug('rabbit: new channel got', { connectionPurpose, channel: this.publishChannel });
|
|
492
|
+
const channel = await this.getNewChannel({});
|
|
424
493
|
channel.on('error', (err) => {
|
|
425
494
|
logger.error('rabbit: channel error', { err });
|
|
426
495
|
});
|
|
427
|
-
|
|
428
|
-
this.publishChannel = channel;
|
|
429
|
-
}
|
|
496
|
+
this.channel = channel;
|
|
430
497
|
resolve(channel);
|
|
431
498
|
} catch (e) {
|
|
432
499
|
reject(e);
|
|
@@ -436,8 +503,9 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
436
503
|
return this.publishChannelSetupPromise;
|
|
437
504
|
}
|
|
438
505
|
|
|
439
|
-
async assertExchange(exchangeName: string, options
|
|
440
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
506
|
+
async assertExchange(exchangeName: string, options?: any) {
|
|
507
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
508
|
+
|
|
441
509
|
if (this.exchanges[exchangeName]) {
|
|
442
510
|
delete this.assertExchangePromises[exchangeName];
|
|
443
511
|
return this.exchanges[exchangeName];
|
|
@@ -452,57 +520,58 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
452
520
|
return this.exchanges[exchangeName];
|
|
453
521
|
}
|
|
454
522
|
|
|
455
|
-
|
|
523
|
+
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
524
|
+
async getQueueLength(queue: string) {
|
|
456
525
|
RabbitMq.validateName('queue', queue);
|
|
457
|
-
const {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
throw new Error('channel is not defined');
|
|
526
|
+
const { oldChannel: channel } = this;
|
|
527
|
+
if (!channel) {
|
|
528
|
+
throw new RabbitError('channel is not defined');
|
|
461
529
|
}
|
|
462
|
-
debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
|
|
463
|
-
return
|
|
530
|
+
debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
|
|
531
|
+
return channel?.checkQueue(queue);
|
|
464
532
|
}
|
|
465
533
|
|
|
466
|
-
private async deleteQueue(queue: string
|
|
534
|
+
private async deleteQueue(queue: string) {
|
|
467
535
|
RabbitMq.validateName('queue', queue);
|
|
468
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
536
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
469
537
|
logger.info('rabbit: deleting queue', { queue });
|
|
470
538
|
const deleteQueueRes = await channel.deleteQueue(queue);
|
|
471
539
|
debug('queue deleted', deleteQueueRes);
|
|
472
540
|
return deleteQueueRes;
|
|
473
541
|
}
|
|
474
542
|
|
|
543
|
+
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
475
544
|
async bindQueue(queue: string, exchange: string) {
|
|
476
|
-
const channel: ChannelWrapper = await this.
|
|
545
|
+
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
477
546
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
|
|
478
547
|
return channel.bindQueue(queue, exchange, '');
|
|
479
548
|
}
|
|
480
549
|
|
|
481
550
|
async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
|
|
482
551
|
let queue: Replies.AssertQueue;
|
|
483
|
-
const connectionPurpose = ConnectionPurpose.Publish;
|
|
484
|
-
const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
|
|
485
552
|
const localeOptions = {
|
|
486
553
|
...options,
|
|
487
554
|
durable: true,
|
|
488
555
|
arguments: {
|
|
489
556
|
...options?.arguments,
|
|
490
557
|
'x-consumer-timeout': 1000 * 60 * 60 * 24,
|
|
491
|
-
'x-queue-type':
|
|
558
|
+
'x-queue-type': 'quorum',
|
|
492
559
|
},
|
|
493
560
|
};
|
|
494
561
|
try {
|
|
495
|
-
const channel: ChannelWrapper = await this.assertChannel(
|
|
562
|
+
const channel: ChannelWrapper = await this.assertChannel();
|
|
496
563
|
debug('assertQueue->channel.addSetup', { queueName });
|
|
497
|
-
await channel.addSetup((setupChannel: ConfirmChannel) =>
|
|
564
|
+
await channel.addSetup(async (setupChannel: ConfirmChannel) => {
|
|
565
|
+
await setupChannel.assertQueue(queueName, localeOptions);
|
|
566
|
+
});
|
|
498
567
|
debug('assertQueue->channel.assertQueue', { queueName });
|
|
499
568
|
queue = await channel.assertQueue(queueName, localeOptions);
|
|
500
569
|
} catch (e) {
|
|
501
570
|
logger.error('rabbit: assertQueue error', { queueName, options, error: e });
|
|
502
571
|
if (!this.options?.dontRetryAssert) {
|
|
503
572
|
debug('retrying assertQueue', { queueName });
|
|
504
|
-
const channel = await this.assertChannel({ force: true
|
|
505
|
-
await this.deleteQueue(queueName
|
|
573
|
+
const channel = await this.assertChannel({ force: true });
|
|
574
|
+
await this.deleteQueue(queueName);
|
|
506
575
|
|
|
507
576
|
debug('retrying assertQueue->channel.addSetup', { queueName });
|
|
508
577
|
await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
|
|
@@ -512,11 +581,11 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
512
581
|
throw e;
|
|
513
582
|
}
|
|
514
583
|
}
|
|
515
|
-
|
|
516
584
|
this.queues[queueName] = queue;
|
|
517
585
|
return queue;
|
|
518
586
|
}
|
|
519
587
|
|
|
588
|
+
// TODO: [QUORUM-PHASE-3] Can be deleted after deleting the old consumers and publishers because all the queues are created us quorum queues
|
|
520
589
|
static shouldUseQuorum(queueName: string): boolean {
|
|
521
590
|
const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
|
|
522
591
|
|
|
@@ -532,8 +601,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
532
601
|
return false;
|
|
533
602
|
}
|
|
534
603
|
|
|
535
|
-
async assertQueue(queueName: string, options?: Options.AssertQueue) {
|
|
536
|
-
debug('rabbit: start assert queue', { queueName });
|
|
604
|
+
async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any> {
|
|
537
605
|
RabbitMq.validateName('queue', queueName);
|
|
538
606
|
if (this.queues[queueName]) {
|
|
539
607
|
delete this.queueSetupPromises[queueName];
|
|
@@ -545,12 +613,11 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
545
613
|
}
|
|
546
614
|
|
|
547
615
|
this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
|
|
548
|
-
debug('rabbit: done assert queue', { queueName });
|
|
549
616
|
return this.queueSetupPromises[queueName];
|
|
550
617
|
}
|
|
551
618
|
|
|
552
619
|
private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
|
|
553
|
-
const isConsumerExist:
|
|
620
|
+
const isConsumerExist :boolean = this.consumers.some((consumer) => consumer.queue === queue);
|
|
554
621
|
if (!isConsumerExist) {
|
|
555
622
|
logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
|
|
556
623
|
this.consumers.push({
|
|
@@ -561,10 +628,27 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
561
628
|
}
|
|
562
629
|
}
|
|
563
630
|
|
|
631
|
+
// Used by the microservices to consume messages from the queue
|
|
564
632
|
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> {
|
|
565
644
|
await this.consumeFromRabbit(queue, callback, options);
|
|
566
645
|
}
|
|
567
646
|
|
|
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
|
+
|
|
568
652
|
private async lockRedisIfNeeded(msg: any, options: any) {
|
|
569
653
|
const { properties: { headers } } = msg;
|
|
570
654
|
const timestamp = headers?.creationTimestamp;
|
|
@@ -595,12 +679,13 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
595
679
|
} = optionsWithDefaults;
|
|
596
680
|
if (useConsumeWithLock) {
|
|
597
681
|
if (!this.redisLock) {
|
|
598
|
-
throw new
|
|
682
|
+
throw new RabbitError('Usage of consumeWithLock requires RedisInstance');
|
|
599
683
|
}
|
|
600
684
|
logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
|
|
601
685
|
}
|
|
602
|
-
const channel = await this.getNewChannel({
|
|
686
|
+
const channel = await this.getNewChannel({});
|
|
603
687
|
return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
|
|
688
|
+
logger.info(`rabbit: channel.addSetup before ${queue} assertQueueOld`);
|
|
604
689
|
const q = await this.assertQueue(queue, optionsWithDefaults);
|
|
605
690
|
await confirmChannel.prefetch(limit, false);
|
|
606
691
|
const { consumerTag } = await confirmChannel.consume(
|
|
@@ -610,9 +695,9 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
610
695
|
return null;
|
|
611
696
|
}
|
|
612
697
|
|
|
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];
|
|
698
|
+
const traceId = msg.properties.headers?.[TRACING_HEADER];
|
|
699
|
+
const userId = msg.properties.headers?.[USER_TRACING_HEADER];
|
|
700
|
+
const automationId = msg.properties.headers?.[AUTOMATION_ID_HEADER];
|
|
616
701
|
const parsedMessage = RabbitMq.parseMsg(msg);
|
|
617
702
|
const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
|
|
618
703
|
const trace = newTrace(traceTypes.RABBIT);
|
|
@@ -689,22 +774,45 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
689
774
|
});
|
|
690
775
|
}
|
|
691
776
|
|
|
692
|
-
|
|
777
|
+
// Used by the microservices to consume messages from the exchange
|
|
778
|
+
async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
|
|
693
779
|
const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
|
|
694
780
|
RabbitMq.validateName('exchange', exchange);
|
|
695
781
|
RabbitMq.validateName('queue', queue);
|
|
696
782
|
const { limit, deadMessageTtl } = optionsWithDefaults;
|
|
697
|
-
|
|
698
|
-
|
|
783
|
+
// TODO: [QUORUM-PHASE-3] Delete the if statement after all the queues are created as quorum queues
|
|
784
|
+
if (options?.isQuorumQueue !== false) {
|
|
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
|
+
}
|
|
699
804
|
|
|
700
|
-
|
|
805
|
+
// TODO: [QUORUM-PHASE-3] Delete the old implementation
|
|
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) => {
|
|
701
809
|
const assertExchange = await assertExchangeFanout(c, exchange);
|
|
702
|
-
await c.assertQueue(queue);
|
|
703
|
-
this.
|
|
810
|
+
// await c.assertQueue(queue);
|
|
811
|
+
this.oldExchanges[exchange] = assertExchange;
|
|
704
812
|
await c.prefetch(limit, false);
|
|
705
813
|
return Promise.all([
|
|
706
|
-
c.bindQueue(queue, exchange, ''),
|
|
707
|
-
this.
|
|
814
|
+
// c.bindQueue(queue, exchange, ''),
|
|
815
|
+
this.consumeOld(
|
|
708
816
|
queue,
|
|
709
817
|
callback,
|
|
710
818
|
options,
|
|
@@ -713,18 +821,21 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
713
821
|
});
|
|
714
822
|
}
|
|
715
823
|
|
|
716
|
-
|
|
717
|
-
|
|
824
|
+
// Used by the microservices to publish messages to the exchange
|
|
825
|
+
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
826
|
+
async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
|
|
718
827
|
return wrapSetImmediate(async () => {
|
|
719
828
|
RabbitMq.validateName('exchange', exchange);
|
|
720
|
-
const channel: ChannelWrapper = await this.
|
|
721
|
-
await this.
|
|
829
|
+
const channel: ChannelWrapper = await this.assertChannelOld();
|
|
830
|
+
await this.assertExchangeOld(exchange);
|
|
722
831
|
await channel.publish(exchange, '',
|
|
723
832
|
Buffer.from(JSON.stringify(content)),
|
|
724
833
|
RabbitMq.getPublishOptions(customHeaders));
|
|
725
834
|
});
|
|
726
835
|
}
|
|
727
836
|
|
|
837
|
+
// Used by the microservices to send messages to the queue
|
|
838
|
+
// TODO: [QUORUM-PHASE-2] Change the implementation to the new one
|
|
728
839
|
async sendToQueue(
|
|
729
840
|
queue: string,
|
|
730
841
|
content: any,
|
|
@@ -732,7 +843,7 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
732
843
|
customHeaders?: any,
|
|
733
844
|
): Promise<boolean | undefined> {
|
|
734
845
|
try {
|
|
735
|
-
await this.
|
|
846
|
+
await this.assertChannelOld();
|
|
736
847
|
} catch (e) {
|
|
737
848
|
logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
|
|
738
849
|
throw e;
|
|
@@ -740,71 +851,395 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
740
851
|
|
|
741
852
|
try {
|
|
742
853
|
RabbitMq.validateName('queue', queue);
|
|
743
|
-
await this.
|
|
854
|
+
await this.assertQueueOld(queue, options);
|
|
744
855
|
} catch (e) {
|
|
745
856
|
logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
|
|
746
857
|
throw e;
|
|
747
858
|
}
|
|
748
859
|
|
|
749
860
|
try {
|
|
750
|
-
const res = await this.
|
|
861
|
+
const res = await this.oldChannel?.sendToQueue(queue,
|
|
751
862
|
Buffer.from(JSON.stringify(content)),
|
|
752
863
|
RabbitMq.getPublishOptions(customHeaders));
|
|
753
864
|
debug(`rabbit: sending to queue ${queue}`, { res });
|
|
754
865
|
return res;
|
|
755
866
|
} catch (e) {
|
|
756
|
-
|
|
867
|
+
const isConnected = await this.isConnected();
|
|
868
|
+
logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
|
|
757
869
|
throw e;
|
|
758
870
|
}
|
|
759
871
|
}
|
|
760
872
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
873
|
+
// TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
|
|
874
|
+
async isConnected() : Promise<boolean> {
|
|
875
|
+
const connection = await this.getConnectionOld();
|
|
876
|
+
const isConnected = connection.isConnected();
|
|
877
|
+
if (!isConnected) {
|
|
878
|
+
logger.error('rabbit: isConnected - false');
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
const channel: any = await this.assertChannelOld();
|
|
882
|
+
try {
|
|
883
|
+
await Promise.all([
|
|
884
|
+
channel.waitForConnect(),
|
|
885
|
+
...this.oldConsumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
|
|
886
|
+
]);
|
|
887
|
+
} catch (e) {
|
|
888
|
+
logger.error('rabbit: isConnected - false');
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
logger.info('rabbit: isConnected - true');
|
|
892
|
+
return true;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
async gracefulShutdown(signal: string) : Promise<void> {
|
|
896
|
+
// TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
|
|
897
|
+
const tagsNumber = this.consumersTags.length + this.oldConsumersTags.length;
|
|
898
|
+
logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
|
|
899
|
+
const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
900
|
+
const cancelTagPromisesOld = this.oldConsumersTags.map(([channel, tag]) => channel.cancel(tag));
|
|
901
|
+
// Clean the array to avoid race
|
|
902
|
+
this.consumersTags = [];
|
|
903
|
+
this.oldConsumersTags = [];
|
|
904
|
+
const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
|
|
905
|
+
const rejected = results.filter((p) => p.status === 'rejected');
|
|
906
|
+
if (rejected.length > 0) {
|
|
907
|
+
logger.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
|
|
908
|
+
} else {
|
|
909
|
+
logger.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
|
|
910
|
+
}
|
|
911
|
+
}
|
|
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;
|
|
771
936
|
}
|
|
772
|
-
logger.info('rabbit: isConnected - true', { connectionPurpose });
|
|
773
937
|
|
|
774
|
-
|
|
775
|
-
|
|
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) {
|
|
776
949
|
try {
|
|
777
950
|
await Promise.all([
|
|
778
|
-
|
|
779
|
-
|
|
951
|
+
createOrSetRabbitTrace(trace, userId),
|
|
952
|
+
createOrSetRabbitTrace(outbreakTrace, userId),
|
|
780
953
|
]);
|
|
781
954
|
} catch (e) {
|
|
782
|
-
logger.error('rabbit:
|
|
783
|
-
return
|
|
955
|
+
logger.error('rabbit: failed to setRabbitTrace', { userId, e });
|
|
956
|
+
return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
|
|
784
957
|
}
|
|
785
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;
|
|
786
1111
|
} else {
|
|
787
|
-
logger.
|
|
1112
|
+
logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
|
|
788
1113
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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
|
+
});
|
|
793
1124
|
}
|
|
794
1125
|
|
|
795
|
-
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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];
|
|
807
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];
|
|
808
1243
|
}
|
|
809
1244
|
}
|
|
810
1245
|
|