
io.helidon.config.SimpleRetryPolicy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of helidon-config Show documentation
Show all versions of helidon-config Show documentation
Configuration Core module.
The newest version!
/*
* Copyright (c) 2020, 2022 Oracle and/or its affiliates.
*
* 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 io.helidon.config;
import java.lang.System.Logger.Level;
import java.time.Duration;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import io.helidon.config.spi.RetryPolicy;
import static java.lang.Math.min;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
/**
* A default retry policy implementation with {@link ScheduledExecutorService}.
* Following attributes can be configured:
*
* - number of retries (excluding the first invocation)
* - delay between the invocations
* - a delay multiplication factor
* - a timeout for the individual invocation
* - an overall timeout
* - an executor service
*
*/
public final class SimpleRetryPolicy implements RetryPolicy {
private static final System.Logger LOGGER = System.getLogger(SimpleRetryPolicy.class.getName());
private final int retries;
private final Duration delay;
private final double delayFactor;
private final Duration callTimeout;
private final Duration overallTimeout;
private final ScheduledExecutorService executorService;
private volatile ScheduledFuture> future;
private SimpleRetryPolicy(Builder builder) {
this.retries = builder.retries;
this.delay = builder.delay;
this.delayFactor = builder.delayFactor;
this.callTimeout = builder.callTimeout;
this.overallTimeout = builder.overallTimeout;
this.executorService = builder.executorService;
}
/**
* Fluent API builder for {@link io.helidon.config.SimpleRetryPolicy}.
*
* @return a new builder
*/
public static Builder builder() {
return new Builder();
}
/**
* Initializes retry policy instance from configuration properties.
*
* Mandatory {@code properties}, see {@link RetryPolicies#repeat(int)}:
*
* - {@code retries} - type {@code int}
*
* Optional {@code properties}:
*
* - {@code delay} - type {@link Duration}, see {@link Builder#delay(Duration)}
* - {@code delay-factor} - type {@code double}, see {@link Builder#delayFactor(double)}
* - {@code call-timeout} - type {@link Duration}, see {@link Builder#callTimeout(Duration)}
* - {@code overall-timeout} - type {@link Duration}, see {@link Builder#overallTimeout(Duration)}
*
*
* @param metaConfig meta-configuration used to initialize returned polling strategy builder instance from.
* @return new instance of polling strategy builder described by {@code metaConfig}
* @throws MissingValueException in case the configuration tree does not contain all expected sub-nodes
* required by the mapper implementation to provide instance of Java type.
* @throws ConfigMappingException in case the mapper fails to map the (existing) configuration tree represented by the
* supplied configuration node to an instance of a given Java type.
* @see PollingStrategies#regular(Duration)
*/
public static SimpleRetryPolicy create(Config metaConfig) {
return builder().config(metaConfig).build();
}
@Override
public T execute(Supplier call) throws ConfigException {
Duration currentDelay = Duration.ZERO;
long overallTimeoutsLeft = overallTimeout.toMillis();
Throwable last = null;
for (int i = 0; i <= retries; i++) {
try {
LOGGER.log(Level.DEBUG, "next delay: " + currentDelay);
overallTimeoutsLeft -= currentDelay.toMillis();
if (overallTimeoutsLeft < 0) {
LOGGER.log(Level.DEBUG, "overall timeout left [ms]: " + overallTimeoutsLeft);
throw new ConfigException(
"Cannot schedule the next call, the current delay would exceed the overall timeout.");
}
ScheduledFuture localFuture = executorService.schedule(call::get, currentDelay.toMillis(), MILLISECONDS);
future = localFuture;
return localFuture.get(min(currentDelay.plus(callTimeout).toMillis(), overallTimeoutsLeft), MILLISECONDS);
} catch (ConfigException e) {
throw e;
} catch (CancellationException e) {
throw new ConfigException("An invocation has been canceled.", e);
} catch (InterruptedException e) {
throw new ConfigException("An invocation has been interrupted.", e);
} catch (TimeoutException e) {
throw new ConfigException("A timeout has been reached.", e);
} catch (Throwable t) {
last = t;
}
currentDelay = nextDelay(i, currentDelay);
}
throw new ConfigException("All repeated calls failed.", last);
}
Duration nextDelay(int invocation, Duration currentDelay) {
if (invocation == 0) {
return delay;
} else {
return Duration.ofMillis((long) (currentDelay.toMillis() * delayFactor));
}
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (future != null) {
if (!future.isDone() && !future.isCancelled()) {
return future.cancel(mayInterruptIfRunning);
}
}
return false;
}
/**
* Number of retries.
* @return retries
*/
public int retries() {
return retries;
}
/**
* Delay between retries.
*
* @return delay
*/
public Duration delay() {
return delay;
}
/**
* Delay multiplication factor.
* @return delay factor
*/
public double delayFactor() {
return delayFactor;
}
/**
* Timeout of the call.
* @return call timeout
*/
public Duration callTimeout() {
return callTimeout;
}
/**
* Overall timeout.
* @return overall timeout
*/
public Duration overallTimeout() {
return overallTimeout;
}
/**
* Fluent API builder for {@link io.helidon.config.SimpleRetryPolicy}.
*/
public static final class Builder implements io.helidon.common.Builder {
private int retries = 3;
private Duration delay = Duration.ofMillis(200);
private double delayFactor = 2;
private Duration callTimeout = Duration.ofMillis(500);
private Duration overallTimeout = Duration.ofSeconds(2);
private ScheduledExecutorService executorService;
private Builder() {
}
@Override
public SimpleRetryPolicy build() {
if (null == executorService) {
this.executorService = Executors.newSingleThreadScheduledExecutor(new ConfigThreadFactory("retry-policy"));
}
return new SimpleRetryPolicy(this);
}
/**
* Update this builder from meta configuration.
*
* Mandatory {@code properties}, see {@link RetryPolicies#repeat(int)}:
*
* - {@code retries} - type {@code int}
*
* Optional {@code properties}:
*
* - {@code delay} - type {@link Duration}, see {@link #delay(Duration)}
* - {@code delay-factor} - type {@code double}, see {@link #delayFactor(double)}
* - {@code call-timeout} - type {@link Duration}, see {@link #callTimeout(Duration)}
* - {@code overall-timeout} - type {@link Duration}, see {@link #overallTimeout(Duration)}
*
*
* @param metaConfig meta configuration used to update this builder
* @return updated builder instance
*/
public Builder config(Config metaConfig) {
// retries
metaConfig.get("retries").asInt().ifPresent(this::retries);
// delay
metaConfig.get("delay").as(Duration.class)
.ifPresent(this::delay);
// delay-factor
metaConfig.get("delay-factor").asDouble()
.ifPresent(this::delayFactor);
// call-timeout
metaConfig.get("call-timeout").as(Duration.class)
.ifPresent(this::callTimeout);
// overall-timeout
metaConfig.get("overall-timeout").as(Duration.class)
.ifPresent(this::overallTimeout);
return this;
}
/**
* Number of retries (excluding the first invocation).
*
* @param retries how many times to retry
* @return updated builder instance
*/
public Builder retries(int retries) {
this.retries = retries;
return this;
}
/**
* Delay between the invocations.
*
* @param delay delay between the invocations
* @return updated builder instance
*/
public Builder delay(Duration delay) {
this.delay = delay;
return this;
}
/**
* A delay multiplication factor.
*
* @param delayFactor a delay multiplication factor
* @return updated builder instance
*/
public Builder delayFactor(double delayFactor) {
this.delayFactor = delayFactor;
return this;
}
/**
* Timeout for the individual invocation.
*
* @param callTimeout a timeout for the individual invocation
* @return updated builder instance
*/
public Builder callTimeout(Duration callTimeout) {
this.callTimeout = callTimeout;
return this;
}
/**
* Overall timeout.
*
* @param overallTimeout an overall timeout
* @return updated builder instance
*/
public Builder overallTimeout(Duration overallTimeout) {
this.overallTimeout = overallTimeout;
return this;
}
/**
* An executor service to schedule retries and run timed operations on.
*
* @param executorService service
* @return updated builder instance
*/
public Builder executorService(ScheduledExecutorService executorService) {
this.executorService = executorService;
return this;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy