2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.netconf.nettyutil;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12 import static java.util.Objects.requireNonNull;
14 import io.netty.util.concurrent.EventExecutor;
15 import io.netty.util.concurrent.Future;
16 import java.util.concurrent.TimeUnit;
17 import java.util.concurrent.TimeoutException;
18 import org.checkerframework.checker.lock.qual.GuardedBy;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
23 * Swiss army knife equivalent for reconnect strategies. This class is thread-safe.
26 * This strategy continues to schedule reconnect attempts, each having to complete in a fixed time (connectTime).
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
35 * The strategy can optionally give up based on two criteria:
38 * A preset number of connection retries (maxAttempts) has been reached, or
41 * A preset absolute deadline is reached (deadline nanoseconds, as reported by System.nanoTime(). In this specific case,
42 * both connectTime and maxSleep will be controlled such that the connection attempt is resolved as closely to the
43 * deadline as possible.
46 * Both these caps can be combined, with the strategy giving up as soon as the first one is reached.
49 public final class TimedReconnectStrategy implements ReconnectStrategy {
50 private static final Logger LOG = LoggerFactory.getLogger(TimedReconnectStrategy.class);
51 private final EventExecutor executor;
52 private final Long deadline;
53 private final Long maxAttempts;
54 private final Long maxSleep;
55 private final double sleepFactor;
56 private final int connectTime;
57 private final long minSleep;
60 private long attempts;
63 private long lastSleep;
66 private boolean scheduled;
68 public TimedReconnectStrategy(final EventExecutor executor, final int connectTime, final long minSleep,
69 final double sleepFactor, final Long maxSleep, final Long maxAttempts, final Long deadline) {
70 checkArgument(maxSleep == null || minSleep <= maxSleep);
71 checkArgument(sleepFactor >= 1);
72 checkArgument(connectTime >= 0);
73 this.executor = requireNonNull(executor);
74 this.deadline = deadline;
75 this.maxAttempts = maxAttempts;
76 this.minSleep = minSleep;
77 this.maxSleep = maxSleep;
78 this.sleepFactor = sleepFactor;
79 this.connectTime = connectTime;
83 public synchronized Future<Void> scheduleReconnect(final Throwable cause) {
84 LOG.debug("Connection attempt failed", cause);
86 // Check if a reconnect attempt is scheduled
87 checkState(!this.scheduled);
89 // Get a stable 'now' time for deadline calculations
90 final long now = System.nanoTime();
92 // Obvious stop conditions
93 if (this.maxAttempts != null && this.attempts >= this.maxAttempts) {
94 return this.executor.newFailedFuture(new Throwable("Maximum reconnection attempts reached"));
96 if (this.deadline != null && this.deadline <= now) {
97 return this.executor.newFailedFuture(new TimeoutException("Reconnect deadline reached"));
101 * First connection attempt gets initialized to minimum sleep,
102 * each subsequent is exponentially backed off by sleepFactor.
104 if (this.attempts != 0) {
105 this.lastSleep *= this.sleepFactor;
107 this.lastSleep = this.minSleep;
110 // Cap the sleep time to maxSleep
111 if (this.maxSleep != null && this.lastSleep > this.maxSleep) {
112 LOG.debug("Capped sleep time from {} to {}", this.lastSleep, this.maxSleep);
113 this.lastSleep = this.maxSleep;
118 // Check if the reconnect attempt is within the deadline
119 if (this.deadline != null && this.deadline <= now + TimeUnit.MILLISECONDS.toNanos(this.lastSleep)) {
120 return this.executor.newFailedFuture(new TimeoutException("Next reconnect would happen after deadline"));
123 LOG.debug("Connection attempt {} sleeping for {} milliseconds", this.attempts, this.lastSleep);
125 // If we are not sleeping at all, return an already-succeeded future
126 if (this.lastSleep == 0) {
127 return this.executor.newSucceededFuture(null);
130 // Set the scheduled flag.
131 this.scheduled = true;
133 // Schedule a task for the right time. It will also clear the flag.
134 return this.executor.schedule(() -> {
135 synchronized (TimedReconnectStrategy.this) {
136 checkState(TimedReconnectStrategy.this.scheduled);
137 TimedReconnectStrategy.this.scheduled = false;
141 }, this.lastSleep, TimeUnit.MILLISECONDS);
145 public synchronized void reconnectSuccessful() {
146 checkState(!this.scheduled);
151 public int getConnectTimeout() throws TimeoutException {
152 int timeout = this.connectTime;
154 if (this.deadline != null) {
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");
163 final long left = TimeUnit.NANOSECONDS.toMillis(this.deadline - now);
165 throw new TimeoutException("Connect timeout too close to deadline");
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
175 if (timeout > left) {
176 timeout = (int) left;
177 } else if (timeout == 0) {
178 timeout = left <= Integer.MAX_VALUE ? (int) left : Integer.MAX_VALUE;