@autofleet/rabbit 2.5.1 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { Options, ConsumeMessage } from 'amqplib';
2
+ import { ConfirmChannel, Options, ConsumeMessage } from 'amqplib';
3
3
  import { AmqpConnectionManager, ChannelWrapper } from 'amqp-connection-manager';
4
4
  import { EventEmitter } from 'events';
5
5
  import { RedisConfig } from './redis';
@@ -62,6 +62,8 @@ declare class RabbitMq implements IAfRabbitMq {
62
62
  options: AfRabbitOptions | undefined;
63
63
  redisClient: any;
64
64
  redisLock?: RedisLockType;
65
+ /** Array of consumers tags used for canceling consumption */
66
+ consumersTags: Array<[ConfirmChannel, string]>;
65
67
  constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig);
66
68
  private shouldConsumeMessageByTimestamp;
67
69
  ack: (channel: ChannelWrapper, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp?: boolean, releaseLock?: null) => (userMsg: ConsumeMessage) => Promise<any>;
@@ -81,5 +83,6 @@ declare class RabbitMq implements IAfRabbitMq {
81
83
  publish(exchange: string, content: any, customHeaders?: any): Promise<boolean>;
82
84
  sendToQueue(queue: string, content: any, options?: any, customHeaders?: any): Promise<boolean>;
83
85
  isConnected(): Promise<boolean>;
86
+ gracefulShutdown(signal: string): Promise<void>;
84
87
  }
85
88
  export default RabbitMq;
package/dist/index.js CHANGED
@@ -105,6 +105,14 @@ class RabbitMq {
105
105
  if (this.redisClient) {
106
106
  this.redisLock = util_1.promisify(redis_lock_1.default(this.redisClient));
107
107
  }
108
+ this.consumersTags = [];
109
+ logger_1.default.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
110
+ process.on('SIGTERM', async () => {
111
+ await this.gracefulShutdown('SIGTERM');
112
+ });
113
+ process.on('SIGINT', async () => {
114
+ await this.gracefulShutdown('SIGINT');
115
+ });
108
116
  }
