Set lambda timeout in service (#15632)

Some functions keep running without a timeout being thrown. Doing it in
service directly.
This commit is contained in:
Thomas Trompette
2025-11-05 12:43:23 +00:00
committed by GitHub
parent c294af2944
commit f21b862d52
5 changed files with 51 additions and 20 deletions
@@ -11,4 +11,5 @@ export enum ServerlessFunctionExceptionCode {
SERVERLESS_FUNCTION_CODE_UNCHANGED = 'SERVERLESS_FUNCTION_CODE_UNCHANGED',
SERVERLESS_FUNCTION_EXECUTION_LIMIT_REACHED = 'SERVERLESS_FUNCTION_EXECUTION_LIMIT_REACHED',
SERVERLESS_FUNCTION_CREATE_FAILED = 'SERVERLESS_FUNCTION_CREATE_FAILED',
SERVERLESS_FUNCTION_EXECUTION_TIMEOUT = 'SERVERLESS_FUNCTION_EXECUTION_TIMEOUT',
}
@@ -13,11 +13,14 @@ import { type ServerlessExecuteResult } from 'src/engine/core-modules/serverless
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
import { SERVERLESS_FUNCTION_EXECUTED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/serverless-function/serverless-function-executed';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { Sources } from 'src/engine/core-modules/file-storage/types/source.type';
import { getBaseTypescriptProjectFiles } from 'src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files';
import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service';
import { getServerlessFolder } from 'src/engine/core-modules/serverless/utils/serverless-get-folder.utils';
import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { ServerlessFunctionLayerService } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.service';
import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
import { type UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import {
@@ -28,9 +31,6 @@ import {
WorkflowVersionStepException,
WorkflowVersionStepExceptionCode,
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
import { ServerlessFunctionLayerService } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.service';
import { Sources } from 'src/engine/core-modules/file-storage/types/source.type';
import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
@Injectable()
export class ServerlessFunctionService {
@@ -102,11 +102,11 @@ export class ServerlessFunctionService {
],
});
const resultServerlessFunction = await this.serverlessService.execute(
functionToExecute,
payload,
version,
);
const resultServerlessFunction = await this.callWithTimeout({
callback: () =>
this.serverlessService.execute(functionToExecute, payload, version),
timeoutMs: functionToExecute.timeoutSeconds * 1000,
});
if (this.twentyConfigService.get('SERVERLESS_LOGS_ENABLED')) {
/* eslint-disable no-console */
@@ -470,4 +470,31 @@ export class ServerlessFunctionService {
);
}
}
private async callWithTimeout<T>({
callback,
timeoutMs,
}: {
callback: () => Promise<T>;
timeoutMs: number;
}): Promise<T> {
let timeoutId: NodeJS.Timeout;
const timeoutPromise = new Promise<never>((_, reject) => {
timeoutId = setTimeout(
() =>
reject(
new ServerlessFunctionException(
`Execution timeout: ${timeoutMs / 1000}s`,
ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_TIMEOUT,
),
),
timeoutMs,
);
});
return Promise.race([callback(), timeoutPromise]).finally(() =>
clearTimeout(timeoutId),
) as Promise<T>;
}
}
@@ -4,6 +4,7 @@ import {
ConflictError,
ForbiddenError,
NotFoundError,
TimeoutError,
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import {
ServerlessFunctionException,
@@ -23,6 +24,8 @@ export const serverlessFunctionGraphQLApiExceptionHandler = (error: any) => {
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_BUILDING:
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_LIMIT_REACHED:
throw new ForbiddenError(error);
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_TIMEOUT:
throw new TimeoutError(error);
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_CODE_UNCHANGED:
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_CREATE_FAILED:
throw error;