All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.kdgregory.logging.common.util.RetryManager2 Maven / Gradle / Ivy

Go to download

Contains the log-writers, along with utility classes used by both facades and appenders.

The newest version!
// Copyright (c) Keith D Gregory
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.kdgregory.logging.common.util;

import java.time.Duration;
import java.time.Instant;
import java.util.function.Consumer;
import java.util.function.Supplier;


/**
 *  Invokes a function, retrying with a delay until specified time, with optional
 *  exponential (2x) backoff. If the operation succeeds, its result is returned to
 *  the caller. On failure, it will return null or throw
 *  {@link TimeoutException} depending on configuration.
 *  

* The function must be a Java8 Supplier, which returns a value. Use * a lambda to wrap calls that don't return a value. *

* If the thread is interrupted (and the invoked operation is interruptible) then * the invocation will return null. *

* Instances are reusable and thread-safe, so can be created once to define the * "standard" retry logic for a given operation. */ public class RetryManager2 { private String operationName; private Duration initialDuration; private boolean isExponential; private boolean throwOnTimeout; // this is used by the variant of invoke that propagates exceptions // testing showed that simply passing a lambda would result in arbitrary timing private Consumer uncaughtHandler = new Consumer() { @Override public void accept(RuntimeException ex) { throw ex; } }; /** * Base constructor. * * @param operationName The name of the operation, used when throwing for timeout. * @param initialDuration The initial sleep duration, in milliseconds. * @param isExponential If true, then the sleep duration is doubled for every attempt. * If false, the sleeps are of equal length. * @param throwOnTimeout If true, then timeout causes an exception; if false, timeout * returns null. */ public RetryManager2(String operationName, Duration initialDuration, boolean isExponential, boolean throwOnTimeout) { this.operationName = operationName; this.initialDuration = initialDuration; this.isExponential = isExponential; this.throwOnTimeout = throwOnTimeout; } /** * Convenience constructor: uses exponential backoff and throws on timeout. * * @param operationName The name of the operation, used when throwing for timeout. * @param initialDuration The initial sleep duration, in milliseconds. */ public RetryManager2(String operationName, Duration initialDuration) { this(operationName, initialDuration, true, true); } /** * Invokes the passed function, passing any exceptions to the provided handler. */ public T invoke(Instant timeoutAt, Supplier supplier, Consumer exceptionHandler) { long currentSleep = initialDuration.toMillis(); long timeoutAtMillis = timeoutAt.toEpochMilli(); while (System.currentTimeMillis() < timeoutAtMillis) { try { T result = supplier.get(); if (result != null) return result; } catch (RuntimeException ex) { exceptionHandler.accept(ex); } sleepQuietly(currentSleep); if (isExponential) currentSleep *= 2; } if (throwOnTimeout) { throw new TimeoutException(operationName, timeoutAt, Instant.now()); } else { return null; } } /** * Invokes the passed function, propagating exceptions. */ public T invoke(Instant timeoutAt, Supplier supplier) { return invoke(timeoutAt, supplier, uncaughtHandler); } /** * Invokes the passed function, passing any exceptions to the provided handler. */ public T invoke(Duration timeout, Supplier supplier, Consumer exceptionHandler) { return invoke(Instant.now().plus(timeout), supplier, exceptionHandler); } /** * Invokes the passed function, propagating exceptions. */ public T invoke(Duration timeout, Supplier supplier) { return invoke(Instant.now().plus(timeout), supplier, uncaughtHandler); } /** * Sleeps for the specified duration (in milliseconds). Returns true if * the sleep completes normally, false if the thread is interrupted. */ public static boolean sleepQuietly(long duration) { try { Thread.sleep(duration); return true; } catch (InterruptedException ex) { return false; } } /** * This exception is thrown by the RetryManager on timeout. */ public static class TimeoutException extends RuntimeException { private static final long serialVersionUID = 1L; private String operation; private Instant expectedTimeout; private Instant actualTimeout; public TimeoutException(String operation, Instant expectedTimeout, Instant actualTimeout) { this.operation = operation; this.expectedTimeout = expectedTimeout; this.actualTimeout = actualTimeout; } @Override public String getMessage() { return operation + " did not complete by " + expectedTimeout + " (now " + actualTimeout + ")"; } /** * Returns the name of the operation that timed out. May be null. */ public String getOperation() { return operation; } /** * Returns the instant provided to the RetryManager as a timeout limit. */ public Instant getExpectedTimeout() { return expectedTimeout; } /** * Returns the instant when the RetryManager decided to abort the operation. */ public Instant getActualTimeout() { return actualTimeout; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy