@autofleet/rabbit 2.5.0 → 2.5.2-beta.0
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 +52 -22
- package/package.json +1 -1
- package/src/index.ts +61 -29
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,35 +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
|
-
|
|
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) {
|
|
259
267
|
const trace = outbreak_1.newTrace(outbreak_1.traceTypes.RABBIT);
|
|
260
268
|
trace?.context?.set(TRACING_HEADER, traceId);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
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
|
+
}
|
|
276
291
|
});
|
|
277
292
|
}
|
|
278
293
|
async consumeFromExchange(queue, exchange, callback, options) {
|
|
@@ -312,5 +327,20 @@ class RabbitMq {
|
|
|
312
327
|
const connection = await this.getConnection();
|
|
313
328
|
return connection.isConnected();
|
|
314
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
|
+
}
|
|
315
345
|
}
|
|
316
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,43 +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
|
-
}
|
|
369
|
+
const { consumerTag } = await c.consume(
|
|
370
|
+
queue,
|
|
371
|
+
async (msg: ConsumeMessageOrNull) => {
|
|
372
|
+
if (!msg) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
365
375
|
|
|
366
|
-
|
|
376
|
+
const traceId = msg.properties.headers[TRACING_HEADER];
|
|
377
|
+
if (traceId) {
|
|
367
378
|
const trace = newTrace(traceTypes.RABBIT);
|
|
368
379
|
(trace as any)?.context?.set(TRACING_HEADER, traceId);
|
|
380
|
+
}
|
|
369
381
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
+
}
|
|
389
406
|
});
|
|
390
407
|
}
|
|
391
408
|
|
|
@@ -438,6 +455,21 @@ class RabbitMq implements IAfRabbitMq {
|
|
|
438
455
|
const connection = await this.getConnection();
|
|
439
456
|
return connection.isConnected();
|
|
440
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
|
+
}
|
|
441
473
|
}
|
|
442
474
|
|
|
443
475
|
export default RabbitMq;
|