com.netflix.curator.SessionFailRetryLoop Maven / Gradle / Ivy
Show all versions of curator-client Show documentation
package com.netflix.curator;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import java.io.Closeable;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
/**
*
* See {@link RetryLoop} for the main details on retry loops. All Curator/ZooKeeper operations
* should be done in a retry loop.
*
*
*
* The standard retry loop treats session failure as a type of connection failure. i.e. the fact
* that it is a session failure isn't considered. This can be problematic if you are performing
* a series of operations that rely on ephemeral nodes. If the session fails after the ephemeral
* node has been created, future Curator/ZooKeeper operations may succeed even though the
* ephemeral node has been removed by ZooKeeper.
*
*
*
* Here's an example:
*
* - You create an ephemeral/sequential node as a kind of lock/marker
* - You perform some other operations
* - The session fails for some reason
* - You attempt to create a node assuming that the lock/marker still exists
*
* - Curator will notice the session failure and try to reconnect
* - In most cases, the reconnect will succeed and, thus, the node creation will succeed
* even though the ephemeral node will have been deleted by ZooKeeper.
*
*
*
*
*
* The SessionFailRetryLoop prevents this type of scenario. When a session failure is detected,
* the thread is marked as failed which will cause all future Curator operations to fail. The
* SessionFailRetryLoop will then either retry the entire
* set of operations or fail (depending on {@link SessionFailRetryLoop.Mode})
*
*
* Canonical usage:
*
* SessionFailRetryLoop retryLoop = client.newSessionFailRetryLoop(mode);
* retryLoop.start();
* try
* {
* while ( retryLoop.shouldContinue() )
* {
* try
* {
* // do work
* }
* catch ( Exception e )
* {
* retryLoop.takeException(e);
* }
* }
* }
* finally
* {
* retryLoop.close();
* }
*
*/
public class SessionFailRetryLoop implements Closeable
{
private final CuratorZookeeperClient client;
private final Mode mode;
private final Thread ourThread = Thread.currentThread();
private final AtomicBoolean sessionHasFailed = new AtomicBoolean(false);
private final AtomicBoolean isDone = new AtomicBoolean(false);
private final RetryLoop retryLoop;
private final Watcher watcher = new Watcher()
{
@Override
public void process(WatchedEvent event)
{
if ( event.getState() == Event.KeeperState.Expired )
{
sessionHasFailed.set(true);
failedSessionThreads.add(ourThread);
}
}
};
private static final Set failedSessionThreads = Sets.newSetFromMap(Maps.newConcurrentMap());
public static class SessionFailedException extends Exception
{
}
public enum Mode
{
/**
* If the session fails, retry the entire set of operations when {@link SessionFailRetryLoop#shouldContinue()}
* is called
*/
RETRY,
/**
* If the session fails, throw {@link KeeperException.SessionExpiredException} when
* {@link SessionFailRetryLoop#shouldContinue()} is called
*/
FAIL
}
/**
* Convenience utility: creates a "session fail" retry loop calling the given proc
*
* @param client Zookeeper
* @param mode how to handle session failures
* @param proc procedure to call with retry
* @param return type
* @return procedure result
* @throws Exception any non-retriable errors
*/
public static T callWithRetry(CuratorZookeeperClient client, Mode mode, Callable proc) throws Exception
{
T result = null;
SessionFailRetryLoop retryLoop = client.newSessionFailRetryLoop(mode);
retryLoop.start();
try
{
while ( retryLoop.shouldContinue() )
{
try
{
result = proc.call();
}
catch ( Exception e )
{
retryLoop.takeException(e);
}
}
}
finally
{
retryLoop.close();
}
return result;
}
SessionFailRetryLoop(CuratorZookeeperClient client, Mode mode)
{
this.client = client;
this.mode = mode;
retryLoop = client.newRetryLoop();
}
static boolean sessionForThreadHasFailed()
{
return (failedSessionThreads.size() > 0) && failedSessionThreads.contains(Thread.currentThread());
}
/**
* SessionFailRetryLoop must be started
*/
public void start()
{
Preconditions.checkState(Thread.currentThread().equals(ourThread), "Not in the correct thread");
client.addParentWatcher(watcher);
}
/**
* If true is returned, make an attempt at the set of operations
*
* @return true/false
*/
public boolean shouldContinue()
{
boolean localIsDone = isDone.getAndSet(true);
return !localIsDone;
}
/**
* Must be called in a finally handler when done with the loop
*/
@Override
public void close()
{
Preconditions.checkState(Thread.currentThread().equals(ourThread), "Not in the correct thread");
failedSessionThreads.remove(ourThread);
client.removeParentWatcher(watcher);
}
/**
* Pass any caught exceptions here
*
* @param exception the exception
* @throws Exception if not retry-able or the retry policy returned negative
*/
public void takeException(Exception exception) throws Exception
{
Preconditions.checkState(Thread.currentThread().equals(ourThread), "Not in the correct thread");
boolean passUp = true;
if ( sessionHasFailed.get() )
{
switch ( mode )
{
case RETRY:
{
sessionHasFailed.set(false);
failedSessionThreads.remove(ourThread);
if ( exception instanceof SessionFailedException )
{
isDone.set(false);
passUp = false;
}
break;
}
case FAIL:
{
break;
}
}
}
if ( passUp )
{
retryLoop.takeException(exception);
}
}
}