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

io.helidon.config.SimpleRetryPolicy Maven / Gradle / Ivy

There is a newer version: 4.1.1
Show 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 - 2024 Weber Informatics LLC | Privacy Policy