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