Merge "Cleanup root pom "name"."
[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 @Deprecated
46 @ThreadSafe
47 public final class TimedReconnectStrategy implements ReconnectStrategy {
48     private static final Logger LOG = LoggerFactory.getLogger(TimedReconnectStrategy.class);
49     private final EventExecutor executor;
50     private final Long deadline, maxAttempts, maxSleep;
51     private final double sleepFactor;
52     private final int connectTime;
53     private final long minSleep;
54
55     @GuardedBy("this")
56     private long attempts;
57
58     @GuardedBy("this")
59     private long lastSleep;
60
61     @GuardedBy("this")
62     private boolean scheduled;
63
64     public TimedReconnectStrategy(final EventExecutor executor, final int connectTime, final long minSleep, final double sleepFactor,
65             final Long maxSleep, final Long maxAttempts, final Long deadline) {
66         Preconditions.checkArgument(maxSleep == null || minSleep <= maxSleep);
67         Preconditions.checkArgument(sleepFactor >= 1);
68         Preconditions.checkArgument(connectTime >= 0);
69         this.executor = Preconditions.checkNotNull(executor);
70         this.deadline = deadline;
71         this.maxAttempts = maxAttempts;
72         this.minSleep = minSleep;
73         this.maxSleep = maxSleep;
74         this.sleepFactor = sleepFactor;
75         this.connectTime = connectTime;
76     }
77
78     @Override
79     public synchronized Future<Void> scheduleReconnect(final Throwable cause) {
80         LOG.debug("Connection attempt failed", cause);
81
82         // Check if a reconnect attempt is scheduled
83         Preconditions.checkState(!this.scheduled);
84
85         // Get a stable 'now' time for deadline calculations
86         final long now = System.nanoTime();
87
88         // Obvious stop conditions
89         if (this.maxAttempts != null && this.attempts >= this.maxAttempts) {
90             return this.executor.newFailedFuture(new Throwable("Maximum reconnection attempts reached"));
91         }
92         if (this.deadline != null && this.deadline <= now) {
93             return this.executor.newFailedFuture(new TimeoutException("Reconnect deadline reached"));
94         }
95
96         /*
97          * First connection attempt gets initialized to minimum sleep,
98          * each subsequent is exponentially backed off by sleepFactor.
99          */
100         if (this.attempts != 0) {
101             this.lastSleep *= this.sleepFactor;
102         } else {
103             this.lastSleep = this.minSleep;
104         }
105
106         // Cap the sleep time to maxSleep
107         if (this.maxSleep != null && this.lastSleep > this.maxSleep) {
108             LOG.debug("Capped sleep time from {} to {}", this.lastSleep, this.maxSleep);
109             this.lastSleep = this.maxSleep;
110         }
111
112         this.attempts++;
113
114         // Check if the reconnect attempt is within the deadline
115         if (this.deadline != null && this.deadline <= now + TimeUnit.MILLISECONDS.toNanos(this.lastSleep)) {
116             return this.executor.newFailedFuture(new TimeoutException("Next reconnect would happen after deadline"));
117         }
118
119         LOG.debug("Connection attempt {} sleeping for {} milliseconds", this.attempts, this.lastSleep);
120
121         // If we are not sleeping at all, return an already-succeeded future
122         if (this.lastSleep == 0) {
123             return this.executor.newSucceededFuture(null);
124         }
125
126         // Need to retain a final reference to this for locking purposes,
127         // also set the scheduled flag.
128         final Object lock = this;
129         this.scheduled = true;
130
131         // Schedule a task for the right time. It will also clear the flag.
132         return this.executor.schedule(new Callable<Void>() {
133             @Override
134             public Void call() throws TimeoutException {
135                 synchronized (lock) {
136                     Preconditions.checkState(TimedReconnectStrategy.this.scheduled);
137                     TimedReconnectStrategy.this.scheduled = false;
138                 }
139
140                 return null;
141             }
142         }, this.lastSleep, TimeUnit.MILLISECONDS);
143     }
144
145     @Override
146     public synchronized void reconnectSuccessful() {
147         Preconditions.checkState(!this.scheduled);
148         this.attempts = 0;
149     }
150
151     @Override
152     public int getConnectTimeout() throws TimeoutException {
153         int timeout = this.connectTime;
154
155         if (this.deadline != null) {
156
157             // If there is a deadline, we may need to cap the connect
158             // timeout to meet the deadline.
159             final long now = System.nanoTime();
160             if (now >= this.deadline) {
161                 throw new TimeoutException("Reconnect deadline already passed");
162             }
163
164             final long left = TimeUnit.NANOSECONDS.toMillis(this.deadline - now);
165             if (left < 1) {
166                 throw new TimeoutException("Connect timeout too close to deadline");
167             }
168
169             /*
170              * A bit of magic:
171              * - if time left is less than the timeout, set it directly
172              * - if there is no timeout, and time left is:
173              *      - less than maximum integer, set timeout to time left
174              *      - more than maximum integer, set timeout Integer.MAX_VALUE
175              */
176             if (timeout > left) {
177                 timeout = (int) left;
178             } else if (timeout == 0) {
179                 timeout = left <= Integer.MAX_VALUE ? (int) left : Integer.MAX_VALUE;
180             }
181         }
182         return timeout;
183     }
184 }