X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=opendaylight%2Fcommons%2Fprotocol-framework%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fprotocol%2Fframework%2FTimedReconnectStrategy.java;fp=opendaylight%2Fcommons%2Fprotocol-framework%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fprotocol%2Fframework%2FTimedReconnectStrategy.java;h=8bb326821dcd98d4e2a300b2ce595d07824e55e4;hb=391180fcde6a7a2336182e346e24a1ff9f754062;hp=0000000000000000000000000000000000000000;hpb=1208cf638d47c7908604ab04eaf0e1dbaa67c9cb;p=controller.git diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/TimedReconnectStrategy.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/TimedReconnectStrategy.java new file mode 100644 index 0000000000..8bb326821d --- /dev/null +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/TimedReconnectStrategy.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.protocol.framework; + +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Future; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.ThreadSafe; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +/** + * Swiss army knife equivalent for reconnect strategies. + * + * This strategy continues to schedule reconnect attempts, each having to complete in a fixed time (connectTime). + * + * Initial sleep time is specified as minSleep. Each subsequent unsuccessful attempt multiplies this time by a constant + * factor (sleepFactor) -- this allows for either constant reconnect times (sleepFactor = 1), or various degrees of + * exponential back-off (sleepFactor > 1). Maximum sleep time between attempts can be capped to a specific value + * (maxSleep). + * + * The strategy can optionally give up based on two criteria: + * + * A preset number of connection retries (maxAttempts) has been reached, or + * + * A preset absolute deadline is reached (deadline nanoseconds, as reported by System.nanoTime(). In this specific case, + * both connectTime and maxSleep will be controlled such that the connection attempt is resolved as closely to the + * deadline as possible. + * + * Both these caps can be combined, with the strategy giving up as soon as the first one is reached. + */ +@ThreadSafe +public final class TimedReconnectStrategy implements ReconnectStrategy { + private static final Logger LOG = LoggerFactory.getLogger(TimedReconnectStrategy.class); + private final EventExecutor executor; + private final Long deadline, maxAttempts, maxSleep; + private final double sleepFactor; + private final int connectTime; + private final long minSleep; + + @GuardedBy("this") + private long attempts; + + @GuardedBy("this") + private long lastSleep; + + @GuardedBy("this") + private boolean scheduled; + + public TimedReconnectStrategy(final EventExecutor executor, final int connectTime, final long minSleep, final double sleepFactor, + final Long maxSleep, final Long maxAttempts, final Long deadline) { + Preconditions.checkArgument(maxSleep == null || minSleep <= maxSleep); + Preconditions.checkArgument(sleepFactor >= 1); + Preconditions.checkArgument(connectTime >= 0); + this.executor = Preconditions.checkNotNull(executor); + this.deadline = deadline; + this.maxAttempts = maxAttempts; + this.minSleep = minSleep; + this.maxSleep = maxSleep; + this.sleepFactor = sleepFactor; + this.connectTime = connectTime; + } + + @Override + public synchronized Future scheduleReconnect(final Throwable cause) { + LOG.debug("Connection attempt failed", cause); + + // Check if a reconnect attempt is scheduled + Preconditions.checkState(!this.scheduled); + + // Get a stable 'now' time for deadline calculations + final long now = System.nanoTime(); + + // Obvious stop conditions + if (this.maxAttempts != null && this.attempts >= this.maxAttempts) { + return this.executor.newFailedFuture(new Throwable("Maximum reconnection attempts reached")); + } + if (this.deadline != null && this.deadline <= now) { + return this.executor.newFailedFuture(new TimeoutException("Reconnect deadline reached")); + } + + /* + * First connection attempt gets initialized to minimum sleep, + * each subsequent is exponentially backed off by sleepFactor. + */ + if (this.attempts != 0) { + this.lastSleep *= this.sleepFactor; + } else { + this.lastSleep = this.minSleep; + } + + // Cap the sleep time to maxSleep + if (this.maxSleep != null && this.lastSleep > this.maxSleep) { + LOG.debug("Capped sleep time from {} to {}", this.lastSleep, this.maxSleep); + this.lastSleep = this.maxSleep; + } + + this.attempts++; + + // Check if the reconnect attempt is within the deadline + if (this.deadline != null && this.deadline <= now + TimeUnit.MILLISECONDS.toNanos(this.lastSleep)) { + return this.executor.newFailedFuture(new TimeoutException("Next reconnect would happen after deadline")); + } + + LOG.debug("Connection attempt {} sleeping for {} milliseconds", this.attempts, this.lastSleep); + + // If we are not sleeping at all, return an already-succeeded future + if (this.lastSleep == 0) { + return this.executor.newSucceededFuture(null); + } + + // Need to retain a final reference to this for locking purposes, + // also set the scheduled flag. + final Object lock = this; + this.scheduled = true; + + // Schedule a task for the right time. It will also clear the flag. + return this.executor.schedule(new Callable() { + @Override + public Void call() throws TimeoutException { + synchronized (lock) { + Preconditions.checkState(TimedReconnectStrategy.this.scheduled); + TimedReconnectStrategy.this.scheduled = false; + } + + return null; + } + }, this.lastSleep, TimeUnit.MILLISECONDS); + } + + @Override + public synchronized void reconnectSuccessful() { + Preconditions.checkState(!this.scheduled); + this.attempts = 0; + } + + @Override + public int getConnectTimeout() throws TimeoutException { + int timeout = this.connectTime; + + if (this.deadline != null) { + + // If there is a deadline, we may need to cap the connect + // timeout to meet the deadline. + final long now = System.nanoTime(); + if (now >= this.deadline) { + throw new TimeoutException("Reconnect deadline already passed"); + } + + final long left = TimeUnit.NANOSECONDS.toMillis(this.deadline - now); + if (left < 1) { + throw new TimeoutException("Connect timeout too close to deadline"); + } + + /* + * A bit of magic: + * - if time left is less than the timeout, set it directly + * - if there is no timeout, and time left is: + * - less than maximum integer, set timeout to time left + * - more than maximum integer, set timeout Integer.MAX_VALUE + */ + if (timeout > left) { + timeout = (int) left; + } else if (timeout == 0) { + timeout = left <= Integer.MAX_VALUE ? (int) left : Integer.MAX_VALUE; + } + } + return timeout; + } +}