View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.named.support;
20  
21  import java.util.concurrent.Callable;
22  import java.util.concurrent.TimeUnit;
23  import java.util.function.Predicate;
24  
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  
28  /**
29   * Retry helper: retries given {@code Callable} as long as it returns {@code null} (interpreted
30   * as "no answer yet") or given time passes. This helper implements similar semantics regarding
31   * caller threads as {@link java.util.concurrent.locks.Lock#tryLock(long, TimeUnit)} method does:
32   * blocks the caller thread until operation return non-{@code null} value within the given waiting
33   * time and the current thread has not been {@linkplain Thread#interrupt interrupted}.
34   *
35   * @since 1.7.3
36   */
37  public final class Retry {
38      private static final Logger LOGGER = LoggerFactory.getLogger(Retry.class);
39  
40      /**
41       * Marker interface to apply onto exceptions to make them "never retried" when thrown. This shortcuts checks with
42       * predicate, if used.
43       *
44       * @since 1.9.13
45       */
46      public interface DoNotRetry {}
47  
48      private Retry() {
49          // no instances
50      }
51  
52      /**
53       * Retries for given amount of time (time, unit) the passed in operation, sleeping given
54       * {@code sleepMills} between retries. In case operation returns {@code null}, it is assumed
55       * "is not done yet" state, so retry will happen (if time barrier allows). If time barrier
56       * passes, and still {@code null} ("is not done yet") is returned from operation, the
57       * {@code defaultResult} is returned.
58       */
59      public static <R> R retry(
60              final long time,
61              final TimeUnit unit,
62              final long sleepMillis,
63              final Callable<R> operation,
64              final Predicate<Exception> retryPredicate,
65              final R defaultResult)
66              throws InterruptedException {
67          long now = System.nanoTime();
68          final long barrier = now + unit.toNanos(time);
69          int attempt = 1;
70          R result = null;
71          while (now < barrier && result == null) {
72              try {
73                  result = operation.call();
74                  if (result == null) {
75                      LOGGER.trace("Retry attempt {}: no result", attempt);
76                      Thread.sleep(sleepMillis);
77                  }
78              } catch (InterruptedException e) {
79                  throw e;
80              } catch (Exception e) {
81                  LOGGER.trace("Retry attempt {}: operation failure", attempt, e);
82                  if (e instanceof DoNotRetry) {
83                      if (e instanceof RuntimeException) {
84                          throw (RuntimeException) e;
85                      } else {
86                          throw new IllegalStateException(e);
87                      }
88                  }
89                  if (retryPredicate != null && !retryPredicate.test(e)) {
90                      throw new IllegalStateException(e);
91                  }
92              }
93              now = System.nanoTime();
94              attempt++;
95          }
96          return result == null ? defaultResult : result;
97      }
98  
99      /**
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 }