Skip to content

Commit 2cc3fd4

Browse files
authored
fix(sdk): prevent setTimeout overflow for wait durations > 24.8 days (#475)
1. Skips setTimeout entirely when delay exceeds MAX_POLL_DURATION_MS instead of capping it 2. Prevents resource waste since Lambda will timeout before a 15-minute setTimeout would fire anyway 3. Updates tests to verify setTimeout is not called for long delays 4. Addressing failing integration test related to tenantId change.
1 parent e8f71ec commit 2cc3fd4

File tree

8 files changed

+389
-8
lines changed

8 files changed

+389
-8
lines changed

packages/aws-durable-execution-sdk-js-examples/scripts/deploy-lambda.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,9 @@ async function createFunction(
305305
LoggingConfig: {
306306
LogGroup: logGroupName,
307307
},
308+
TenancyConfig: exampleConfig.handler.includes("tenant-target")
309+
? { TenantIsolationMode: "PER_TENANT" }
310+
: undefined,
308311
};
309312

310313
const command = new CreateFunctionCommand(createParams);
@@ -365,6 +368,9 @@ async function updateFunction(
365368
},
366369
}
367370
: undefined,
371+
TenancyConfig: exampleConfig.handler.includes("tenant-target")
372+
? { TenantIsolationMode: "PER_TENANT" }
373+
: undefined,
368374
};
369375

370376
// Check if DurableConfig needs updating
@@ -479,12 +485,25 @@ async function main(): Promise<void> {
479485
functionExists = false;
480486
}
481487

