Wait Operations¶
Time-based durable waits¶
The Wait operation of the DurableContext pauses execution for a specified time without
consuming compute. The SDK will checkpoint the start of the wait operation, the function
suspends and Lambda exits, and then the backend automatically resumes execution when the
wait completes. The SDK will replay and resume processing from where it had paused for
the wait.
Unlike language-native sleep functions such as setTimeout(), time.sleep(), or
Thread.sleep(), durable wait operations do not consume Lambda execution time. The
durable function invocation exits cleanly after it checkpoints the start of the wait and
resumes later at the specified time, even if the wait lasts hours or days.
The minimum wait duration is 1 second. The maximum wait duration is the maximum execution duration of 1 year. There is no cost associated with longer waits.
You cannot cancel a wait after it has started.
When to use wait¶
Use context.wait() for a time-based delay. For example, use waits between
steps to delay the new next step in multi-step workflows.
Wait for an event or status change¶
To wait for an event or status change, rather than just a fixed delay, consider these alternatives:
- Polling until a condition is met
- Wait for Condition handles the polling loop, state tracking, and backoff for you.
- Waiting for an external system response
- Callbacks suspend your durable function until an external system sends a response.
Wait walkthrough¶
Here's a simple example of using a wait operation:
from aws_durable_execution_sdk_python import DurableContext, durable_execution
from aws_durable_execution_sdk_python.config import Duration
@durable_execution
def handler(event: dict, context: DurableContext) -> str:
"""Simple durable function with a wait."""
# Wait for 5 seconds
context.wait(duration=Duration.from_seconds(5))
return "Wait completed"
import java.time.Duration;
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableHandler;
public class BasicWaitExample extends DurableHandler<Object, String> {
@Override
public String handleRequest(Object input, DurableContext context) {
// Wait for 5 seconds
context.wait(null, Duration.ofSeconds(5));
return "Wait completed";
}
}
When this function runs:
- The SDK checkpoints the wait operation with a scheduled end time
- The Lambda function suspends
- After 5 seconds, the backend automatically invokes your function again
- Execution resumes after the wait and returns "Wait completed"
Method signature¶
Parameters:
duration(required) - How long to wait. Must be at least 1 second. See Duration for how to specify durations in each programming language.name(optional) - Only used for display, debugging and testing.
Returns:
DurablePromise<void>
None
Void (sync)
DurableFuture<Void> (async)
Raises/Throws:
None
ValidationError(DurableExecutionsError)
IllegalArgumentException
Duration¶
Duration signature¶
# module: aws_durable_execution_sdk_python.config
@dataclass
class Duration:
seconds: int = 0
@classmethod
def from_seconds(cls, value: float) -> Duration: ...
@classmethod
def from_minutes(cls, value: float) -> Duration: ...
@classmethod
def from_hours(cls, value: float) -> Duration: ...
@classmethod
def from_days(cls, value: float) -> Duration: ...
Duration usage¶
// Wait for 30 seconds
await context.wait({ seconds: 30 });
// Wait for 5 minutes
await context.wait({ minutes: 5 });
// Wait for 2 hours
await context.wait({ hours: 2 });
// Wait for 1 day
await context.wait({ days: 1 });
// Combined - 1 hour and 30 minutes
await context.wait({ hours: 1, minutes: 30 });
from aws_durable_execution_sdk_python.config import Duration
# Wait for 30 seconds
context.wait(duration=Duration.from_seconds(30))
# Wait for 5 minutes
context.wait(duration=Duration.from_minutes(5))
# Wait for 2 hours
context.wait(duration=Duration.from_hours(2))
# Wait for 1 day
context.wait(duration=Duration.from_days(1))
Named wait operations¶
Name wait operations to make them easier to identify in logs and tests.
from aws_durable_execution_sdk_python import DurableContext, durable_execution
from aws_durable_execution_sdk_python.config import Duration
@durable_execution
def handler(event: dict, context: DurableContext) -> str:
"""Durable function with a named wait."""
# Wait with explicit name
context.wait(duration=Duration.from_seconds(2), name="custom_wait")
return "Wait with name completed"
import java.time.Duration;
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableHandler;
public class NamedWaitExample extends DurableHandler<Object, String> {
@Override
public String handleRequest(Object input, DurableContext context) {
// Wait with explicit name
context.wait("custom_wait", Duration.ofSeconds(2));
return "Wait with name completed";
}
}
Scheduled end timestamp¶
Each wait operation has a scheduled end timestamp that indicates when it completes. This timestamp uses Unix milliseconds.
The ScheduledEndTimestamp field is in the checkpoint's WaitDetails. The SDK
calculates the scheduled end time when it first checkpoints the wait operation:
Wait durations are approximate. The actual resume time depends on system scheduling, Lambda cold start time, and current system load.
Concurrency¶
Waits execute sequentially in the order they appear in your code. You can use waits
inside parallel or map operations. If a branch or iteration
of a parent is ready to suspend due to a wait, the durable function will wait for all
child operations of that parent to complete or suspend before terminating the
invocation.
In TypeScript and Java, you can run a wait concurrently with other operations. This is useful for enforcing a minimum duration — for example, ensuring at least 5 seconds pass while a step runs in parallel.
Don't await the wait immediately — use Promise.all to run it alongside other
operations.
export const handler = withDurableExecution(
async (event: any, context: DurableContext) => {
// Start wait and step but don't await yet
const waitPromise = context.wait("min-delay", { seconds: 5 });
const stepPromise = context.step("process", async () => processData(event));
// Wait for both — guarantees at least 5 seconds elapsed
const [, result] = await Promise.all([waitPromise, stepPromise]);
return result;
},
);
Use waitAsync() which returns a DurableFuture<Void>, then call .get() when you
need to block.
import java.time.Duration;
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableFuture;
import software.amazon.lambda.durable.DurableHandler;
public class AsyncWaitExample extends DurableHandler<Object, String> {
@Override
public String handleRequest(Object input, DurableContext context) {
// Start wait and step concurrently — returns immediately
DurableFuture<Void> waitFuture = context.waitAsync("min-delay", Duration.ofSeconds(5));
DurableFuture<String> stepFuture = context.stepAsync("process", String.class, stepCtx -> processData(input));
// Block until both complete — guarantees at least 5 seconds elapsed
waitFuture.get();
var result = stepFuture.get();
return result;
}
}
Testing¶
You can verify wait operations in your tests by inspecting the operations list:
import {
LocalDurableTestRunner,
OperationType,
OperationStatus,
} from "@aws/durable-execution-sdk-js-testing";
import { handler } from "./multiple-waits";
let runner: LocalDurableTestRunner;
beforeAll(async () => {
await LocalDurableTestRunner.setupTestEnvironment({ skipTime: true });
});
afterAll(async () => {
await LocalDurableTestRunner.teardownTestEnvironment();
});
beforeEach(() => {
runner = new LocalDurableTestRunner({ handlerFunction: handler });
});
it("should handle multiple sequential wait operations", async () => {
const firstWait = runner.getOperation("wait-1");
const secondWait = runner.getOperation("wait-2");
const result = await runner.run();
expect(result.getResult()).toEqual({ completedWaits: 2, finalStep: "done" });
const operations = result.getOperations();
expect(operations.length).toEqual(2);
expect(firstWait.getType()).toBe(OperationType.WAIT);
expect(firstWait.getStatus()).toBe(OperationStatus.SUCCEEDED);
expect(firstWait.getWaitDetails()?.waitSeconds).toBe(5);
expect(secondWait.getType()).toBe(OperationType.WAIT);
expect(secondWait.getStatus()).toBe(OperationStatus.SUCCEEDED);
expect(secondWait.getWaitDetails()?.waitSeconds).toBe(5);
});
@pytest.mark.durable_execution(handler=multiple_wait.handler)
def test_multiple_waits(durable_runner):
"""Test multiple sequential waits."""
with durable_runner:
result = durable_runner.run(input="test", timeout=20)
assert result.status is InvocationStatus.SUCCEEDED
# Find wait operations by name
wait_1 = result.get_wait("wait-1")
wait_2 = result.get_wait("wait-2")
assert wait_1.scheduled_end_timestamp is not None
assert wait_2.scheduled_end_timestamp is not None
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import software.amazon.lambda.durable.model.ExecutionStatus;
import software.amazon.awssdk.services.lambda.model.OperationStatus;
import software.amazon.awssdk.services.lambda.model.OperationType;
import software.amazon.lambda.durable.testing.LocalDurableTestRunner;
@Test
void testMultipleWaits() {
var runner = LocalDurableTestRunner.create(Object.class, new MultipleWaitExample());
var result = runner.runUntilComplete("test");
assertEquals(ExecutionStatus.SUCCEEDED, result.getStatus());
// Find all wait operations
var waitOps = result.getOperations().stream()
.filter(op -> op.getType() == OperationType.WAIT)
.toList();
assertEquals(2, waitOps.size());
// Verify both waits have names
var waitNames = waitOps.stream().map(op -> op.getName()).toList();
assertTrue(waitNames.contains("wait-1"));
assertTrue(waitNames.contains("wait-2"));
}
See also¶
- Steps - Execute business logic with automatic checkpointing
- Wait for Condition - Poll until a condition is met
- Callbacks - Wait for external system responses
- Getting Started - Learn the basics of durable functions