Bug 2538: Remove redundant Augmentation checks and tests
[controller.git] / opendaylight / commons / protocol-framework / src / main / java / org / opendaylight / protocol / framework / TimedReconnectStrategy.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.protocol.framework;
9
10 import io.netty.util.concurrent.EventExecutor;
11 import io.netty.util.concurrent.Future;
12
13 import java.util.concurrent.Callable;
14 import java.util.concurrent.TimeUnit;
15 import java.util.concurrent.TimeoutException;
16
17 import javax.annotation.concurrent.GuardedBy;
18 import javax.annotation.concurrent.ThreadSafe;
19
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22
23 import com.google.common.base.Preconditions;
24
25 /**
26  * Swiss army knife equivalent for reconnect strategies.
27  *
28  * This strategy continues to schedule reconnect attempts, each having to complete in a fixed time (connectTime).
29  *
30  * Initial sleep time is specified as minSleep. Each subsequent unsuccessful attempt multiplies this time by a constant
31  * factor (sleepFactor) -- this allows for either constant reconnect times (sleepFactor = 1), or various degrees of
32  * exponential back-off (sleepFactor > 1). Maximum sleep time between attempts can be capped to a specific value
33  * (maxSleep).
34  *
35  * The strategy can optionally give up based on two criteria:
36  *
37  * A preset number of connection retries (maxAttempts) has been reached, or
38  *
39  * A preset absolute deadline is reached (deadline nanoseconds, as reported by System.nanoTime(). In this specific case,
40  * both connectTime and maxSleep will be controlled such that the connection attempt is resolved as closely to the
41  * deadline as possible.
42  *
43  * Both these caps can be combined, with the strategy giving up as soon as the first one is reached.
44  */
45 @ThreadSafe
46 public final class TimedReconnectStrategy implements ReconnectStrategy {
47     private static final Logger LOG = LoggerFactory.getLogger(TimedReconnectStrategy.class);
48     private final EventExecutor executor;
49     private final Long deadline, maxAttempts, maxSleep;
50     private final double sleepFactor;
51     private final int connectTime;
52     private final long minSleep;
53
54     @GuardedBy("this")
55     private long attempts;
56
57     @GuardedBy("this")
58     private long lastSleep;
59
60     @GuardedBy("this")
61     private boolean scheduled;
62
63     public TimedReconnectStrategy(final EventExecutor executor, final int connectTime, final long minSleep, final double sleepFactor,
64             final Long maxSleep, final Long maxAttempts, final Long deadline) {
65         Preconditions.checkArgument(maxSleep == null || minSleep <= maxSleep);
66         Preconditions.checkArgument(sleepFactor >= 1);
67         Preconditions.checkArgument(connectTime >= 0);
68         this.executor = Preconditions.checkNotNull(executor);
69         this.deadline = deadline;
70         this.maxAttempts = maxAttempts;
71         this.minSleep = minSleep;
72         this.maxSleep = maxSleep;
73         this.sleepFactor = sleepFactor;
74         this.connectTime = connectTime;
75     }
76
77     @Override
78     public synchronized Future<Void> scheduleReconnect(final Throwable cause) {
79         LOG.debug("Connection attempt failed", cause);
80
81         // Check if a reconnect attempt is scheduled
82         Preconditions.checkState(!this.scheduled);
83
84         // Get a stable 'now' time for deadline calculations
85         final long now = System.nanoTime();
86
87         // Obvious stop conditions
88         if (this.maxAttempts != null && this.attempts >= this.maxAttempts) {
89             return this.executor.newFailedFuture(new Throwable("Maximum reconnection attempts reached"));
90         }
91         if (this.deadline != null && this.deadline <= now) {
92             return this.executor.newFailedFuture(new TimeoutException("Reconnect deadline reached"));
93         }
94
95         /*
96          * First connection attempt gets initialized to minimum sleep,
97          * each subsequent is exponentially backed off by sleepFactor.
98          */
99         if (this.attempts != 0) {
100             this.lastSleep *= this.sleepFactor;
101         } else {
102             this.lastSleep = this.minSleep;
103         }
104
105         // Cap the sleep time to maxSleep
106         if (this.maxSleep != null && this.lastSleep > this.maxSleep) {
107             LOG.debug("Capped sleep time from {} to {}", this.lastSleep, this.maxSleep);
108             this.lastSleep = this.maxSleep;
109         }
110
111         this.attempts++;
112
113         // Check if the reconnect attempt is within the deadline
114         if (this.deadline != null && this.deadline <= now + TimeUnit.MILLISECONDS.toNanos(this.lastSleep)) {
115             return this.executor.newFailedFuture(new TimeoutException("Next reconnect would happen after deadline"));
116         }
117
118         LOG.debug("Connection attempt {} sleeping for {} milliseconds", this.attempts, this.lastSleep);
119
120         // If we are not sleeping at all, return an already-succeeded future
121         if (this.lastSleep == 0) {
122             return this.executor.newSucceededFuture(null);
123         }
124
125         // Need to retain a final reference to this for locking purposes,
126         // also set the scheduled flag.
127         final Object lock = this;
128         this.scheduled = true;
129
130         // Schedule a task for the right time. It will also clear the flag.
131         return this.executor.schedule(new Callable<Void>() {
132             @Override
133             public Void call() throws TimeoutException {
134                 synchronized (lock) {
135                     Preconditions.checkState(TimedReconnectStrategy.this.scheduled);
136                     TimedReconnectStrategy.this.scheduled = false;
137                 }
138
139                 return null;
140             }
141         }, this.lastSleep, TimeUnit.MILLISECONDS);
142     }
143
144     @Override
145     public synchronized void reconnectSuccessful() {
146         Preconditions.checkState(!this.scheduled);
147         this.attempts = 0;
148     }
149
150     @Override
151     public int getConnectTimeout() throws TimeoutException {
152         int timeout = this.connectTime;
153
154         if (this.deadline != null) {
155
156             // If there is a deadline, we may need to cap the connect
157             // timeout to meet the deadline.
158             final long now = System.nanoTime();
159             if (now >= this.deadline) {
160                 throw new TimeoutException("Reconnect deadline already passed");
161             }
162
163             final long left = TimeUnit.NANOSECONDS.toMillis(this.deadline - now);
164             if (left < 1) {
165                 throw new TimeoutException("Connect timeout too close to deadline");
166             }
167
168             /*
169              * A bit of magic:
170              * - if time left is less than the timeout, set it directly
171              * - if there is no timeout, and time left is:
172              *      - less than maximum integer, set timeout to time left
173              *      - more than maximum integer, set timeout Integer.MAX_VALUE
174              */
175             if (timeout > left) {
176                 timeout = (int) left;
177             } else if (timeout == 0) {
178                 timeout = left <= Integer.MAX_VALUE ? (int) left : Integer.MAX_VALUE;
179             }
180         }
181         return timeout;
182     }
183 }