001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.named.support;
020
021import java.util.concurrent.Callable;
022import java.util.concurrent.TimeUnit;
023import java.util.function.Predicate;
024
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028/**
029 * Retry helper: retries given {@code Callable} as long as it returns {@code null} (interpreted
030 * as "no answer yet") or given time passes. This helper implements similar semantics regarding
031 * caller threads as {@link java.util.concurrent.locks.Lock#tryLock(long, TimeUnit)} method does:
032 * blocks the caller thread until operation return non-{@code null} value within the given waiting
033 * time and the current thread has not been {@linkplain Thread#interrupt interrupted}.
034 *
035 * @since 1.7.3
036 */
037public final class Retry {
038    private static final Logger LOGGER = LoggerFactory.getLogger(Retry.class);
039
040    /**
041     * Marker interface to apply onto exceptions to make them "never retried" when thrown. This shortcuts checks with
042     * predicate, if used.
043     *
044     * @since 1.9.13
045     */
046    public interface DoNotRetry {}
047
048    private Retry() {
049        // no instances
050    }
051
052    /**
053     * Retries for given amount of time (time, unit) the passed in operation, sleeping given
054     * {@code sleepMills} between retries. In case operation returns {@code null}, it is assumed
055     * "is not done yet" state, so retry will happen (if time barrier allows). If time barrier
056     * passes, and still {@code null} ("is not done yet") is returned from operation, the
057     * {@code defaultResult} is returned.
058     */
059    public static <R> R retry(
060            final long time,
061            final TimeUnit unit,
062            final long sleepMillis,
063            final Callable<R> operation,
064            final Predicate<Exception> retryPredicate,
065            final R defaultResult)
066            throws InterruptedException {
067        long now = System.nanoTime();
068        final long barrier = now + unit.toNanos(time);
069        int attempt = 1;
070        R result = null;
071        while (now < barrier && result == null) {
072            try {
073                result = operation.call();
074                if (result == null) {
075                    LOGGER.trace("Retry attempt {}: no result", attempt);
076                    Thread.sleep(sleepMillis);
077                }
078            } catch (InterruptedException e) {
079                throw e;
080            } catch (Exception e) {
081                LOGGER.trace("Retry attempt {}: operation failure", attempt, e);
082                if (e instanceof DoNotRetry) {
083                    if (e instanceof RuntimeException) {
084                        throw (RuntimeException) e;
085                    } else {
086                        throw new IllegalStateException(e);
087                    }
088                }
089                if (retryPredicate != null && !retryPredicate.test(e)) {
090                    throw new IllegalStateException(e);
091                }
092            }
093            now = System.nanoTime();
094            attempt++;
095        }
096        return result == null ? defaultResult : result;
097    }
098
099    /**
100     * Retries attempting max given times the passed in operation, sleeping given
101     * {@code sleepMills} between retries. In case operation returns {@code null}, it is assumed
102     * "is not done yet" state, so retry will happen (if attempt count allows). If all attempts
103     * used, and still {@code null} ("is not done yet") is returned from operation, the
104     * {@code defaultResult} is returned.
105     * <p>
106     * Just to clear things up: 5 attempts is really 4 retries (once do it and retry 4 times). 0 attempts means
107     * "do not even try it", and this method returns without doing anything.
108     */
109    public static <R> R retry(
110            final int attempts,
111            final long sleepMillis,
112            final Callable<R> operation,
113            final Predicate<Exception> retryPredicate,
114            final R defaultResult)
115            throws InterruptedException {
116        int attempt = 1;
117        R result = null;
118        while (attempt <= attempts && result == null) {
119            try {
120                result = operation.call();
121                if (result == null) {
122                    LOGGER.trace("Retry attempt {}: no result", attempt);
123                    Thread.sleep(sleepMillis);
124                }
125            } catch (InterruptedException e) {
126                throw e;
127            } catch (Exception e) {
128                LOGGER.trace("Retry attempt {}: operation failure", attempt, e);
129                if (e instanceof DoNotRetry) {
130                    if (e instanceof RuntimeException) {
131                        throw (RuntimeException) e;
132                    } else {
133                        throw new IllegalStateException(e);
134                    }
135                }
136                if (retryPredicate != null && !retryPredicate.test(e)) {
137                    throw new IllegalStateException(e);
138                }
139            }
140            attempt++;
141        }
142        return result == null ? defaultResult : result;
143    }
144}