109
117
  static parseMsg(msg) {
110
118
  let { content } = msg;
@@ -244,37 +252,42 @@ class RabbitMq {
244
252
  if (!this.redisLock) {
245
253
  throw new Error('Usage of consumeWithLock requires RedisInstance');
246
254
  }
247
- logger_1.default.info(`Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
255
+ logger_1.default.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
248
256
  }
249
257
  const channel = await this.getNewChannel();
250
258
  return channel.addSetup(async (c) => {
251
259
  await c.assertQueue(queue);
252
260
  await c.prefetch(limit, true);
253
- return Promise.all([
254
- c.consume(queue, async (msg) => {
255
- if (!msg) {
256
- return null;
257
- }
258
- const traceId = msg.properties.headers[TRACING_HEADER];
259
- if (traceId) {
260
- const trace = outbreak_1.newTrace(outbreak_1.traceTypes.RABBIT);
261
- trace?.context?.set(TRACING_HEADER, traceId);
262
- }
263
- const parsedMessage = RabbitMq.parseMsg(msg);
264
- const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
265
- const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
266
- if (!shouldConsume) {
267
- await this.unlockRedisIfNeeded(releaseLock);
268
- return this.ack(channel, msg)(msg);
269
- }
270
- try {
271
- await callback(parsedMessage, this.ack(channel, msg, true, releaseLock), this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock));
272
- }
273
- catch (e) {
274
- await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
275
- }
276
- }),
277
- ]);
261
+ const { consumerTag } = await c.consume(queue, async (msg) => {
262
+ if (!msg) {
263
+ return null;
264
+ }
265
+ const traceId = msg.properties.headers[TRACING_HEADER];
266
+ if (traceId) {
267
+ const trace = outbreak_1.newTrace(outbreak_1.traceTypes.RABBIT);
268
+ trace?.context?.set(TRACING_HEADER, traceId);
269
+ }
270
+ const parsedMessage = RabbitMq.parseMsg(msg);
271
+ const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
272
+ const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
273
+ if (!shouldConsume) {
274
+ await this.unlockRedisIfNeeded(releaseLock);
275
+ return this.ack(channel, msg)(msg);
276
+ }
277
+ try {
278
+ await callback(parsedMessage, this.ack(channel, msg, true, releaseLock), this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock));
279
+ }
280
+ catch (e) {
281
+ await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
282
+ }
283
+ });
284
+ if (!consumerTag) {
285
+ logger_1.default.error(`rabbit: failed to consume from queue ${queue}`);
286
+ }
287
+ else {
288
+ logger_1.default.info(`rabbit: adding tag ${consumerTag} to the array.`);
289
+ this.consumersTags.push([c, consumerTag]);
290
+ }
278
291
  });
279
292
  }
280
293
  async consumeFromExchange(queue, exchange, callback, options) {
@@ -314,5 +327,20 @@ class RabbitMq {
314
327
  const connection = await this.getConnection();
315
328
  return connection.isConnected();
316
329
  }
330
+ async gracefulShutdown(signal) {
331
+ const tagsNumber = this.consumersTags.length;
332
+ logger_1.default.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
333
+ const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
334
+ // Clean the array to avoid race
335
+ this.consumersTags = [];
336
+ const results = await Promise.allSettled(cancelTagPromises);
337
+ const rejected = results.filter((p) => p.status === 'rejected');
338
+ if (rejected.length > 0) {
339
+ logger_1.default.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
340
+ }
341
+ else {
342
+ logger_1.default.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
343
+ }
344
+ }
317
345
  }
318
346
  exports.default = RabbitMq;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/rabbit",
3
- "version": "2.5.1",
3
+ "version": "2.5.2",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
package/src/index.ts CHANGED
@@ -66,7 +66,7 @@ const defaultOptions = {
66
66
  useConsumeWithLock: DEFAULT_USE_CONSUME_WITH_LOCK,
67
67
  };
68
68
 
69
- const assertExchangeFanout = async (c: any, exchangeName: string) => c.assertExchange(exchangeName, 'fanout');
69
+ const assertExchangeFanout = async (c: ChannelWrapper | ConfirmChannel, exchangeName: string) => c.assertExchange(exchangeName, 'fanout');
70
70
 
71
71
  const connectionCreatedConst = 'connectionCreated';
72
72
 
@@ -140,6 +140,9 @@ class RabbitMq implements IAfRabbitMq {
140
140
 
141
141
  redisLock?: RedisLockType;
142
142
 
143
+ /** Array of consumers tags used for canceling consumption */
144
+ consumersTags: Array<[ConfirmChannel, string]>;
145
+
143
146
  constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig) {
144
147
  this.em = new EventEmitter();
145
148
  this.channel = null;
@@ -152,6 +155,14 @@ class RabbitMq implements IAfRabbitMq {
152
155
  if (this.redisClient) {
153
156
  this.redisLock = promisify(RedisLock(this.redisClient)) as RedisLockType;
154
157
  }
158
+ this.consumersTags = [];
159
+ logger.info(`rabbit: [gracefully-shutdown] adding gracefully shutdown for process.pid ${process.pid}`);
160
+ process.on('SIGTERM', async () => {
161
+ await this.gracefulShutdown('SIGTERM');
162
+ });
163
+ process.on('SIGINT', async () => {
164
+ await this.gracefulShutdown('SIGINT');
165
+ });
155
166
  }
156
167
 
157
168
  private shouldConsumeMessageByTimestamp = async (msg: ConsumeMessageOrNull) => {
@@ -349,45 +360,49 @@ class RabbitMq implements IAfRabbitMq {
349
360
  if (!this.redisLock) {
350
361
  throw new Error('Usage of consumeWithLock requires RedisInstance');
351
362
  }
352
- logger.info(`Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
363
+ logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
353
364
  }
354
365
  const channel: ChannelWrapper = await this.getNewChannel();
355
366
  return channel.addSetup(async (c: ConfirmChannel) => {
356
367
  await c.assertQueue(queue);
357
368
  await c.prefetch(limit, true);
358
- return Promise.all([
359
- c.consume(
360
- queue,
361
- async (msg: ConsumeMessageOrNull) => {
362
- if (!msg) {
363
- return null;
364
- }
365
-
366
- const traceId = msg.properties.headers[TRACING_HEADER];
367
- if (traceId) {
368
- const trace = newTrace(traceTypes.RABBIT);
369
- (trace as any)?.context?.set(TRACING_HEADER, traceId);
370
- }
371
-
372
- const parsedMessage = RabbitMq.parseMsg(msg);
373
- const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
374
- const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
375
- if (!shouldConsume) {
376
- await this.unlockRedisIfNeeded(releaseLock);
377
- return this.ack(channel, msg)(msg);
378
- }
379
- try {
380
- await callback(
381
- parsedMessage,
382
- this.ack(channel, msg, true, releaseLock),
383
- this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock),
384
- );
385
- } catch (e) {
386
- await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
387
- }
388
- },
389
- ),
390
- ]);
369
+ const { consumerTag } = await c.consume(
370
+ queue,
371
+ async (msg: ConsumeMessageOrNull) => {
372
+ if (!msg) {
373
+ return null;
374
+ }
375
+
376
+ const traceId = msg.properties.headers[TRACING_HEADER];
377
+ if (traceId) {
378
+ const trace = newTrace(traceTypes.RABBIT);
379
+ (trace as any)?.context?.set(TRACING_HEADER, traceId);
380
+ }
381
+
382
+ const parsedMessage = RabbitMq.parseMsg(msg);
383
+ const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
384
+ const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
385
+ if (!shouldConsume) {
386
+ await this.unlockRedisIfNeeded(releaseLock);
387
+ return this.ack(channel, msg)(msg);
388
+ }
389
+ try {
390
+ await callback(
391
+ parsedMessage,
392
+ this.ack(channel, msg, true, releaseLock),
393
+ this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock),
394
+ );
395
+ } catch (e) {
396
+ await this.nack(channel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
397
+ }
398
+ },
399
+ );
400
+ if (!consumerTag) {
401
+ logger.error(`rabbit: failed to consume from queue ${queue}`);
402
+ } else {
403
+ logger.info(`rabbit: adding tag ${consumerTag} to the array.`);
404
+ this.consumersTags.push([c, consumerTag]);
405
+ }
391
406
  });
392
407
  }
393
408
 
@@ -440,6 +455,21 @@ class RabbitMq implements IAfRabbitMq {
440
455
  const connection = await this.getConnection();
441
456
  return connection.isConnected();
442
457
  }
458
+
459
+ async gracefulShutdown(signal: string) : Promise<void> {
460
+ const tagsNumber = this.consumersTags.length;
461
+ logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
462
+ const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
463
+ // Clean the array to avoid race
464
+ this.consumersTags = [];
465
+ const results = await Promise.allSettled(cancelTagPromises);
466
+ const rejected = results.filter((p) => p.status === 'rejected');
467
+ if (rejected.length > 0) {
468
+ logger.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
469
+ } else {
470
+ logger.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
471
+ }
472
+ }
443
473
  }
444
474
 
445
475
  export default RabbitMq;