488+
// Check if tenancy configuration needs to change
489+
const needsTenancy = exampleConfig.handler.includes("tenant-target");
490+
const hasTenancy = !!currentConfig.TenancyConfig;
491+
if (needsTenancy !== hasTenancy) {
492+
console.log(
493+
"Deleting function since tenancy configuration changed",
494+
);
495+
functionExists = false;
496+
}
497+
482498
if (!functionExists) {
483499
await lambdaClient.send(
484500
new DeleteFunctionCommand({
485501
FunctionName: functionName,
486502
}),
487503
);
504+
// Wait for function to be fully deleted
505+
console.log("Waiting for function deletion to complete...");
506+
await new Promise((resolve) => setTimeout(resolve, 5000));
488507
}
489508
}
490509

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
[
2+
{
3+
"EventType": "ExecutionStarted",
4+
"EventId": 1,
5+
"Id": "ebf5b0f0-eab3-40cf-a205-e1e8bdb67ceb",
6+
"EventTimestamp": "2026-03-09T23:44:09.031Z",
7+
"ExecutionStartedDetails": {
8+
"Input": {
9+
"Payload": "{}"
10+
}
11+
}
12+
},
13+
{
14+
"EventType": "ContextStarted",
15+
"SubType": "Parallel",
16+
"EventId": 2,
17+
"Id": "c4ca4238a0b92382",
18+
"EventTimestamp": "2026-03-09T23:44:09.035Z",
19+
"ContextStartedDetails": {}
20+
},
21+
{
22+
"EventType": "ContextStarted",
23+
"SubType": "ParallelBranch",
24+
"EventId": 3,
25+
"Id": "ea66c06c1e1c05fa",
26+
"Name": "parallel-branch-0",
27+
"EventTimestamp": "2026-03-09T23:44:09.035Z",
28+
"ParentId": "c4ca4238a0b92382",
29+
"ContextStartedDetails": {}
30+
},
31+
{
32+
"EventType": "ContextStarted",
33+
"SubType": "WaitForCallback",
34+
"EventId": 4,
35+
"Id": "2f221a18eb863803",
36+
"Name": "wait-for-callback-1",
37+
"EventTimestamp": "2026-03-09T23:44:09.035Z",
38+
"ParentId": "ea66c06c1e1c05fa",
39+
"ContextStartedDetails": {}
40+
},
41+
{
42+
"EventType": "CallbackStarted",
43+
"SubType": "Callback",
44+
"EventId": 5,
45+
"Id": "55998bab939d40e7",
46+
"EventTimestamp": "2026-03-09T23:44:09.035Z",
47+
"ParentId": "2f221a18eb863803",
48+
"CallbackStartedDetails": {
49+
"CallbackId": "eyJleGVjdXRpb25JZCI6IjYwYTc2ZTVlLTExYTYtNDcyYy1iOWRlLWRkYjNmOGI5MGVjZiIsIm9wZXJhdGlvbklkIjoiNTU5OThiYWI5MzlkNDBlNyIsInRva2VuIjoiMTE1ZDVkYjMtZTkzNi00N2Q2LTgyM2ItZjcyMjA5ZjlmYjFiIn0=",
50+
"Input": {}
51+
}
52+
},
53+
{
54+
"EventType": "ContextStarted",
55+
"SubType": "ParallelBranch",
56+
"EventId": 6,
57+
"Id": "98c6f2c2287f4c73",
58+
"Name": "parallel-branch-1",
59+
"EventTimestamp": "2026-03-09T23:44:09.035Z",
60+
"ParentId": "c4ca4238a0b92382",
61+
"ContextStartedDetails": {}
62+
},
63+
{
64+
"EventType": "ContextStarted",
65+
"SubType": "WaitForCallback",
66+
"EventId": 7,
67+
"Id": "6151f5ab282d90e4",
68+
"Name": "wait-for-callback-2",
69+
"EventTimestamp": "2026-03-09T23:44:09.035Z",
70+
"ParentId": "98c6f2c2287f4c73",
71+
"ContextStartedDetails": {}
72+
},
73+
{
74+
"EventType": "CallbackStarted",
75+
"SubType": "Callback",
76+
"EventId": 8,
77+
"Id": "361fe970d1e192e9",
78+
"EventTimestamp": "2026-03-09T23:44:09.035Z",
79+
"ParentId": "6151f5ab282d90e4",
80+
"CallbackStartedDetails": {
81+
"CallbackId": "eyJleGVjdXRpb25JZCI6IjYwYTc2ZTVlLTExYTYtNDcyYy1iOWRlLWRkYjNmOGI5MGVjZiIsIm9wZXJhdGlvbklkIjoiMzYxZmU5NzBkMWUxOTJlOSIsInRva2VuIjoiMGUwZTQ1NzgtNmU2ZS00MTkyLTllNTMtMTYzZTA5MmYxY2ZjIn0=",
82+
"Input": {}
83+
}
84+
},
85+
{
86+
"EventType": "StepStarted",
87+
"SubType": "Step",
88+
"EventId": 9,
89+
"Id": "90ef5a310191970a",
90+
"EventTimestamp": "2026-03-09T23:44:09.038Z",
91+
"ParentId": "2f221a18eb863803",
92+
"StepStartedDetails": {}
93+
},
94+
{
95+
"EventType": "StepStarted",
96+
"SubType": "Step",
97+
"EventId": 10,
98+
"Id": "1c573df7b335bdcb",
99+
"EventTimestamp": "2026-03-09T23:44:09.038Z",
100+
"ParentId": "6151f5ab282d90e4",
101+
"StepStartedDetails": {}
102+
},
103+
{
104+
"EventType": "StepSucceeded",
105+
"SubType": "Step",
106+
"EventId": 11,
107+
"Id": "1c573df7b335bdcb",
108+
"EventTimestamp": "2026-03-09T23:44:09.038Z",
109+
"ParentId": "6151f5ab282d90e4",
110+
"StepSucceededDetails": {
111+
"Result": {},
112+
"RetryDetails": {}
113+
}
114+
},
115+
{
116+
"EventType": "CallbackSucceeded",
117+
"SubType": "Callback",
118+
"EventId": 12,
119+
"Id": "361fe970d1e192e9",
120+
"EventTimestamp": "2026-03-09T23:44:09.139Z",
121+
"ParentId": "6151f5ab282d90e4",
122+
"CallbackSucceededDetails": {
123+
"Result": {
124+
"Payload": "{\"id\":2,\"data\":\"second-completed\"}"
125+
}
126+
}
127+
},
128+
{
129+
"EventType": "CallbackSucceeded",
130+
"SubType": "Callback",
131+
"EventId": 13,
132+
"Id": "55998bab939d40e7",
133+
"EventTimestamp": "2026-03-09T23:44:09.139Z",
134+
"ParentId": "2f221a18eb863803",
135+
"CallbackSucceededDetails": {
136+
"Result": {
137+
"Payload": "{\"id\":1,\"data\":\"first-completed\"}"
138+
}
139+
}
140+
},
141+
{
142+
"EventType": "ContextSucceeded",
143+
"SubType": "WaitForCallback",
144+
"EventId": 14,
145+
"Id": "6151f5ab282d90e4",
146+
"Name": "wait-for-callback-2",
147+
"EventTimestamp": "2026-03-09T23:44:10.043Z",
148+
"ParentId": "98c6f2c2287f4c73",
149+
"ContextSucceededDetails": {
150+
"Result": {
151+
"Payload": "\"{\\\"id\\\":2,\\\"data\\\":\\\"second-completed\\\"}\""
152+
}
153+
}
154+
},
155+
{
156+
"EventType": "ContextSucceeded",
157+
"SubType": "ParallelBranch",
158+
"EventId": 15,
159+
"Id": "98c6f2c2287f4c73",
160+
"Name": "parallel-branch-1",
161+
"EventTimestamp": "2026-03-09T23:44:10.043Z",
162+
"ParentId": "c4ca4238a0b92382",
163+
"ContextSucceededDetails": {
164+
"Result": {
165+
"Payload": "\"{\\\"id\\\":2,\\\"data\\\":\\\"second-completed\\\"}\""
166+
}
167+
}
168+
},
169+
{
170+
"EventType": "StepSucceeded",
171+
"SubType": "Step",
172+
"EventId": 16,
173+
"Id": "90ef5a310191970a",
174+
"EventTimestamp": "2026-03-09T23:44:11.038Z",
175+
"ParentId": "2f221a18eb863803",
176+
"StepSucceededDetails": {
177+
"Result": {},
178+
"RetryDetails": {}
179+
}
180+
},
181+
{
182+
"EventType": "ContextSucceeded",
183+
"SubType": "WaitForCallback",
184+
"EventId": 17,
185+
"Id": "2f221a18eb863803",
186+
"Name": "wait-for-callback-1",
187+
"EventTimestamp": "2026-03-09T23:44:11.040Z",
188+
"ParentId": "ea66c06c1e1c05fa",
189+
"ContextSucceededDetails": {
190+
"Result": {
191+
"Payload": "\"{\\\"id\\\":1,\\\"data\\\":\\\"first-completed\\\"}\""
192+
}
193+
}
194+
},
195+
{
196+
"EventType": "ContextSucceeded",
197+
"SubType": "ParallelBranch",
198+
"EventId": 18,
199+
"Id": "ea66c06c1e1c05fa",
200+
"Name": "parallel-branch-0",
201+
"EventTimestamp": "2026-03-09T23:44:11.040Z",
202+
"ParentId": "c4ca4238a0b92382",
203+
"ContextSucceededDetails": {
204+
"Result": {
205+
"Payload": "\"{\\\"id\\\":1,\\\"data\\\":\\\"first-completed\\\"}\""
206+
}
207+
}
208+
},
209+
{
210+
"EventType": "ContextSucceeded",
211+
"SubType": "Parallel",
212+
"EventId": 19,
213+
"Id": "c4ca4238a0b92382",
214+
"EventTimestamp": "2026-03-09T23:44:11.040Z",
215+
"ContextSucceededDetails": {
216+
"Result": {
217+
"Payload": "{\"all\":[{\"result\":\"{\\\"id\\\":1,\\\"data\\\":\\\"first-completed\\\"}\",\"index\":0,\"status\":\"SUCCEEDED\"},{\"result\":\"{\\\"id\\\":2,\\\"data\\\":\\\"second-completed\\\"}\",\"index\":1,\"status\":\"SUCCEEDED\"}],\"completionReason\":\"ALL_COMPLETED\"}"
218+
}
219+
}
220+
},
221+
{
222+
"EventType": "InvocationCompleted",
223+
"EventId": 20,
224+
"EventTimestamp": "2026-03-09T23:44:11.040Z",
225+
"InvocationCompletedDetails": {
226+
"StartTimestamp": "2026-03-09T23:44:09.030Z",
227+
"EndTimestamp": "2026-03-09T23:44:11.040Z",
228+
"Error": {},
229+
"RequestId": "c434f2a0-e7e4-401f-96c0-521c1f57d527"
230+
}
231+
},
232+
{
233+
"EventType": "ExecutionSucceeded",
234+
"EventId": 21,
235+
"Id": "ebf5b0f0-eab3-40cf-a205-e1e8bdb67ceb",
236+
"EventTimestamp": "2026-03-09T23:44:11.041Z",
237+
"ExecutionSucceededDetails": {
238+
"Result": {
239+
"Payload": "{\"results\":[{\"id\":1,\"data\":\"first-completed\"},{\"id\":2,\"data\":\"second-completed\"}],\"allCompleted\":true}"
240+
}
241+
}
242+
}
243+
]

