Bump odlparent 2.0.2 to 2.0.4
[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.TimeUnit;
14 import java.util.concurrent.TimeoutException;
15
16 import javax.annotation.concurrent.GuardedBy;
17 import javax.annotation.concurrent.ThreadSafe;
18
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 import com.google.common.base.Preconditions;
23
24 /**
25  * Swiss army knife equivalent for reconnect strategies.
26  *
27  * This strategy continues to schedule reconnect attempts, each having to complete in a fixed time (connectTime).
28  *
29  * Initial sleep time is specified as minSleep. Each subsequent unsuccessful attempt multiplies this time by a constant
30  * factor (sleepFactor) -- this allows for either constant reconnect times (sleepFactor = 1), or various degrees of
31  * exponential back-off (sleepFactor > 1). Maximum sleep time between attempts can be capped to a specific value
32  * (maxSleep).
33  *
34  * The strategy can optionally give up based on two criteria:
35  *
36  * A preset number of connection retries (maxAttempts) has been reached, or
37  *
38  * A preset absolute deadline is reached (deadline nanoseconds, as reported by System.nanoTime(). In this specific case,
39  * both connectTime and maxSleep will be controlled such that the connection attempt is resolved as closely to the
40  * deadline as possible.
41  *
42  * Both these caps can be combined, with the strategy giving up as soon as the first one is reached.
43  */
44 @Deprecated
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(() -> {
132             synchronized (lock) {
133                 Preconditions.checkState(TimedReconnectStrategy.this.scheduled);
134                 TimedReconnectStrategy.this.scheduled = false;
135             }
136
137             return null;
138         }, this.lastSleep, TimeUnit.MILLISECONDS);
139     }
140
141     @Override
142     public synchronized void reconnectSuccessful() {
143         Preconditions.checkState(!this.scheduled);
144         this.attempts = 0;
145     }
146
147     @Override
148     public int getConnectTimeout() throws TimeoutException {
149         int timeout = this.connectTime;
150
151         if (this.deadline != null) {
152
153             // If there is a deadline, we may need to cap the connect
154             // timeout to meet the deadline.
155             final long now = System.nanoTime();
156             if (now >= this.deadline) {
157                 throw new TimeoutException("Reconnect deadline already passed");
158             }
159
160             final long left = TimeUnit.NANOSECONDS.toMillis(this.deadline - now);
161             if (left < 1) {
162                 throw new TimeoutException("Connect timeout too close to deadline");
163             }
164
165             /*
166              * A bit of magic:
167              * - if time left is less than the timeout, set it directly
168              * - if there is no timeout, and time left is:
169              *      - less than maximum integer, set timeout to time left
170              *      - more than maximum integer, set timeout Integer.MAX_VALUE
171              */
172             if (timeout > left) {
173                 timeout = (int) left;
174             } else if (timeout == 0) {
175                 timeout = left <= Integer.MAX_VALUE ? (int) left : Integer.MAX_VALUE;
176             }
177         }
178         return timeout;
179     }
180 }