mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-27 05:59:59 +00:00
perf(core): Load node types on demand on runners (no-changelog) (#11559)
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import type { NeededNodeType } from '@n8n/task-runner';
|
||||
import type { Dirent } from 'fs';
|
||||
import { readdir } from 'fs/promises';
|
||||
import { loadClassInIsolation } from 'n8n-core';
|
||||
@@ -149,4 +150,22 @@ export class NodeTypes implements INodeTypes {
|
||||
dirent.name.toLowerCase().startsWith('v')
|
||||
);
|
||||
}
|
||||
|
||||
getNodeTypeDescriptions(nodeTypes: NeededNodeType[]): INodeTypeDescription[] {
|
||||
return nodeTypes.map(({ name: nodeTypeName, version: nodeTypeVersion }) => {
|
||||
const nodeType = this.getNode(nodeTypeName);
|
||||
|
||||
if (!nodeType) throw new ApplicationError(`Unknown node type: ${nodeTypeName}`);
|
||||
|
||||
const { description } = NodeHelpers.getVersionedNodeType(nodeType.type, nodeTypeVersion);
|
||||
|
||||
const descriptionCopy = { ...description };
|
||||
|
||||
descriptionCopy.name = descriptionCopy.name.startsWith('n8n-nodes')
|
||||
? descriptionCopy.name
|
||||
: `n8n-nodes-base.${descriptionCopy.name}`; // nodes-base nodes are unprefixed
|
||||
|
||||
return descriptionCopy;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { RunnerMessage, TaskResultData } from '@n8n/task-runner';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { INodeTypeBaseDescription } from 'n8n-workflow';
|
||||
|
||||
import { TaskRejectError } from '../errors';
|
||||
import { TaskBroker } from '../task-broker.service';
|
||||
@@ -11,7 +12,7 @@ describe('TaskBroker', () => {
|
||||
let taskBroker: TaskBroker;
|
||||
|
||||
beforeEach(() => {
|
||||
taskBroker = new TaskBroker(mock(), mock());
|
||||
taskBroker = new TaskBroker(mock());
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
@@ -76,13 +77,6 @@ describe('TaskBroker', () => {
|
||||
const messageCallback = jest.fn();
|
||||
|
||||
taskBroker.registerRunner(runner, messageCallback);
|
||||
|
||||
expect(messageCallback).toBeCalledWith({
|
||||
type: 'broker:nodetypes',
|
||||
// We're mocking the node types service, so this will
|
||||
// be undefined.
|
||||
nodeType: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -560,5 +554,68 @@ describe('TaskBroker', () => {
|
||||
params: rpcParams,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle `runner:nodetypesrequest` message', async () => {
|
||||
const runnerId = 'runner1';
|
||||
const taskId = 'task1';
|
||||
const requesterId = 'requester1';
|
||||
const requestId = 'request1';
|
||||
const requestParams = [
|
||||
{
|
||||
name: 'n8n-nodes-base.someNode',
|
||||
version: 1,
|
||||
},
|
||||
];
|
||||
|
||||
const message: RunnerMessage.ToBroker.NodeTypesRequest = {
|
||||
type: 'runner:nodetypesrequest',
|
||||
taskId,
|
||||
requestId,
|
||||
requestParams,
|
||||
};
|
||||
|
||||
const requesterMessageCallback = jest.fn();
|
||||
|
||||
taskBroker.registerRunner(mock<TaskRunner>({ id: runnerId }), jest.fn());
|
||||
taskBroker.setTasks({
|
||||
[taskId]: { id: taskId, runnerId, requesterId, taskType: 'test' },
|
||||
});
|
||||
taskBroker.registerRequester(requesterId, requesterMessageCallback);
|
||||
|
||||
await taskBroker.onRunnerMessage(runnerId, message);
|
||||
|
||||
expect(requesterMessageCallback).toHaveBeenCalledWith({
|
||||
type: 'broker:nodetypesrequest',
|
||||
taskId,
|
||||
requestId,
|
||||
requestParams,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('onRequesterMessage', () => {
|
||||
it('should handle `requester:nodetypesresponse` message', async () => {
|
||||
const runnerId = 'runner1';
|
||||
const taskId = 'task1';
|
||||
const requesterId = 'requester1';
|
||||
const requestId = 'request1';
|
||||
const nodeTypes = [mock<INodeTypeBaseDescription>(), mock<INodeTypeBaseDescription>()];
|
||||
|
||||
const runnerMessageCallback = jest.fn();
|
||||
|
||||
taskBroker.registerRunner(mock<TaskRunner>({ id: runnerId }), runnerMessageCallback);
|
||||
taskBroker.setTasks({
|
||||
[taskId]: { id: taskId, runnerId, requesterId, taskType: 'test' },
|
||||
});
|
||||
|
||||
await taskBroker.handleRequesterNodeTypesResponse(taskId, requestId, nodeTypes);
|
||||
|
||||
expect(runnerMessageCallback).toHaveBeenCalledWith({
|
||||
type: 'broker:nodetypes',
|
||||
taskId,
|
||||
requestId,
|
||||
nodeTypes,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,7 +70,7 @@ export class TaskRunnerWsServer {
|
||||
this.sendMessage.bind(this, id) as MessageCallback,
|
||||
);
|
||||
|
||||
this.logger.info(`Runner "${message.name}"(${id}) has been registered`);
|
||||
this.logger.info(`Runner "${message.name}" (${id}) has been registered`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { ApplicationError } from 'n8n-workflow';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { Service } from 'typedi';
|
||||
|
||||
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
|
||||
import { Logger } from '@/logging/logger.service';
|
||||
|
||||
import { TaskRejectError } from './errors';
|
||||
@@ -79,19 +78,7 @@ export class TaskBroker {
|
||||
|
||||
private pendingTaskRequests: TaskRequest[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly loadNodesAndCredentials: LoadNodesAndCredentials,
|
||||
) {
|
||||
this.loadNodesAndCredentials.addPostProcessor(this.updateNodeTypes);
|
||||
}
|
||||
|
||||
updateNodeTypes = async () => {
|
||||
await this.messageAllRunners({
|
||||
type: 'broker:nodetypes',
|
||||
nodeTypes: this.loadNodesAndCredentials.types.nodes,
|
||||
});
|
||||
};
|
||||
constructor(private readonly logger: Logger) {}
|
||||
|
||||
expireTasks() {
|
||||
const now = process.hrtime.bigint();
|
||||
@@ -105,10 +92,6 @@ export class TaskBroker {
|
||||
registerRunner(runner: TaskRunner, messageCallback: MessageCallback) {
|
||||
this.knownRunners.set(runner.id, { runner, messageCallback });
|
||||
void this.knownRunners.get(runner.id)!.messageCallback({ type: 'broker:runnerregistered' });
|
||||
void this.knownRunners.get(runner.id)!.messageCallback({
|
||||
type: 'broker:nodetypes',
|
||||
nodeTypes: this.loadNodesAndCredentials.types.nodes,
|
||||
});
|
||||
}
|
||||
|
||||
deregisterRunner(runnerId: string, error: Error) {
|
||||
@@ -145,14 +128,6 @@ export class TaskBroker {
|
||||
await this.knownRunners.get(runnerId)?.messageCallback(message);
|
||||
}
|
||||
|
||||
private async messageAllRunners(message: BrokerMessage.ToRunner.All) {
|
||||
await Promise.allSettled(
|
||||
[...this.knownRunners.values()].map(async (runner) => {
|
||||
await runner.messageCallback(message);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async messageRequester(requesterId: string, message: BrokerMessage.ToRequester.All) {
|
||||
await this.requesters.get(requesterId)?.(message);
|
||||
}
|
||||
@@ -187,7 +162,9 @@ export class TaskBroker {
|
||||
case 'runner:taskdatarequest':
|
||||
await this.handleDataRequest(message.taskId, message.requestId, message.requestParams);
|
||||
break;
|
||||
|
||||
case 'runner:nodetypesrequest':
|
||||
await this.handleNodeTypesRequest(message.taskId, message.requestId, message.requestParams);
|
||||
break;
|
||||
case 'runner:rpc':
|
||||
await this.handleRpcRequest(message.taskId, message.callId, message.name, message.params);
|
||||
break;
|
||||
@@ -249,6 +226,23 @@ export class TaskBroker {
|
||||
});
|
||||
}
|
||||
|
||||
async handleNodeTypesRequest(
|
||||
taskId: Task['id'],
|
||||
requestId: RunnerMessage.ToBroker.NodeTypesRequest['requestId'],
|
||||
requestParams: RunnerMessage.ToBroker.NodeTypesRequest['requestParams'],
|
||||
) {
|
||||
const task = this.tasks.get(taskId);
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
await this.messageRequester(task.requesterId, {
|
||||
type: 'broker:nodetypesrequest',
|
||||
taskId,
|
||||
requestId,
|
||||
requestParams,
|
||||
});
|
||||
}
|
||||
|
||||
async handleResponse(
|
||||
taskId: Task['id'],
|
||||
requestId: RunnerMessage.ToBroker.TaskDataRequest['requestId'],
|
||||
@@ -284,6 +278,13 @@ export class TaskBroker {
|
||||
case 'requester:taskdataresponse':
|
||||
await this.handleRequesterDataResponse(message.taskId, message.requestId, message.data);
|
||||
break;
|
||||
case 'requester:nodetypesresponse':
|
||||
await this.handleRequesterNodeTypesResponse(
|
||||
message.taskId,
|
||||
message.requestId,
|
||||
message.nodeTypes,
|
||||
);
|
||||
break;
|
||||
case 'requester:rpcresponse':
|
||||
await this.handleRequesterRpcResponse(
|
||||
message.taskId,
|
||||
@@ -322,6 +323,21 @@ export class TaskBroker {
|
||||
});
|
||||
}
|
||||
|
||||
async handleRequesterNodeTypesResponse(
|
||||
taskId: Task['id'],
|
||||
requestId: RequesterMessage.ToBroker.NodeTypesResponse['requestId'],
|
||||
nodeTypes: RequesterMessage.ToBroker.NodeTypesResponse['nodeTypes'],
|
||||
) {
|
||||
const runner = await this.getRunnerOrFailTask(taskId);
|
||||
|
||||
await this.messageRunner(runner.id, {
|
||||
type: 'broker:nodetypes',
|
||||
taskId,
|
||||
requestId,
|
||||
nodeTypes,
|
||||
});
|
||||
}
|
||||
|
||||
handleRequesterAccept(
|
||||
taskId: Task['id'],
|
||||
settings: RequesterMessage.ToBroker.TaskSettings['settings'],
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import type { RequesterMessage } from '@n8n/task-runner';
|
||||
import Container from 'typedi';
|
||||
import Container, { Service } from 'typedi';
|
||||
|
||||
import { NodeTypes } from '@/node-types';
|
||||
|
||||
import { TaskManager } from './task-manager';
|
||||
import type { RequesterMessageCallback } from '../task-broker.service';
|
||||
import { TaskBroker } from '../task-broker.service';
|
||||
|
||||
@Service()
|
||||
export class LocalTaskManager extends TaskManager {
|
||||
taskBroker: TaskBroker;
|
||||
|
||||
id: string = 'single-main';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(nodeTypes: NodeTypes) {
|
||||
super(nodeTypes);
|
||||
this.registerRequester();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ import type {
|
||||
} from 'n8n-workflow';
|
||||
import { createResultOk, createResultError } from 'n8n-workflow';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { Service } from 'typedi';
|
||||
|
||||
import { NodeTypes } from '@/node-types';
|
||||
|
||||
import { DataRequestResponseBuilder } from './data-request-response-builder';
|
||||
|
||||
@@ -43,7 +46,8 @@ interface ExecuteFunctionObject {
|
||||
[name: string]: ((...args: unknown[]) => unknown) | ExecuteFunctionObject;
|
||||
}
|
||||
|
||||
export class TaskManager {
|
||||
@Service()
|
||||
export abstract class TaskManager {
|
||||
requestAcceptRejects: Map<string, { accept: RequestAccept; reject: RequestReject }> = new Map();
|
||||
|
||||
taskAcceptRejects: Map<string, { accept: TaskAccept; reject: TaskReject }> = new Map();
|
||||
@@ -52,6 +56,8 @@ export class TaskManager {
|
||||
|
||||
tasks: Map<string, Task> = new Map();
|
||||
|
||||
constructor(private readonly nodeTypes: NodeTypes) {}
|
||||
|
||||
async startTask<TData, TError>(
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
taskType: string,
|
||||
@@ -173,6 +179,9 @@ export class TaskManager {
|
||||
case 'broker:taskdatarequest':
|
||||
this.sendTaskData(message.taskId, message.requestId, message.requestParams);
|
||||
break;
|
||||
case 'broker:nodetypesrequest':
|
||||
this.sendNodeTypes(message.taskId, message.requestId, message.requestParams);
|
||||
break;
|
||||
case 'broker:rpc':
|
||||
void this.handleRpc(message.taskId, message.callId, message.name, message.params);
|
||||
break;
|
||||
@@ -239,6 +248,21 @@ export class TaskManager {
|
||||
});
|
||||
}
|
||||
|
||||
sendNodeTypes(
|
||||
taskId: string,
|
||||
requestId: string,
|
||||
neededNodeTypes: BrokerMessage.ToRequester.NodeTypesRequest['requestParams'],
|
||||
) {
|
||||
const nodeTypes = this.nodeTypes.getNodeTypeDescriptions(neededNodeTypes);
|
||||
|
||||
this.sendMessage({
|
||||
type: 'requester:nodetypesresponse',
|
||||
taskId,
|
||||
requestId,
|
||||
nodeTypes,
|
||||
});
|
||||
}
|
||||
|
||||
async handleRpc(
|
||||
taskId: string,
|
||||
callId: string,
|
||||
|
||||
@@ -54,7 +54,7 @@ export class TaskRunnerModule {
|
||||
private async loadTaskManager() {
|
||||
const { TaskManager } = await import('@/runners/task-managers/task-manager');
|
||||
const { LocalTaskManager } = await import('@/runners/task-managers/local-task-manager');
|
||||
this.taskManager = new LocalTaskManager();
|
||||
this.taskManager = Container.get(LocalTaskManager);
|
||||
Container.set(TaskManager, this.taskManager);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user