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