packages/aws-durable-execution-sdk-js-examples/src/examples/concurrent/callback-submitter/concurrent-callback-submitter.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ createTests({
3131

3232
const execution = await executionPromise;
3333

34-
assertEventSignatures(execution);
34+
assertEventSignatures(execution, "concurrent-callback-submitter", {
35+
invocationCompletedDifference: 1,
36+
});
3537
});
3638
},
3739
});

packages/aws-durable-execution-sdk-js-examples/src/examples/invoke/tenant-id/invoke-tenant-id.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import { LocalDurableTestRunner } from "@aws/durable-execution-sdk-js-testing";
22
import { createTests } from "../../../utils/test-helper";
33
import { handler } from "./invoke-tenant-id";
4-
import { handler as namedWaitHandler } from "../../wait/named/wait-named";
4+
import { handler as tenantTargetHandler } from "../tenant-target/tenant-target";
55

66
createTests({
77
handler,
88
tests: function (runner, { functionNameMap, assertEventSignatures }) {
99
it("should invoke with tenantId for tenant isolation", async () => {
1010
if (runner instanceof LocalDurableTestRunner) {
1111
runner.registerDurableFunction(
12-
functionNameMap.getFunctionName("wait-named"),
13-
namedWaitHandler,
12+
functionNameMap.getFunctionName("tenant-target"),
13+
tenantTargetHandler,
1414
);
1515
}
1616

1717
const result = await runner.run({
1818
payload: {
19-
functionName: functionNameMap.getFunctionName("wait-named"),
19+
functionName: functionNameMap.getFunctionName("tenant-target"),
2020
tenantId: "tenant-abc-123",
2121
},
2222
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {
2+
DurableContext,
3+
withDurableExecution,
4+
} from "@aws/durable-execution-sdk-js";
5+
import { ExampleConfig } from "../../../types";
6+
7+
export const config: ExampleConfig = {
8+
name: "Tenant Target",
9+
description:
10+
"Target function for tenant isolation testing - supports tenant ID invocation",
11+
durableConfig: {
12+
ExecutionTimeout: 300,
13+
RetentionPeriodInDays: 7,
14+
},
15+
};
16+
17+
export const handler = withDurableExecution(
18+
async (event: { seconds?: number }, context: DurableContext) => {
19+
const waitTime = event.seconds || 1;
20+
21+
context.logger.info("Starting tenant-enabled target function", {
22+
waitTime,
23+
});
24+
25+
await context.wait("tenant-wait", { seconds: waitTime });
26+
27+
context.logger.info("Tenant target function completed");
28+
return "wait finished";
29+
},
30+
);

0 commit comments

Comments
 (0)