- What are wait operations?
- When to use wait operations
- Terminology
- Key features
- Getting started
- Method signature
- Duration helpers
- Naming wait operations
- Multiple sequential waits
- Understanding scheduled_end_timestamp
- Best practices
- FAQ
- Alternatives to wait operations
- Testing
- See also
Wait operation - A durable operation that pauses execution for a specified duration. Created using context.wait().
Duration - A time period specified in seconds, minutes, hours, or days using the Duration class.
Scheduled end timestamp - The Unix timestamp (in milliseconds) when the wait operation is scheduled to complete.
Suspend - The process of pausing execution and saving state. The Lambda function exits and resumes later.
Resume - The process of continuing execution after a wait completes. The SDK automatically invokes your function again.
Wait operations pause execution for a specified time. Your function suspends, the Lambda exits, and the system automatically resumes execution when the wait completes.
Unlike time.sleep(), waits don't consume Lambda execution time. Your function checkpoints, exits cleanly, and resumes later, even if the wait lasts hours or days.
Use context.wait() when you need a simple time-based delay.
Choose a different method if you need:
- Wait for external system response → Use
context.wait_for_callback() - Wait until a condition is met → Use
context.wait_for_condition() - Wait for a step to complete → Use
context.step()
- Durable pauses - Execution suspends and resumes automatically
- Flexible durations - Specify time in seconds, minutes, hours, or days
- Named operations - Identify waits by name for debugging and testing
- Automatic scheduling - The SDK handles timing and resumption
- Sequential waits - Chain multiple waits together
- No polling required - The system invokes your function when ready
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"When this function runs:
- The wait operation is checkpointed with a scheduled end time
- The Lambda function exits (suspends)
- After 5 seconds, the system automatically invokes your function again
- Execution resumes after the wait and returns "Wait completed"
def wait(
duration: Duration,
name: str | None = None,
) -> NoneParameters:
duration(Duration, required) - How long to wait. Must be at least 1 second. UseDuration.from_seconds(),Duration.from_minutes(),Duration.from_hours(), orDuration.from_days()to create a duration.name(str, optional) - A name for the wait operation. Useful for debugging and testing.
Returns: None
Raises:
ValidationError- If duration is less than 1 second
The Duration class provides convenient methods to specify time periods:
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))If using duration in seconds, you can also create a Duration directly:
# Wait for 300 seconds (5 minutes)
context.wait(duration=Duration(seconds=300))You can 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"Named waits are helpful when:
- You have multiple waits in your function
- You want to identify specific waits in test assertions
- You're debugging execution flow
Each wait operation has a scheduled_end_timestamp attribute that indicates when the wait is scheduled to complete. This timestamp is in Unix milliseconds.
You can access this timestamp when inspecting operations in tests or logs. The SDK uses this timestamp to determine when to resume your function.
The scheduled end time is calculated when the wait operation is first checkpointed:
- Current time + wait duration = scheduled end timestamp
When your function hits a wait, it terminates execution and doesn't incur compute charges during the wait period. The function resumes with a new invocation when the wait completes. Choose durations based on your workflow needs:
# Short wait for rate limiting
context.wait(duration=Duration.from_seconds(30))
# Medium wait for polling intervals
context.wait(duration=Duration.from_minutes(5))
# Long wait for scheduled tasks
context.wait(duration=Duration.from_hours(24))Note: If you have concurrent operations running (like parallel or map operations), those continue executing even when the main execution hits a wait. The function waits for all concurrent operations to complete before terminating.
Name your waits when you have multiple waits or complex logic:
# Good - clear purpose
context.wait(duration=Duration.from_seconds(60), name="rate_limit_cooldown")
context.wait(duration=Duration.from_minutes(5), name="polling_interval")
# Less clear - unnamed waits
context.wait(duration=Duration.from_seconds(60))
context.wait(duration=Duration.from_minutes(5))Use waits between steps to implement delays in your workflow:
@durable_execution
def handler(event: dict, context: DurableContext) -> dict:
# Start a process
job_id = context.step(start_job())
# Wait before checking status
context.wait(duration=Duration.from_seconds(30), name="initial_delay")
# Check status
status = context.step(check_job_status(job_id))
return {"job_id": job_id, "status": status}Waits must be at least 1 second. For very short delays, consider if you actually need a wait:
# Avoid - too short, will raise ValidationError
context.wait(duration=Duration.from_seconds(0))
# Minimum - 1 second
context.wait(duration=Duration.from_seconds(1))
# Better - use meaningful durations
context.wait(duration=Duration.from_seconds(5))There is an upper limit of 1 year - that's the maximum length of an execution.
The wait itself doesn't consume Lambda execution time, your function suspends and resumes later. However, consider cost implications of long-running executions.
No, once a wait operation is checkpointed, it will complete after the specified duration. Design your workflows with this in mind.
No, waits execute sequentially in the order they appear in your code. If you need parallel operations, use context.parallel() or context.map() instead.
Wait durations are approximate. The actual resume time depends on:
- System scheduling
- Lambda cold start time
- Current system load
You can, but we recommend using context.wait_for_condition() instead. It simplifies polling by handling the loop logic for you:
from aws_durable_execution_sdk_python.waits import WaitForConditionConfig, FixedWait
@durable_execution
def handler(event: dict, context: DurableContext) -> dict:
job_id = context.step(start_job())
# wait_for_condition handles the polling loop
def check_status(state, check_context):
status = get_job_status(state["job_id"])
return {"job_id": state["job_id"], "status": status}
result = context.wait_for_condition(
check=check_status,
config=WaitForConditionConfig(
initial_state={"job_id": job_id},
condition=lambda state: state["status"] == "completed",
wait_strategy=FixedWait(Duration.from_minutes(1))
)
)
return resultWhen you need to wait for an external system to respond, use context.wait_for_callback():
from aws_durable_execution_sdk_python import DurableContext, durable_execution
@durable_execution
def handler(event: dict, context: DurableContext) -> dict:
# Wait for external approval
def submit_for_approval(callback_id: str):
# Send callback_id to external approval system
send_to_approval_system(callback_id)
result = context.wait_for_callback(
submitter=submit_for_approval,
name="approval_wait"
)
return resultSee Callbacks for more details.
When you need to poll until a condition is met, use context.wait_for_condition():
from aws_durable_execution_sdk_python import DurableContext, durable_execution
from aws_durable_execution_sdk_python.waits import WaitForConditionConfig, ExponentialBackoff
from aws_durable_execution_sdk_python.config import Duration
@durable_execution
def handler(event: dict, context: DurableContext) -> dict:
# Poll until job completes
def check_job_status(state, check_context):
status = get_job_status(state["job_id"])
return {
"job_id": state["job_id"],
"status": status,
"done": status == "COMPLETED"
}
result = context.wait_for_condition(
check=check_job_status,
config=WaitForConditionConfig(
initial_state={"job_id": "job-123", "done": False},
condition=lambda state: state["done"],
wait_strategy=ExponentialBackoff(
initial_wait=Duration.from_seconds(5)
)
)
)
return resultYou can verify wait operations in your tests by inspecting the operations list:
import pytest
from aws_durable_execution_sdk_python.execution import InvocationStatus
from src.wait import wait
@pytest.mark.durable_execution(
handler=wait.handler,
lambda_function_name="Wait State",
)
def test_wait(durable_runner):
"""Test wait example."""
with durable_runner:
result = durable_runner.run(input="test", timeout=10)
assert result.status is InvocationStatus.SUCCEEDED
# Find the wait operation
wait_ops = [op for op in result.operations if op.operation_type.value == "WAIT"]
assert len(wait_ops) == 1
# Verify the wait has a scheduled end timestamp
wait_op = wait_ops[0]
assert wait_op.scheduled_end_timestamp is not NoneWhen testing functions with multiple waits, you can verify each wait individually:
@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 all wait operations
wait_ops = [op for op in result.operations if op.operation_type.value == "WAIT"]
assert len(wait_ops) == 2
# Verify both waits have names
wait_names = [op.name for op in wait_ops]
assert "wait-1" in wait_names
assert "wait-2" in wait_namesNamed waits are easier to identify in tests:
@pytest.mark.durable_execution(handler=wait_with_name.handler)
def test_named_wait(durable_runner):
"""Test wait with custom name."""
with durable_runner:
result = durable_runner.run(input="test", timeout=10)
assert result.status is InvocationStatus.SUCCEEDED
# Find the named wait operation
wait_ops = [op for op in result.operations
if op.operation_type.value == "WAIT" and op.name == "custom_wait"]
assert len(wait_ops) == 1- Steps - Execute business logic with automatic checkpointing
- Callbacks - Wait for external system responses
- Getting Started - Learn the basics of durable functions
See the LICENSE file for our project's licensing.