@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 +4 -1
- package/dist/index.js +54 -26
- package/package.json +1 -1
- package/src/index.ts +65 -35
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
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:
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
|
|
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;
|