mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 10:31:15 +00:00
⚡ Implement timeout for workers and corrected timeout for subworkflows (#1634)
* Implemented timeout for workers and corrected timeout for subworkflows * Fixed issue with timeouts when running on separate processes * Standardized timeouts across all n8n Now the maxTimeout setting takes effect whenever a timeout is set to any workflows. This causes local timeouts (either set on a per-workflow basis or via global settings) to be capped by the maximum timeout. This behavior already existed but was not applied to all places. Also changed the way n8n enforces timeouts for subworkflows, making it work always. In effect, with this change, if you have one workflow that calls others only the main workflow's timeout is taken into consideration, limiting the maximum time that other workflows combined can run. * ⚡ Fix timeout problem in "own" mode Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
CredentialTypes,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
IWorkflowExecuteProcess,
|
||||
IWorkflowExecutionDataProcessWithExecution,
|
||||
NodeTypes,
|
||||
WorkflowExecuteAdditionalData,
|
||||
@@ -40,6 +41,9 @@ export class WorkflowRunnerProcess {
|
||||
workflow: Workflow | undefined;
|
||||
workflowExecute: WorkflowExecute | undefined;
|
||||
executionIdCallback: (executionId: string) => void | undefined;
|
||||
childExecutions: {
|
||||
[key: string]: IWorkflowExecuteProcess,
|
||||
} = {};
|
||||
|
||||
static async stopProcess() {
|
||||
setTimeout(() => {
|
||||
@@ -107,8 +111,18 @@ export class WorkflowRunnerProcess {
|
||||
await Db.init();
|
||||
}
|
||||
|
||||
// Start timeout for the execution
|
||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
||||
if (this.data.workflowData.settings && this.data.workflowData.settings.executionTimeout) {
|
||||
workflowTimeout = this.data.workflowData.settings!.executionTimeout as number; // preference on workflow setting
|
||||
}
|
||||
|
||||
if (workflowTimeout > 0) {
|
||||
workflowTimeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number);
|
||||
}
|
||||
|
||||
this.workflow = new Workflow({ id: this.data.workflowData.id as string | undefined, name: this.data.workflowData.name, nodes: this.data.workflowData!.nodes, connections: this.data.workflowData!.connections, active: this.data.workflowData!.active, nodeTypes, staticData: this.data.workflowData!.staticData, settings: this.data.workflowData!.settings });
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(this.data.credentials);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(this.data.credentials, undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000);
|
||||
additionalData.hooks = this.getProcessForwardHooks();
|
||||
|
||||
const executeWorkflowFunction = additionalData.executeWorkflow;
|
||||
@@ -123,14 +137,20 @@ export class WorkflowRunnerProcess {
|
||||
});
|
||||
let result: IRun;
|
||||
try {
|
||||
result = await executeWorkflowFunction(workflowInfo, additionalData, inputData, executionId, workflowData, runData);
|
||||
const executeWorkflowFunctionOutput = await executeWorkflowFunction(workflowInfo, additionalData, inputData, executionId, workflowData, runData) as {workflowExecute: WorkflowExecute, workflow: Workflow} as IWorkflowExecuteProcess;
|
||||
const workflowExecute = executeWorkflowFunctionOutput.workflowExecute;
|
||||
this.childExecutions[executionId] = executeWorkflowFunctionOutput;
|
||||
const workflow = executeWorkflowFunctionOutput.workflow;
|
||||
result = await workflowExecute.processRunExecutionData(workflow) as IRun;
|
||||
await externalHooks.run('workflow.postExecute', [result, workflowData]);
|
||||
await sendToParentProcess('finishExecution', { executionId, result });
|
||||
delete this.childExecutions[executionId];
|
||||
} catch (e) {
|
||||
await sendToParentProcess('finishExecution', { executionId });
|
||||
// Throw same error we had
|
||||
throw e;
|
||||
delete this.childExecutions[executionId];
|
||||
// Throw same error we had
|
||||
throw e;
|
||||
}
|
||||
|
||||
await sendToParentProcess('finishExecution', { executionId, result });
|
||||
|
||||
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(result);
|
||||
return returnData!.data!.main;
|
||||
@@ -254,6 +274,8 @@ const workflowRunner = new WorkflowRunnerProcess();
|
||||
process.on('message', async (message: IProcessMessage) => {
|
||||
try {
|
||||
if (message.type === 'startWorkflow') {
|
||||
await sendToParentProcess('start', {});
|
||||
|
||||
const runData = await workflowRunner.runWorkflow(message.data);
|
||||
|
||||
await sendToParentProcess('end', {
|
||||
@@ -267,6 +289,18 @@ process.on('message', async (message: IProcessMessage) => {
|
||||
let runData: IRun;
|
||||
|
||||
if (workflowRunner.workflowExecute !== undefined) {
|
||||
|
||||
const executionIds = Object.keys(workflowRunner.childExecutions);
|
||||
|
||||
for (const executionId of executionIds) {
|
||||
const childWorkflowExecute = workflowRunner.childExecutions[executionId];
|
||||
runData = childWorkflowExecute.workflowExecute.getFullRunData(workflowRunner.childExecutions[executionId].startedAt);
|
||||
const timeOutError = message.type === 'timeout' ? new WorkflowOperationError('Workflow execution timed out!') : undefined;
|
||||
|
||||
// If there is any data send it to parent process, if execution timedout add the error
|
||||
await childWorkflowExecute.workflowExecute.processSuccessExecution(workflowRunner.childExecutions[executionId].startedAt, childWorkflowExecute.workflow, timeOutError);
|
||||
}
|
||||
|
||||
// Workflow started already executing
|
||||
runData = workflowRunner.workflowExecute.getFullRunData(workflowRunner.startedAt);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user