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 }