Skip to content

[Bug]: retry count increment #288

@yaythomas

Description

@yaythomas

Expected Behavior

The attempts_made parameter passed to the wait_strategy function should represent the current attempt being executed, starting at 1 and incrementing monotonically:

Execution 1: attempts_made = 1
Execution 2: attempts_made = 2
Execution 3: attempts_made = 3
Execution 4: attempts_made = 4
Execution 5: attempts_made = 5

This matches the JavaScript SDK behavior where:

const currentAttempt = (stepData?.StepDetails?.Attempt ?? 0) + 1;

Reference:

Actual Behavior

The attempts_made parameter incorrectly reports:

Execution 1: attempts_made = 1  ✓ (correct)
Execution 2: attempts_made = 1  ✗ (WRONG - should be 2)
Execution 3: attempts_made = 2  ✗ (WRONG - should be 3)
Execution 4: attempts_made = 3  ✗ (WRONG - should be 4)
Execution 5: attempts_made = 4  ✗ (WRONG - should be 5)

The first attempt is counted twice, causing all subsequent attempts to be off by one.

Steps to Reproduce

import random
from aws_durable_execution_sdk_python import DurableContext, durable_execution
from aws_durable_execution_sdk_python.config import Duration
from aws_durable_execution_sdk_python.waits import WaitForConditionConfig, WaitForConditionDecision

@durable_execution
def lambda_handler(event: dict, context: DurableContext) -> dict:
    def check_status(status, context):
        context.logger.info(f"check_status called with status: {status}")
        if random.random() < 0.80:
            status = "complete"
        else:
            status = "incomplete"
        return {"status": status}
    
    def wait_strategy(result, attempts_made: int) -> WaitForConditionDecision:
        context.logger.info(f"wait_strategy called with attempts_made: {attempts_made}")
        
        # Check if we've exceeded max attempts (5 iterations)
        if attempts_made >= 5:
            return WaitForConditionDecision.stop_polling()
        
        # For the first attempt, delay by 10 seconds
        if attempts_made == 1:
            delay = Duration.from_seconds(10)
        elif result["status"] == "complete":
            return WaitForConditionDecision.stop_polling()
        else:
            # For subsequent attempts, delay by 2 seconds
            delay = Duration.from_seconds(2)
        
        return WaitForConditionDecision.continue_waiting(delay)
    
    result = context.wait_for_condition(
        name="poll",
        check=check_status,
        config=WaitForConditionConfig(
            initial_state={"status":"incomplete"},
            wait_strategy=wait_strategy
        )
    )
    return result

SDK Version

1.1.1

Python Version

3.14

Is this a regression?

No

Last Working Version

No response

Additional Context

Root Cause

In src/aws_durable_execution_sdk_python/operation/wait_for_condition.py lines 157-159:

# Get attempt number
attempt: int = 1
if checkpointed_result.operation and checkpointed_result.operation.step_details:
    attempt = checkpointed_result.operation.step_details.attempt

The code reads step_details.attempt directly, but this value represents how many attempts have been checkpointed (completed attempts), not what the current attempt is.

The semantic meaning:

  • step_details.attempt = 0 → No attempts checkpointed yet → Current attempt should be 1
  • step_details.attempt = 1 → One attempt checkpointed → Current attempt should be 2
  • step_details.attempt = N → N attempts checkpointed → Current attempt should be N+1

Suggested Fix

Add 1 to the checkpointed attempt count to get the current attempt number:

# Get attempt number - current attempt is checkpointed attempts + 1
attempt: int = 1
if checkpointed_result.operation and checkpointed_result.operation.step_details:
    attempt = checkpointed_result.operation.step_details.attempt + 1

This matches the JavaScript SDK implementation:

// TypeScript wait-for-condition-handler.ts line 176
const currentAttempt = (stepData?.StepDetails?.Attempt ?? 0) + 1;

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions