feat(RabbitMQ Trigger Node): Make message acknowledgement and parallel processing configurable (#3385)

* feat(RabbitMQ Trigger Node): Make message acknowledgement and concurrent
processing configurable

*  Make sure that messages do not get executed multiple times

* 👕 Fix lint issue

* 🐛 Fix issue that for manual executions in "own" mode messages got
know acknowledged

*  Increment count now that console.log got removed

*  Improvements

*  Fix default value

*  Improve display name
This commit is contained in:
Jan Oberhauser
2022-05-30 12:16:44 +02:00
committed by GitHub
parent d7c6833dc3
commit b851289001
9 changed files with 336 additions and 72 deletions

View File

@@ -4,9 +4,15 @@ import {
ITriggerFunctions,
} from 'n8n-workflow';
const amqplib = require('amqplib');
import * as amqplib from 'amqplib';
export async function rabbitmqConnect(this: IExecuteFunctions | ITriggerFunctions, options: IDataObject): Promise<any> { // tslint:disable-line:no-any
declare module 'amqplib' {
interface Channel {
connection: amqplib.Connection;
}
}
export async function rabbitmqConnect(this: IExecuteFunctions | ITriggerFunctions, options: IDataObject): Promise<amqplib.Channel> {
const credentials = await this.getCredentials('rabbitmq');
const credentialKeys = [
@@ -44,7 +50,7 @@ export async function rabbitmqConnect(this: IExecuteFunctions | ITriggerFunction
reject(error);
});
const channel = await connection.createChannel().catch(console.warn);
const channel = await connection.createChannel().catch(console.warn) as amqplib.Channel;
if (options.arguments && ((options.arguments as IDataObject).argument! as IDataObject[]).length) {
const additionalArguments: IDataObject = {};
@@ -54,7 +60,6 @@ export async function rabbitmqConnect(this: IExecuteFunctions | ITriggerFunction
options.arguments = additionalArguments;
}
resolve(channel);
} catch (error) {
reject(error);
@@ -62,7 +67,7 @@ export async function rabbitmqConnect(this: IExecuteFunctions | ITriggerFunction
});
}
export async function rabbitmqConnectQueue(this: IExecuteFunctions | ITriggerFunctions, queue: string, options: IDataObject): Promise<any> { // tslint:disable-line:no-any
export async function rabbitmqConnectQueue(this: IExecuteFunctions | ITriggerFunctions, queue: string, options: IDataObject): Promise<amqplib.Channel> {
const channel = await rabbitmqConnect.call(this, options);
return new Promise(async (resolve, reject) => {
@@ -75,7 +80,7 @@ export async function rabbitmqConnectQueue(this: IExecuteFunctions | ITriggerFun
});
}
export async function rabbitmqConnectExchange(this: IExecuteFunctions | ITriggerFunctions, exchange: string, type: string, options: IDataObject): Promise<any> { // tslint:disable-line:no-any
export async function rabbitmqConnectExchange(this: IExecuteFunctions | ITriggerFunctions, exchange: string, type: string, options: IDataObject): Promise<amqplib.Channel> {
const channel = await rabbitmqConnect.call(this, options);
return new Promise(async (resolve, reject) => {
@@ -87,3 +92,53 @@ export async function rabbitmqConnectExchange(this: IExecuteFunctions | ITrigger
}
});
}
export class MessageTracker {
messages: number[] = [];
isClosing = false;
received(message: amqplib.ConsumeMessage) {
this.messages.push(message.fields.deliveryTag);
}
answered(message: amqplib.ConsumeMessage) {
if (this.messages.length === 0) {
return;
}
const index = this.messages.findIndex(value => value !== message.fields.deliveryTag);
this.messages.splice(index);
}
unansweredMessages() {
return this.messages.length;
}
async closeChannel(channel: amqplib.Channel, consumerTag: string) {
if (this.isClosing) {
return;
}
this.isClosing = true;
// Do not accept any new messages
await channel.cancel(consumerTag);
let count = 0;
let unansweredMessages = this.unansweredMessages();
// Give currently executing messages max. 5 minutes to finish before
// the channel gets closed. If we would not do that, it would not be possible
// to acknowledge messages anymore for which the executions were already running
// when for example a new version of the workflow got saved. That would lead to
// them getting delivered and processed again.
while (unansweredMessages !== 0 && count++ <= 300) {
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
unansweredMessages = this.unansweredMessages();
}
await channel.close();
await channel.connection.close();
}
}