1 package org.opendaylight.protocol.framework;
3 import io.netty.util.concurrent.EventExecutor;
4 import io.netty.util.concurrent.Future;
6 import java.util.concurrent.Callable;
7 import java.util.concurrent.TimeUnit;
8 import java.util.concurrent.TimeoutException;
10 import javax.annotation.concurrent.GuardedBy;
11 import javax.annotation.concurrent.ThreadSafe;
13 import org.slf4j.Logger;
14 import org.slf4j.LoggerFactory;
16 import com.google.common.base.Preconditions;
19 * Swiss army knife equivalent for reconnect strategies.
21 * This strategy continues to schedule reconnect attempts, each having to complete in a fixed time (connectTime).
23 * Initial sleep time is specified as minSleep. Each subsequent unsuccessful attempt multiplies this time by a constant
24 * factor (sleepFactor) -- this allows for either constant reconnect times (sleepFactor = 1), or various degrees of
25 * exponential back-off (sleepFactor > 1). Maximum sleep time between attempts can be capped to a specific value
28 * The strategy can optionally give up based on two criteria:
30 * A preset number of connection retries (maxAttempts) has been reached, or
32 * A preset absolute deadline is reached (deadline nanoseconds, as reported by System.nanoTime(). In this specific case,
33 * both connectTime and maxSleep will be controlled such that the connection attempt is resolved as closely to the
34 * deadline as possible.
36 * Both these caps can be combined, with the strategy giving up as soon as the first one is reached.
39 public final class TimedReconnectStrategy implements ReconnectStrategy {
40 private static final Logger LOG = LoggerFactory.getLogger(TimedReconnectStrategy.class);
41 private final EventExecutor executor;
42 private final Long deadline, maxAttempts, maxSleep;
43 private final double sleepFactor;
44 private final int connectTime;
45 private final long minSleep;
48 private long attempts;
51 private long lastSleep;
54 private boolean scheduled;
56 public TimedReconnectStrategy(final EventExecutor executor, final int connectTime, final long minSleep, final double sleepFactor,
57 final Long maxSleep, final Long maxAttempts, final Long deadline) {
58 Preconditions.checkArgument(maxSleep == null || minSleep <= maxSleep);
59 Preconditions.checkArgument(sleepFactor >= 1);
60 Preconditions.checkArgument(connectTime >= 0);
61 this.executor = Preconditions.checkNotNull(executor);
62 this.deadline = deadline;
63 this.maxAttempts = maxAttempts;
64 this.minSleep = minSleep;
65 this.maxSleep = maxSleep;
66 this.sleepFactor = sleepFactor;
67 this.connectTime = connectTime;
71 public synchronized Future<Void> scheduleReconnect(final Throwable cause) {
72 LOG.debug("Connection attempt failed", cause);
74 // Check if a reconnect attempt is scheduled
75 Preconditions.checkState(!this.scheduled);
77 // Get a stable 'now' time for deadline calculations
78 final long now = System.nanoTime();
80 // Obvious stop conditions
81 if (this.maxAttempts != null && this.attempts >= this.maxAttempts) {
82 return this.executor.newFailedFuture(new Throwable("Maximum reconnection attempts reached"));
84 if (this.deadline != null && this.deadline <= now) {
85 return this.executor.newFailedFuture(new TimeoutException("Reconnect deadline reached"));
89 * First connection attempt gets initialized to minimum sleep,
90 * each subsequent is exponentially backed off by sleepFactor.
92 if (this.attempts != 0) {
93 this.lastSleep *= this.sleepFactor;
95 this.lastSleep = this.minSleep;
98 // Cap the sleep time to maxSleep
99 if (this.maxSleep != null && this.lastSleep > this.maxSleep) {
100 LOG.debug("Capped sleep time from {} to {}", this.lastSleep, this.maxSleep);
101 this.lastSleep = this.maxSleep;
106 // Check if the reconnect attempt is within the deadline
107 if (this.deadline != null && this.deadline <= now + TimeUnit.MILLISECONDS.toNanos(this.lastSleep)) {
108 return this.executor.newFailedFuture(new TimeoutException("Next reconnect would happen after deadline"));
111 LOG.debug("Connection attempt {} sleeping for {} milliseconds", this.attempts, this.lastSleep);
113 // If we are not sleeping at all, return an already-succeeded future
114 if (this.lastSleep == 0) {
115 return this.executor.newSucceededFuture(null);
118 // Need to retain a final reference to this for locking purposes,
119 // also set the scheduled flag.
120 final Object lock = this;
121 this.scheduled = true;
123 // Schedule a task for the right time. It will also clear the flag.
124 return this.executor.schedule(new Callable<Void>() {
126 public Void call() throws TimeoutException {
127 synchronized (lock) {
128 Preconditions.checkState(TimedReconnectStrategy.this.scheduled);
129 TimedReconnectStrategy.this.scheduled = false;
134 }, this.lastSleep, TimeUnit.MILLISECONDS);
138 public synchronized void reconnectSuccessful() {
139 Preconditions.checkState(!this.scheduled);
144 public int getConnectTimeout() throws TimeoutException {
145 int timeout = this.connectTime;
147 if (this.deadline != null) {
149 // If there is a deadline, we may need to cap the connect
150 // timeout to meet the deadline.
151 final long now = System.nanoTime();
152 if (now >= this.deadline) {
153 throw new TimeoutException("Reconnect deadline already passed");
156 final long left = TimeUnit.NANOSECONDS.toMillis(this.deadline - now);
158 throw new TimeoutException("Connect timeout too close to deadline");
163 * - if time left is less than the timeout, set it directly
164 * - if there is no timeout, and time left is:
165 * - less than maximum integer, set timeout to time left
166 * - more than maximum integer, set timeout Integer.MAX_VALUE
168 if (timeout > left) {
169 timeout = (int) left;
170 } else if (timeout == 0) {
171 timeout = left <= Integer.MAX_VALUE ? (int) left : Integer.MAX_VALUE;