Skip to content

Commit 6c41b29

Browse files
RicardoE105krynble
andauthored
fix(cli): Fix issue with n8n crashing when error in poll method (#4008)
* 🐛 Fix issue with n8n crashing when error in poll method * Remove unnecessary imports and add async property * Remove unnecessary imports * ⚡ Move createErrorExecution to genericHelper * ⚡ Improvements Co-authored-by: Omar Ajoue <[email protected]>
1 parent 07672cc commit 6c41b29

File tree

4 files changed

+126
-11
lines changed

4 files changed

+126
-11
lines changed

packages/cli/src/ActiveWorkflowRunner.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import { User } from './databases/entities/User';
5757
import { whereClause } from './WorkflowHelpers';
5858
import { WorkflowEntity } from './databases/entities/WorkflowEntity';
5959
import * as ActiveExecutions from './ActiveExecutions';
60+
import { createErrorExecution } from './GenericHelpers';
6061

6162
const activeExecutions = ActiveExecutions.getInstance();
6263

@@ -650,7 +651,14 @@ export class ActiveWorkflowRunner {
650651
activation,
651652
);
652653
// eslint-disable-next-line no-underscore-dangle
653-
returnFunctions.__emit = (data: INodeExecutionData[][]): void => {
654+
returnFunctions.__emit = async (
655+
data: INodeExecutionData[][] | ExecutionError,
656+
): Promise<void> => {
657+
if (data instanceof Error) {
658+
await createErrorExecution(data, node, workflowData, workflow, mode);
659+
this.executeErrorWorkflow(data, workflowData, mode);
660+
return;
661+
}
654662
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
655663
Logger.debug(`Received event to trigger execution for workflow "${workflow.name}"`);
656664
WorkflowHelpers.saveStaticData(workflow);

packages/cli/src/GenericHelpers.ts

+98-2
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,27 @@
88
import express from 'express';
99
import { join as pathJoin } from 'path';
1010
import { readFile as fsReadFile } from 'fs/promises';
11-
import { IDataObject } from 'n8n-workflow';
11+
import {
12+
ExecutionError,
13+
IDataObject,
14+
INode,
15+
IRunExecutionData,
16+
Workflow,
17+
WorkflowExecuteMode,
18+
} from 'n8n-workflow';
1219
import { validate } from 'class-validator';
1320
import config from '../config';
1421

1522
// eslint-disable-next-line import/no-cycle
16-
import { Db, ICredentialsDb, IPackageVersions, ResponseHelper } from '.';
23+
import {
24+
Db,
25+
ICredentialsDb,
26+
IExecutionDb,
27+
IExecutionFlattedDb,
28+
IPackageVersions,
29+
IWorkflowDb,
30+
ResponseHelper,
31+
} from '.';
1732
// eslint-disable-next-line import/order
1833
import { Like } from 'typeorm';
1934
// eslint-disable-next-line import/no-cycle
@@ -214,4 +229,85 @@ export async function validateEntity(
214229
}
215230
}
216231

232+
/**
233+
* Create an error execution
234+
*
235+
* @param {INode} node
236+
* @param {IWorkflowDb} workflowData
237+
* @param {Workflow} workflow
238+
* @param {WorkflowExecuteMode} mode
239+
* @returns
240+
* @memberof ActiveWorkflowRunner
241+
*/
242+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
243+
export async function createErrorExecution(
244+
error: ExecutionError,
245+
node: INode,
246+
workflowData: IWorkflowDb,
247+
workflow: Workflow,
248+
mode: WorkflowExecuteMode,
249+
): Promise<void> {
250+
const saveDataErrorExecutionDisabled = workflowData?.settings?.saveDataErrorExecution === 'none';
251+
252+
if (saveDataErrorExecutionDisabled) return;
253+
254+
const executionData: IRunExecutionData = {
255+
startData: {
256+
destinationNode: node.name,
257+
runNodeFilter: [node.name],
258+
},
259+
executionData: {
260+
contextData: {},
261+
nodeExecutionStack: [
262+
{
263+
node,
264+
data: {
265+
main: [
266+
[
267+
{
268+
json: {},
269+
pairedItem: {
270+
item: 0,
271+
},
272+
},
273+
],
274+
],
275+
},
276+
source: null,
277+
},
278+
],
279+
waitingExecution: {},
280+
waitingExecutionSource: {},
281+
},
282+
resultData: {
283+
runData: {
284+
[node.name]: [
285+
{
286+
startTime: 0,
287+
executionTime: 0,
288+
error,
289+
source: [],
290+
},
291+
],
292+
},
293+
error,
294+
lastNodeExecuted: node.name,
295+
},
296+
};
297+
298+
const fullExecutionData: IExecutionDb = {
299+
data: executionData,
300+
mode,
301+
finished: false,
302+
startedAt: new Date(),
303+
workflowData,
304+
workflowId: workflow.id,
305+
stoppedAt: new Date(),
306+
};
307+
308+
const execution = ResponseHelper.flattenExecutionData(fullExecutionData);
309+
310+
await Db.collections.Execution.save(execution as IExecutionFlattedDb);
311+
}
312+
217313
export const DEFAULT_EXECUTIONS_GET_ALL_LIMIT = 20;

packages/core/src/ActiveWorkflows.ts

+18-7
Original file line numberDiff line numberDiff line change
@@ -163,24 +163,35 @@ export class ActiveWorkflows {
163163

164164
// Get all the trigger times
165165
const cronTimes = (pollTimes.item || []).map(toCronExpression);
166-
167166
// The trigger function to execute when the cron-time got reached
168-
const executeTrigger = async () => {
167+
const executeTrigger = async (testingTrigger = false) => {
169168
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
170169
Logger.debug(`Polling trigger initiated for workflow "${workflow.name}"`, {
171170
workflowName: workflow.name,
172171
workflowId: workflow.id,
173172
});
174-
const pollResponse = await workflow.runPoll(node, pollFunctions);
175173

176-
if (pollResponse !== null) {
177-
// eslint-disable-next-line no-underscore-dangle
178-
pollFunctions.__emit(pollResponse);
174+
try {
175+
const pollResponse = await workflow.runPoll(node, pollFunctions);
176+
177+
if (pollResponse !== null) {
178+
// eslint-disable-next-line no-underscore-dangle
179+
pollFunctions.__emit(pollResponse);
180+
}
181+
} catch (error) {
182+
// If the poll function failes in the first activation
183+
// throw the error back so we let the user know there is
184+
// an issue with the trigger.
185+
if (testingTrigger) {
186+
throw error;
187+
}
188+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-underscore-dangle
189+
pollFunctions.__emit(error);
179190
}
180191
};
181192

182193
// Execute the trigger directly to be able to know if it works
183-
await executeTrigger();
194+
await executeTrigger(true);
184195

185196
const timezone = pollFunctions.getTimezone();
186197

packages/workflow/src/Interfaces.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ export interface IHookFunctions {
715715
}
716716

717717
export interface IPollFunctions {
718-
__emit(data: INodeExecutionData[][]): void;
718+
__emit(data: INodeExecutionData[][] | NodeApiError): void;
719719
getCredentials(type: string): Promise<ICredentialDataDecryptedObject>;
720720
getMode(): WorkflowExecuteMode;
721721
getActivationMode(): WorkflowActivateMode;

0 commit comments

Comments
 (0)