From 6c41b29ad2c1fffc5710d06250037e2a278b9b4a Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 15 Sep 2022 16:16:54 -0400 Subject: [PATCH] fix(cli): Fix issue with n8n crashing when error in poll method (#4008) * :bug: Fix issue with n8n crashing when error in poll method * Remove unnecessary imports and add async property * Remove unnecessary imports * :zap: Move createErrorExecution to genericHelper * :zap: Improvements Co-authored-by: Omar Ajoue --- packages/cli/src/ActiveWorkflowRunner.ts | 10 ++- packages/cli/src/GenericHelpers.ts | 100 ++++++++++++++++++++++- packages/core/src/ActiveWorkflows.ts | 25 ++++-- packages/workflow/src/Interfaces.ts | 2 +- 4 files changed, 126 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index 60388a73a3..89209768dd 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -57,6 +57,7 @@ import { User } from './databases/entities/User'; import { whereClause } from './WorkflowHelpers'; import { WorkflowEntity } from './databases/entities/WorkflowEntity'; import * as ActiveExecutions from './ActiveExecutions'; +import { createErrorExecution } from './GenericHelpers'; const activeExecutions = ActiveExecutions.getInstance(); @@ -650,7 +651,14 @@ export class ActiveWorkflowRunner { activation, ); // eslint-disable-next-line no-underscore-dangle - returnFunctions.__emit = (data: INodeExecutionData[][]): void => { + returnFunctions.__emit = async ( + data: INodeExecutionData[][] | ExecutionError, + ): Promise => { + if (data instanceof Error) { + await createErrorExecution(data, node, workflowData, workflow, mode); + this.executeErrorWorkflow(data, workflowData, mode); + return; + } // eslint-disable-next-line @typescript-eslint/restrict-template-expressions Logger.debug(`Received event to trigger execution for workflow "${workflow.name}"`); WorkflowHelpers.saveStaticData(workflow); diff --git a/packages/cli/src/GenericHelpers.ts b/packages/cli/src/GenericHelpers.ts index d9188af839..160dd7b7a1 100644 --- a/packages/cli/src/GenericHelpers.ts +++ b/packages/cli/src/GenericHelpers.ts @@ -8,12 +8,27 @@ import express from 'express'; import { join as pathJoin } from 'path'; import { readFile as fsReadFile } from 'fs/promises'; -import { IDataObject } from 'n8n-workflow'; +import { + ExecutionError, + IDataObject, + INode, + IRunExecutionData, + Workflow, + WorkflowExecuteMode, +} from 'n8n-workflow'; import { validate } from 'class-validator'; import config from '../config'; // eslint-disable-next-line import/no-cycle -import { Db, ICredentialsDb, IPackageVersions, ResponseHelper } from '.'; +import { + Db, + ICredentialsDb, + IExecutionDb, + IExecutionFlattedDb, + IPackageVersions, + IWorkflowDb, + ResponseHelper, +} from '.'; // eslint-disable-next-line import/order import { Like } from 'typeorm'; // eslint-disable-next-line import/no-cycle @@ -214,4 +229,85 @@ export async function validateEntity( } } +/** + * Create an error execution + * + * @param {INode} node + * @param {IWorkflowDb} workflowData + * @param {Workflow} workflow + * @param {WorkflowExecuteMode} mode + * @returns + * @memberof ActiveWorkflowRunner + */ +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export async function createErrorExecution( + error: ExecutionError, + node: INode, + workflowData: IWorkflowDb, + workflow: Workflow, + mode: WorkflowExecuteMode, +): Promise { + const saveDataErrorExecutionDisabled = workflowData?.settings?.saveDataErrorExecution === 'none'; + + if (saveDataErrorExecutionDisabled) return; + + const executionData: IRunExecutionData = { + startData: { + destinationNode: node.name, + runNodeFilter: [node.name], + }, + executionData: { + contextData: {}, + nodeExecutionStack: [ + { + node, + data: { + main: [ + [ + { + json: {}, + pairedItem: { + item: 0, + }, + }, + ], + ], + }, + source: null, + }, + ], + waitingExecution: {}, + waitingExecutionSource: {}, + }, + resultData: { + runData: { + [node.name]: [ + { + startTime: 0, + executionTime: 0, + error, + source: [], + }, + ], + }, + error, + lastNodeExecuted: node.name, + }, + }; + + const fullExecutionData: IExecutionDb = { + data: executionData, + mode, + finished: false, + startedAt: new Date(), + workflowData, + workflowId: workflow.id, + stoppedAt: new Date(), + }; + + const execution = ResponseHelper.flattenExecutionData(fullExecutionData); + + await Db.collections.Execution.save(execution as IExecutionFlattedDb); +} + export const DEFAULT_EXECUTIONS_GET_ALL_LIMIT = 20; diff --git a/packages/core/src/ActiveWorkflows.ts b/packages/core/src/ActiveWorkflows.ts index 01271de8fb..1f3c015196 100644 --- a/packages/core/src/ActiveWorkflows.ts +++ b/packages/core/src/ActiveWorkflows.ts @@ -163,24 +163,35 @@ export class ActiveWorkflows { // Get all the trigger times const cronTimes = (pollTimes.item || []).map(toCronExpression); - // The trigger function to execute when the cron-time got reached - const executeTrigger = async () => { + const executeTrigger = async (testingTrigger = false) => { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions Logger.debug(`Polling trigger initiated for workflow "${workflow.name}"`, { workflowName: workflow.name, workflowId: workflow.id, }); - const pollResponse = await workflow.runPoll(node, pollFunctions); - if (pollResponse !== null) { - // eslint-disable-next-line no-underscore-dangle - pollFunctions.__emit(pollResponse); + try { + const pollResponse = await workflow.runPoll(node, pollFunctions); + + if (pollResponse !== null) { + // eslint-disable-next-line no-underscore-dangle + pollFunctions.__emit(pollResponse); + } + } catch (error) { + // If the poll function failes in the first activation + // throw the error back so we let the user know there is + // an issue with the trigger. + if (testingTrigger) { + throw error; + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-underscore-dangle + pollFunctions.__emit(error); } }; // Execute the trigger directly to be able to know if it works - await executeTrigger(); + await executeTrigger(true); const timezone = pollFunctions.getTimezone(); diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 97d657e0a7..4d2222bdbe 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -715,7 +715,7 @@ export interface IHookFunctions { } export interface IPollFunctions { - __emit(data: INodeExecutionData[][]): void; + __emit(data: INodeExecutionData[][] | NodeApiError): void; getCredentials(type: string): Promise; getMode(): WorkflowExecuteMode; getActivationMode(): WorkflowActivateMode;