1
1
mirror of https://github.com/n8n-io/n8n.git synced 2024-10-06 09:37:36 +03:00

feat(core): Support bidirectional communication between specific mains and specific workers (#10377)

This commit is contained in:
Iván Ovejero 2024-08-20 12:32:31 +02:00 committed by GitHub
parent 51f3e84dff
commit d0fc9dee0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 57 additions and 25 deletions

View File

@ -239,7 +239,10 @@ export class Start extends BaseCommand {
await orchestrationService.init();
await Container.get(OrchestrationHandlerMainService).init();
await Container.get(OrchestrationHandlerMainService).initWithOptions({
queueModeId: this.queueModeId,
redisPublisher: Container.get(OrchestrationService).redisPublisher,
});
if (!orchestrationService.isMultiMainSetupEnabled) return;

View File

@ -19,6 +19,7 @@ import { Push } from '@/push';
import { ActiveWorkflowManager } from '@/ActiveWorkflowManager';
import { mockInstance } from '@test/mocking';
import { RedisClientService } from '@/services/redis/redis-client.service';
import type { MainResponseReceivedHandlerOptions } from '../orchestration/main/types';
const instanceSettings = Container.get(InstanceSettings);
const redisClientService = mockInstance(RedisClientService);
@ -96,8 +97,9 @@ describe('Orchestration Service', () => {
test('should handle worker responses', async () => {
const response = await handleWorkerResponseMessageMain(
JSON.stringify(workerRestartEventBusResponse),
mock<MainResponseReceivedHandlerOptions>(),
);
expect(response.command).toEqual('restartEventBus');
expect(response?.command).toEqual('restartEventBus');
});
test('should handle command messages from others', async () => {

View File

@ -2,6 +2,7 @@ import Container from 'typedi';
import { RedisService } from './redis.service';
import type { RedisServicePubSubSubscriber } from './redis/RedisServicePubSubSubscriber';
import type { WorkerCommandReceivedHandlerOptions } from './orchestration/worker/types';
import type { MainResponseReceivedHandlerOptions } from './orchestration/main/types';
export abstract class OrchestrationHandlerService {
protected initialized = false;
@ -19,7 +20,9 @@ export abstract class OrchestrationHandlerService {
this.initialized = true;
}
async initWithOptions(options: WorkerCommandReceivedHandlerOptions) {
async initWithOptions(
options: WorkerCommandReceivedHandlerOptions | MainResponseReceivedHandlerOptions,
) {
await this.initSubscriber(options);
this.initialized = true;
}
@ -29,5 +32,7 @@ export abstract class OrchestrationHandlerService {
this.initialized = false;
}
protected abstract initSubscriber(options?: WorkerCommandReceivedHandlerOptions): Promise<void>;
protected abstract initSubscriber(
options?: WorkerCommandReceivedHandlerOptions | MainResponseReceivedHandlerOptions,
): Promise<void>;
}

View File

@ -3,25 +3,40 @@ import Container from 'typedi';
import { Logger } from '@/Logger';
import { Push } from '../../../push';
import type { RedisServiceWorkerResponseObject } from '../../redis/RedisServiceCommands';
import { WORKER_RESPONSE_REDIS_CHANNEL } from '@/services/redis/RedisConstants';
import type { MainResponseReceivedHandlerOptions } from './types';
export async function handleWorkerResponseMessageMain(messageString: string) {
const workerResponse = jsonParse<RedisServiceWorkerResponseObject>(messageString);
if (workerResponse) {
switch (workerResponse.command) {
case 'getStatus':
const push = Container.get(Push);
push.broadcast('sendWorkerStatusMessage', {
workerId: workerResponse.workerId,
status: workerResponse.payload,
});
break;
case 'getId':
break;
default:
Container.get(Logger).debug(
`Received worker response ${workerResponse.command} from ${workerResponse.workerId}`,
);
}
export async function handleWorkerResponseMessageMain(
messageString: string,
options: MainResponseReceivedHandlerOptions,
) {
const workerResponse = jsonParse<RedisServiceWorkerResponseObject | null>(messageString, {
fallbackValue: null,
});
if (!workerResponse) {
Container.get(Logger).debug(
`Received invalid message via channel ${WORKER_RESPONSE_REDIS_CHANNEL}: "${messageString}"`,
);
return;
}
if (workerResponse.targets && !workerResponse.targets.includes(options.queueModeId)) return;
switch (workerResponse.command) {
case 'getStatus':
Container.get(Push).broadcast('sendWorkerStatusMessage', {
workerId: workerResponse.workerId,
status: workerResponse.payload,
});
break;
case 'getId':
break;
default:
Container.get(Logger).debug(
`Received worker response ${workerResponse.command} from ${workerResponse.workerId}`,
);
}
return workerResponse;
}

View File

@ -3,10 +3,11 @@ import { COMMAND_REDIS_CHANNEL, WORKER_RESPONSE_REDIS_CHANNEL } from '../../redi
import { handleWorkerResponseMessageMain } from './handleWorkerResponseMessageMain';
import { handleCommandMessageMain } from './handleCommandMessageMain';
import { OrchestrationHandlerService } from '../../orchestration.handler.base.service';
import type { MainResponseReceivedHandlerOptions } from './types';
@Service()
export class OrchestrationHandlerMainService extends OrchestrationHandlerService {
async initSubscriber() {
async initSubscriber(options: MainResponseReceivedHandlerOptions) {
this.redisSubscriber = await this.redisService.getPubSubSubscriber();
await this.redisSubscriber.subscribeToCommandChannel();
@ -16,7 +17,7 @@ export class OrchestrationHandlerMainService extends OrchestrationHandlerService
'OrchestrationMessageReceiver',
async (channel: string, messageString: string) => {
if (channel === WORKER_RESPONSE_REDIS_CHANNEL) {
await handleWorkerResponseMessageMain(messageString);
await handleWorkerResponseMessageMain(messageString, options);
} else if (channel === COMMAND_REDIS_CHANNEL) {
await handleCommandMessageMain(messageString);
}

View File

@ -0,0 +1,6 @@
import type { RedisServicePubSubPublisher } from '@/services/redis/RedisServicePubSubPublisher';
export type MainResponseReceivedHandlerOptions = {
queueModeId: string;
redisPublisher: RedisServicePubSubPublisher;
};

View File

@ -94,7 +94,7 @@ export type RedisServiceWorkerResponseObject = {
workflowId: string;
};
}
);
) & { targets?: string[] };
export type RedisServiceCommandObject = {
targets?: string[];