RetryInProcessExample.java

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.amazon.lambda.durable.examples.step;

import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.lambda.durable.DurableContext;
import software.amazon.lambda.durable.DurableFuture;
import software.amazon.lambda.durable.DurableHandler;
import software.amazon.lambda.durable.config.StepConfig;
import software.amazon.lambda.durable.retry.JitterStrategy;
import software.amazon.lambda.durable.retry.RetryStrategies;

/**
 * Example demonstrating in-process retry behavior with concurrent operations.
 *
 * <p>This example shows:
 *
 * <ul>
 *   <li>An async step that fails and retries while other work continues
 *   <li>A long-running synchronous step that keeps the process busy
 *   <li>Retry happens in-process without suspension because main thread is active
 * </ul>
 */
public class RetryInProcessExample extends DurableHandler<Object, String> {

    private static final Logger logger = LoggerFactory.getLogger(RetryInProcessExample.class);

    private final AtomicInteger attemptCount = new AtomicInteger(0);

    @Override
    public String handleRequest(Object input, DurableContext context) {
        logger.info("Starting retry in-process example");

        // Start async step that will fail and retry
        DurableFuture<String> asyncStep = context.stepAsync(
                "flaky-async-operation",
                String.class,
                stepCtx -> {
                    int attempt = attemptCount.incrementAndGet();
                    logger.info(
                            "Async operation attempt #{} in thread: {}",
                            attempt,
                            Thread.currentThread().getName());

                    // Fail first 2 attempts, succeed on 3rd
                    if (attempt < 3) {
                        var message = "Async operation failing on attempt " + attempt;
                        logger.warn(message);
                        throw new RuntimeException(message);
                    } else {
                        var message = "Async operation succeeded on attempt " + attempt;
                        logger.info(message);
                        return message;
                    }
                },
                StepConfig.builder()
                        .retryStrategy(RetryStrategies.exponentialBackoff(
                                5, Duration.ofSeconds(1), Duration.ofSeconds(10), 2.0, JitterStrategy.NONE))
                        .build());

        // Long-running synchronous step that keeps process busy
        // This prevents suspension during async step retries
        String syncResult = context.step("long-running-operation", String.class, stepCtx -> {
            logger.info(
                    "Starting long-running operation (10 seconds) in thread: {}",
                    Thread.currentThread().getName());
            try {
                Thread.sleep(10000); // 10 seconds
                logger.info("Long-running operation completed");
                return "Long operation completed";
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Long operation interrupted", e);
            }
        });

        // Get async step result (should be ready by now due to retries during sync
        // step)
        logger.info("Getting async step result");
        String asyncResult = asyncStep.get();

        logger.info("Sync result: {}", syncResult);
        logger.info("Async result: {}", asyncResult);

        return "Retry in-process completed - Sync: " + syncResult + ", Async: " + asyncResult;
    }
}