tt-fix-logic-function-error-handling (#20928)
## Summary - **Fix `callWithTimeout` timer leak**: the `setTimeout` was never cleared when the callback resolved first, leaving orphaned timers (up to 15 minutes) in the Node.js event loop. Now uses `try/finally` with `clearTimeout`. - **Properly classify timeout errors**: introduced `ExecutionTimedOutError` so the Lambda driver can throw `LOGIC_FUNCTION_EXECUTION_TIMEOUT` instead of `LOGIC_FUNCTION_EXECUTION_FAILED` — users now see "Function execution timed out" instead of the generic "Function execution failed." ## Test plan - [x] Execute a logic function normally and verify it still works - [x] Trigger a logic function timeout and verify the error message says "Function execution timed out" (not "Function execution failed") - [x] Verify that a deleted logic function invocation logs a specific "was deleted" warning - [x] Verify no orphaned timers remain after logic function execution completes
This commit is contained in:
+30
-25
@@ -41,7 +41,6 @@ import { ASSET_PATH } from 'src/constants/assets-path';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { type CacheLockService } from 'src/engine/core-modules/cache-lock/cache-lock.service';
|
||||
import { COMMON_LAYER_DEPENDENCIES_DIRNAME } from 'src/engine/core-modules/logic-function/logic-function-drivers/constants/common-layer-dependencies-dirname';
|
||||
import { callWithTimeout } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/call-with-timeout';
|
||||
import { copyBuilder } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-builder';
|
||||
import { copyCommonLayerDependencies } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-common-layer-dependencies';
|
||||
import { copyExecutor } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-executor';
|
||||
@@ -429,17 +428,18 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
|
||||
const yarnInstallFunctionName = await this.getYarnInstallFunctionName();
|
||||
|
||||
const result = await callWithTimeout({
|
||||
callback: () =>
|
||||
lambdaClient.send(
|
||||
new InvokeCommand({
|
||||
FunctionName: yarnInstallFunctionName,
|
||||
Payload: JSON.stringify(payload),
|
||||
LogType: LogType.Tail,
|
||||
}),
|
||||
const result = await lambdaClient.send(
|
||||
new InvokeCommand({
|
||||
FunctionName: yarnInstallFunctionName,
|
||||
Payload: JSON.stringify(payload),
|
||||
LogType: LogType.Tail,
|
||||
}),
|
||||
{
|
||||
abortSignal: AbortSignal.timeout(
|
||||
YARN_INSTALL_LAMBDA_TIMEOUT_SECONDS * 1000,
|
||||
),
|
||||
timeoutMs: YARN_INSTALL_LAMBDA_TIMEOUT_SECONDS * 1000,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if (result.FunctionError) {
|
||||
const parsedResult = result.Payload
|
||||
@@ -529,17 +529,16 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
builtFileName,
|
||||
};
|
||||
|
||||
const result = await callWithTimeout({
|
||||
callback: () =>
|
||||
lambdaClient.send(
|
||||
new InvokeCommand({
|
||||
FunctionName: builderFunctionName,
|
||||
Payload: JSON.stringify(payload),
|
||||
LogType: LogType.Tail,
|
||||
}),
|
||||
),
|
||||
timeoutMs: BUILDER_LAMBDA_TIMEOUT_SECONDS * 1000,
|
||||
});
|
||||
const result = await lambdaClient.send(
|
||||
new InvokeCommand({
|
||||
FunctionName: builderFunctionName,
|
||||
Payload: JSON.stringify(payload),
|
||||
LogType: LogType.Tail,
|
||||
}),
|
||||
{
|
||||
abortSignal: AbortSignal.timeout(BUILDER_LAMBDA_TIMEOUT_SECONDS * 1000),
|
||||
},
|
||||
);
|
||||
|
||||
if (result.FunctionError) {
|
||||
const parsedResult = result.Payload
|
||||
@@ -1195,9 +1194,8 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
const lambdaClient = await this.getLambdaClient();
|
||||
|
||||
const invokeStart = Date.now();
|
||||
const result = await callWithTimeout({
|
||||
callback: () => lambdaClient.send(command),
|
||||
timeoutMs,
|
||||
const result = await lambdaClient.send(command, {
|
||||
abortSignal: AbortSignal.timeout(timeoutMs),
|
||||
});
|
||||
const invokeSendMs = Date.now() - invokeStart;
|
||||
|
||||
@@ -1248,6 +1246,13 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof Error && error.name === 'TimeoutError') {
|
||||
throw new LogicFunctionException(
|
||||
`Lambda invocation timed out for function '${flatLogicFunction.id}': ${error.message}`,
|
||||
LogicFunctionExceptionCode.LOGIC_FUNCTION_EXECUTION_TIMEOUT,
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof LogicFunctionException) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
export const callWithTimeout = async <T>({
|
||||
callback,
|
||||
timeoutMs,
|
||||
}: {
|
||||
callback: () => Promise<T>;
|
||||
timeoutMs: number;
|
||||
}): Promise<T> => {
|
||||
return Promise.race([
|
||||
callback(),
|
||||
new Promise<T>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Execution timed out')), timeoutMs),
|
||||
),
|
||||
]);
|
||||
};
|
||||
Reference in New Issue
Block a user