PollingStrategies.java

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

import java.time.Duration;
import java.util.Objects;

/** Factory class for creating common polling strategies. */
public class PollingStrategies {

    /** Preset polling strategies for common use cases. */
    public static class Presets {

        /**
         * Default polling strategy: - Base interval: 1 second - Backoff rate: 2x - Jitter: FULL - Max interval 10
         * second
         */
        public static final PollingStrategy DEFAULT =
                exponentialBackoff(Duration.ofMillis(1000), 2.0, JitterStrategy.FULL, Duration.ofSeconds(10));
    }

    /**
     * Creates an exponential backoff polling strategy.
     *
     * <p>The delay calculation follows the formula: delay = jitter(baseInterval × backoffRate^attempt)
     *
     * @param baseInterval Base delay before first poll
     * @param backoffRate Multiplier for exponential backoff (must be positive)
     * @param jitter Jitter strategy to apply to delays
     * @param maxInterval Maximum delay between polls
     * @return PollingStrategy implementing exponential backoff with jitter
     */
    public static PollingStrategy exponentialBackoff(
            Duration baseInterval, double backoffRate, JitterStrategy jitter, Duration maxInterval) {
        Objects.requireNonNull(jitter, "jitter must not be null");
        Objects.requireNonNull(baseInterval, "base interval must not be null");
        Objects.requireNonNull(maxInterval, "max interval must not be null");
        if (backoffRate <= 0) {
            throw new IllegalArgumentException("backoffRate must be positive");
        }

        if (baseInterval.isNegative() || baseInterval.isZero()) {
            throw new IllegalArgumentException("baseInterval must be positive");
        }

        if (maxInterval.isNegative() || maxInterval.isZero()) {
            throw new IllegalArgumentException("maxInterval must be positive");
        }

        return (attempt) -> {
            double delayMs = baseInterval.toMillis() * Math.pow(backoffRate, attempt);
            delayMs = Math.min(jitter.apply(delayMs), maxInterval.toMillis());
            return Duration.ofMillis(Math.round(delayMs));
        };
    }

    /**
     * Creates a fixed-delay polling strategy that uses the same interval for every attempt.
     *
     * @param interval Fixed delay between polls
     * @return PollingStrategy with fixed delay
     */
    public static PollingStrategy fixedDelay(Duration interval) {
        Objects.requireNonNull(interval, "interval must not be null");
        if (interval.isNegative() || interval.isZero()) {
            throw new IllegalArgumentException("interval must be positive");
        }
        return (attempt) -> interval;
    }
}