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
22 * complete in a fixed time (connectTime).
24 * Initial sleep time is specified as minSleep. Each subsequent unsuccessful
25 * attempt multiplies this time by a constant factor (sleepFactor) -- this
26 * allows for either constant reconnect times (sleepFactor = 1), or various
27 * degrees of exponential back-off (sleepFactor > 1). Maximum sleep time
28 * between attempts can be capped to a specific value (maxSleep).
30 * The strategy can optionally give up based on two criteria:
32 * A preset number of connection retries (maxAttempts) has been reached, or
34 * A preset absolute deadline is reached (deadline nanoseconds, as reported
35 * by System.nanoTime(). In this specific case, both connectTime and maxSleep
36 * will be controlled such that the connection attempt is resolved as closely
37 * to the deadline as possible.
39 * Both these caps can be combined, with the strategy giving up as soon as the
40 * first one is reached.
43 public final class TimedReconnectStrategy implements ReconnectStrategy {
44 private static final Logger logger = LoggerFactory.getLogger(TimedReconnectStrategy.class);
45 private final EventExecutor executor;
46 private final Long deadline, maxAttempts, maxSleep;
47 private final double sleepFactor;
48 private final int connectTime;
49 private final long minSleep;
52 private long attempts;
55 private long lastSleep;
58 private boolean scheduled;
60 public TimedReconnectStrategy(final EventExecutor executor, final int connectTime,
61 final long minSleep, final double sleepFactor, final Long maxSleep,
62 final Long maxAttempts, final Long deadline) {
63 Preconditions.checkArgument(maxSleep == null || minSleep <= maxSleep);
64 Preconditions.checkArgument(sleepFactor >= 1);
65 Preconditions.checkArgument(connectTime >= 0);
66 this.executor = Preconditions.checkNotNull(executor);
67 this.deadline = deadline;
68 this.maxAttempts = maxAttempts;
69 this.minSleep = minSleep;
70 this.maxSleep = maxSleep;
71 this.sleepFactor = sleepFactor;
72 this.connectTime = connectTime;
76 public synchronized Future<Void> scheduleReconnect(final Throwable cause) {
77 logger.debug("Connection attempt failed", cause);
79 // Check if a reconnect attempt is scheduled
80 Preconditions.checkState(scheduled == false);
82 // Get a stable 'now' time for deadline calculations
83 final long now = System.nanoTime();
85 // Obvious stop conditions
86 if (maxAttempts != null && attempts >= maxAttempts) {
87 return executor.newFailedFuture(new Throwable("Maximum reconnection attempts reached"));
89 if (deadline != null && deadline <= now) {
90 return executor.newFailedFuture(new TimeoutException("Reconnect deadline reached"));
94 * First connection attempt gets initialized to minimum sleep,
95 * each subsequent is exponentially backed off by sleepFactor.
98 lastSleep *= sleepFactor;
100 lastSleep = minSleep;
103 // Cap the sleep time to maxSleep
104 if (maxSleep != null && lastSleep > maxSleep) {
105 lastSleep = maxSleep;
108 // Check if the reconnect attempt is within the deadline
109 if (deadline != null && deadline <= now + TimeUnit.MILLISECONDS.toNanos(lastSleep)) {
110 return executor.newFailedFuture(new TimeoutException("Next reconnect would happen after deadline"));
113 // If we are not sleeping at all, return an already-succeeded future
114 if (lastSleep == 0) {
115 return 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;
123 // Schedule a task for the right time. It will also clear the flag.
124 return executor.schedule(new Callable<Void>() {
126 public Void call() throws TimeoutException {
127 synchronized (lock) {
128 Preconditions.checkState(scheduled == true);
134 }, lastSleep, TimeUnit.MILLISECONDS);
138 public synchronized void reconnectSuccessful() {
139 Preconditions.checkState(scheduled == false);
144 public int getConnectTimeout() throws TimeoutException {
145 int timeout = connectTime;
147 if (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 >= deadline) {
153 throw new TimeoutException("Reconnect deadline already passed");
156 final long left = TimeUnit.NANOSECONDS.toMillis(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;