From 5ffff1bb22691c09c5ca8b3ada2a19d5ce155a0b Mon Sep 17 00:00:00 2001 From: Omar Ajoue Date: Thu, 21 Dec 2023 09:40:39 +0000 Subject: [PATCH] fix: Stop binary data restoration from preventing execution from finishing (#8082) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the case of a filesystem failure to rename the binary files as part of the execution's cleanup process, the execution would fail to be saved and would never finish. This catch prevents it. ## Summary Whenever an execution is wrapping u to save the data, if it uses binary data n8n will try to find possibly misallocated files and place them in the right folder. If this process fails, the execution fails to finish. Given the execution has already finished at this point, and we cannot handle the binary data errors more gracefully, all we can do at this point is log the message as it's a filesystem issue. The rest of the execution saving process should remain as normal. ## Related tickets and issues https://linear.app/n8n/issue/HELP-430 ## Review / Merge checklist - [ ] PR title and summary are descriptive. **Remember, the title automatically goes into the changelog. Use `(no-changelog)` otherwise.** ([conventions](https://github.com/n8n-io/n8n/blob/master/.github/pull_request_title_conventions.md)) - [ ] [Docs updated](https://github.com/n8n-io/n8n-docs) or follow-up ticket created. - [ ] Tests included. > A bug is not considered fixed, unless a test is added to prevent it from happening again. > A feature is not complete without tests. --------- Co-authored-by: Iván Ovejero --- .../restoreBinaryDataId.ts | 44 +++++++++++++------ .../restoreBinaryDataId.test.ts | 22 ++++++++++ 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/packages/cli/src/executionLifecycleHooks/restoreBinaryDataId.ts b/packages/cli/src/executionLifecycleHooks/restoreBinaryDataId.ts index 15c03e1b4d..84780a785e 100644 --- a/packages/cli/src/executionLifecycleHooks/restoreBinaryDataId.ts +++ b/packages/cli/src/executionLifecycleHooks/restoreBinaryDataId.ts @@ -3,6 +3,7 @@ import { BinaryDataService } from 'n8n-core'; import type { IRun, WorkflowExecuteMode } from 'n8n-workflow'; import type { BinaryData } from 'n8n-core'; import config from '@/config'; +import { Logger } from '@/Logger'; /** * Whenever the execution ID is not available to the binary data service at the @@ -32,28 +33,43 @@ export async function restoreBinaryDataId( return; } - const { runData } = run.data.resultData; + try { + const { runData } = run.data.resultData; - const promises = Object.keys(runData).map(async (nodeName) => { - const binaryDataId = runData[nodeName]?.[0]?.data?.main?.[0]?.[0]?.binary?.data?.id; + const promises = Object.keys(runData).map(async (nodeName) => { + const binaryDataId = runData[nodeName]?.[0]?.data?.main?.[0]?.[0]?.binary?.data?.id; - if (!binaryDataId) return; + if (!binaryDataId) return; - const [mode, fileId] = binaryDataId.split(':') as [BinaryData.StoredMode, string]; + const [mode, fileId] = binaryDataId.split(':') as [BinaryData.StoredMode, string]; - const isMissingExecutionId = fileId.includes('/temp/'); + const isMissingExecutionId = fileId.includes('/temp/'); - if (!isMissingExecutionId) return; + if (!isMissingExecutionId) return; - const correctFileId = fileId.replace('temp', executionId); + const correctFileId = fileId.replace('temp', executionId); - await Container.get(BinaryDataService).rename(fileId, correctFileId); + await Container.get(BinaryDataService).rename(fileId, correctFileId); - const correctBinaryDataId = `${mode}:${correctFileId}`; + const correctBinaryDataId = `${mode}:${correctFileId}`; - // @ts-expect-error Validated at the top - run.data.resultData.runData[nodeName][0].data.main[0][0].binary.data.id = correctBinaryDataId; - }); + // @ts-expect-error Validated at the top + run.data.resultData.runData[nodeName][0].data.main[0][0].binary.data.id = correctBinaryDataId; + }); - await Promise.all(promises); + await Promise.all(promises); + } catch (e) { + const error = e instanceof Error ? e : new Error(`${e}`); + const logger = Container.get(Logger); + + if (error.message.includes('ENOENT')) { + logger.warn('Failed to restore binary data ID - No such file or dir', { + executionId, + error, + }); + return; + } + + logger.error('Failed to restore binary data ID - Unknown error', { executionId, error }); + } } diff --git a/packages/cli/test/unit/execution-lifecyle/restoreBinaryDataId.test.ts b/packages/cli/test/unit/execution-lifecyle/restoreBinaryDataId.test.ts index 05d828100c..3fcdb79c72 100644 --- a/packages/cli/test/unit/execution-lifecyle/restoreBinaryDataId.test.ts +++ b/packages/cli/test/unit/execution-lifecyle/restoreBinaryDataId.test.ts @@ -142,6 +142,28 @@ for (const mode of ['filesystem-v2', 's3'] as const) { expect(binaryDataService.rename).not.toHaveBeenCalled(); }); + + it('should ignore error thrown on renaming', async () => { + const workflowId = '6HYhhKmJch2cYxGj'; + const executionId = 'temp'; + const binaryDataFileUuid = 'a5c3f1ed-9d59-4155-bc68-9a370b3c51f6'; + + const incorrectFileId = `workflows/${workflowId}/executions/temp/binary_data/${binaryDataFileUuid}`; + + const run = toIRun({ + binary: { + data: { id: `s3:${incorrectFileId}` }, + }, + }); + + binaryDataService.rename.mockRejectedValueOnce(new Error('ENOENT')); + + const promise = restoreBinaryDataId(run, executionId, 'webhook'); + + await expect(promise).resolves.not.toThrow(); + + expect(binaryDataService.rename).toHaveBeenCalled(); + }); }); }