Fixed some sonar warnings.
[bgpcep.git] / framework / src / main / java / org / opendaylight / protocol / framework / TimedReconnectStrategy.java
1 package org.opendaylight.protocol.framework;
2
3 import io.netty.util.concurrent.EventExecutor;
4 import io.netty.util.concurrent.Future;
5
6 import java.util.concurrent.Callable;
7 import java.util.concurrent.TimeUnit;
8 import java.util.concurrent.TimeoutException;
9
10 import javax.annotation.concurrent.GuardedBy;
11 import javax.annotation.concurrent.ThreadSafe;
12
13 import org.slf4j.Logger;
14 import org.slf4j.LoggerFactory;
15
16 import com.google.common.base.Preconditions;
17
18 /**
19  * Swiss army knife equivalent for reconnect strategies.
20  * 
21  * This strategy continues to schedule reconnect attempts, each having to complete in a fixed time (connectTime).
22  * 
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
26  * (maxSleep).
27  * 
28  * The strategy can optionally give up based on two criteria:
29  * 
30  * A preset number of connection retries (maxAttempts) has been reached, or
31  * 
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.
35  * 
36  * Both these caps can be combined, with the strategy giving up as soon as the first one is reached.
37  */
38 @ThreadSafe
39 public final class TimedReconnectStrategy implements ReconnectStrategy {
40         private static final Logger logger = 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;
46
47         @GuardedBy("this")
48         private long attempts;
49
50         @GuardedBy("this")
51         private long lastSleep;
52
53         @GuardedBy("this")
54         private boolean scheduled;
55
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;
68         }
69
70         @Override
71         public synchronized Future<Void> scheduleReconnect(final Throwable cause) {
72                 logger.debug("Connection attempt failed", cause);
73
74                 // Check if a reconnect attempt is scheduled
75                 Preconditions.checkState(!this.scheduled);
76
77                 // Get a stable 'now' time for deadline calculations
78                 final long now = System.nanoTime();
79
80                 // Obvious stop conditions
81                 if (this.maxAttempts != null && this.attempts >= this.maxAttempts) {
82                         return this.executor.newFailedFuture(new Throwable("Maximum reconnection attempts reached"));
83                 }
84                 if (this.deadline != null && this.deadline <= now) {
85                         return this.executor.newFailedFuture(new TimeoutException("Reconnect deadline reached"));
86                 }
87
88                 /*
89                  * First connection attempt gets initialized to minimum sleep,
90                  * each subsequent is exponentially backed off by sleepFactor.
91                  */
92                 if (this.attempts != 0) {
93                         this.lastSleep *= this.sleepFactor;
94                 } else {
95                         this.lastSleep = this.minSleep;
96                 }
97
98                 // Cap the sleep time to maxSleep
99                 if (this.maxSleep != null && this.lastSleep > this.maxSleep) {
100                         this.lastSleep = this.maxSleep;
101                 }
102
103                 // Check if the reconnect attempt is within the deadline
104                 if (this.deadline != null && this.deadline <= now + TimeUnit.MILLISECONDS.toNanos(this.lastSleep)) {
105                         return this.executor.newFailedFuture(new TimeoutException("Next reconnect would happen after deadline"));
106                 }
107
108                 // If we are not sleeping at all, return an already-succeeded future
109                 if (this.lastSleep == 0) {
110                         return this.executor.newSucceededFuture(null);
111                 }
112
113                 // Need to retain a final reference to this for locking purposes,
114                 // also set the scheduled flag.
115                 final Object lock = this;
116                 this.scheduled = true;
117
118                 // Schedule a task for the right time. It will also clear the flag.
119                 return this.executor.schedule(new Callable<Void>() {
120                         @Override
121                         public Void call() throws TimeoutException {
122                                 synchronized (lock) {
123                                         Preconditions.checkState(TimedReconnectStrategy.this.scheduled);
124                                         TimedReconnectStrategy.this.scheduled = false;
125                                 }
126
127                                 return null;
128                         }
129                 }, this.lastSleep, TimeUnit.MILLISECONDS);
130         }
131
132         @Override
133         public synchronized void reconnectSuccessful() {
134                 Preconditions.checkState(!this.scheduled);
135                 this.attempts = 0;
136         }
137
138         @Override
139         public int getConnectTimeout() throws TimeoutException {
140                 int timeout = this.connectTime;
141
142                 if (this.deadline != null) {
143
144                         // If there is a deadline, we may need to cap the connect
145                         // timeout to meet the deadline.
146                         final long now = System.nanoTime();
147                         if (now >= this.deadline) {
148                                 throw new TimeoutException("Reconnect deadline already passed");
149                         }
150
151                         final long left = TimeUnit.NANOSECONDS.toMillis(this.deadline - now);
152                         if (left < 1) {
153                                 throw new TimeoutException("Connect timeout too close to deadline");
154                         }
155
156                         /*
157                          * A bit of magic:
158                          * - if time left is less than the timeout, set it directly
159                          * - if there is no timeout, and time left is:
160                          *      - less than maximum integer, set timeout to time left
161                          *      - more than maximum integer, set timeout Integer.MAX_VALUE
162                          */
163                         if (timeout > left) {
164                                 timeout = (int) left;
165                         } else if (timeout == 0) {
166                                 timeout = left <= Integer.MAX_VALUE ? (int) left : Integer.MAX_VALUE;
167                         }
168                 }
169                 return timeout;
170         }
171 }