package edu.uw.zookeeper.protocol.client; import java.nio.channels.ClosedChannelException; import java.util.Iterator; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import net.engio.mbassy.common.IConcurrentSet; import net.engio.mbassy.common.StrongConcurrentSet; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.zookeeper.KeeperException; import com.google.common.base.MoreObjects; import com.google.common.collect.Iterators; import com.google.common.collect.Sets; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import edu.uw.zookeeper.common.Actor; import edu.uw.zookeeper.common.Automaton; import edu.uw.zookeeper.common.Automatons; import edu.uw.zookeeper.common.Automatons.AutomatonListener; import edu.uw.zookeeper.common.Eventful; import edu.uw.zookeeper.common.LoggingFutureListener; import edu.uw.zookeeper.common.Pair; import edu.uw.zookeeper.common.Promise; import edu.uw.zookeeper.common.PromiseTask; import edu.uw.zookeeper.common.SettableFuturePromise; import edu.uw.zookeeper.common.TimeValue; import edu.uw.zookeeper.data.Operations; import edu.uw.zookeeper.net.Connection; import edu.uw.zookeeper.protocol.Message; import edu.uw.zookeeper.protocol.ConnectMessage; import edu.uw.zookeeper.protocol.Operation; import edu.uw.zookeeper.protocol.Operation.ProtocolResponse; import edu.uw.zookeeper.protocol.ProtocolConnection; import edu.uw.zookeeper.protocol.ProtocolRequestMessage; import edu.uw.zookeeper.protocol.ProtocolState; import edu.uw.zookeeper.protocol.Session; import edu.uw.zookeeper.protocol.SessionListener; import edu.uw.zookeeper.protocol.TimeOutActor; import edu.uw.zookeeper.protocol.TimeOutParameters; import edu.uw.zookeeper.protocol.proto.IWatcherEvent; import edu.uw.zookeeper.protocol.proto.OpCode; import edu.uw.zookeeper.protocol.proto.OpCodeXid; public abstract class AbstractConnectionClientExecutor< I extends Operation.Request, V extends Operation.ProtocolResponse<?>, T extends Future<?>, C extends ProtocolConnection<? super Message.ClientSession, ? extends Operation.Response,?,?,?>, O> implements ConnectionClientExecutor<I,V,SessionListener,C>, Connection.Listener<Operation.Response>, FutureCallback<O>, AutomatonListener<ProtocolState>, Actor<T> { public static <I extends Operation.Request, T extends ConnectionClientExecutor<I,?,?,?>> void disconnect(T client) throws InterruptedException, ExecutionException, TimeoutException, KeeperException { Disconnect<I,T> instance = Disconnect.create(client); if (client.session().isDone()) { instance.get(client.session().get().getTimeOut(), TimeUnit.MILLISECONDS); } else { instance.get(); } } public static final class Disconnect<I extends Operation.Request, T extends ConnectionClientExecutor<I,?,?,?>> extends PromiseTask<T, Boolean> implements Automatons.AutomatonListener<ProtocolState>, Connection.Listener<Object>, Runnable, FutureCallback<Operation.ProtocolResponse<?>> { public static <I extends Operation.Request, T extends ConnectionClientExecutor<I,?,?,?>> Disconnect<I,T> create( T client) { Disconnect<I,T> instance = new Disconnect<I,T>(client, SettableFuturePromise.<Boolean>create()); instance.run(); return instance; } protected Disconnect( T client, Promise<Boolean> promise) { super(client, promise); task().connection().subscribe(this); task().connection().codec().subscribe(this); addListener(this, MoreExecutors.directExecutor()); } @Override public void run() { if (isDone()) { task().connection().unsubscribe(this); task().connection().codec().unsubscribe(this); } else if (task().session().isDone()) { ProtocolState state = task().connection().codec().state(); handleAutomatonTransition(Automaton.Transition.create(state, state)); } else { task().session().addListener(this, MoreExecutors.directExecutor()); } } @SuppressWarnings("unchecked") @Override public void handleAutomatonTransition( Automaton.Transition<ProtocolState> transition) { switch (transition.to()) { case CONNECTED: if (transition.from() == transition.to()) { Futures.addCallback( task().submit( (I) ProtocolRequestMessage.of( 0, Operations.Requests.disconnect().build())), this); } break; case DISCONNECTED: set(Boolean.TRUE); break; case ERROR: onFailure(new KeeperException.SessionExpiredException()); break; default: { if (transition.from() == transition.to()) { Connection.State state = task().connection().state(); handleConnectionState(Automaton.Transition.create(state, state)); } break; } } } @Override public void handleConnectionState( Automaton.Transition<Connection.State> state) { if (state.to() == Connection.State.CONNECTION_CLOSED) { setException(new ClosedChannelException()); } } @Override public void handleConnectionRead(Object message) { } @Override public void onSuccess(Operation.ProtocolResponse<?> result) { if (result.record().opcode() == OpCode.CLOSE_SESSION) { set(Boolean.TRUE); } } @Override public void onFailure(Throwable t) { setException(t); } } protected final ListenableFuture<ConnectMessage.Response> session; protected final C connection; protected final Listeners listeners; protected final TimeOutServer<Operation.Response> timer; protected final AtomicReference<Throwable> failure; protected AbstractConnectionClientExecutor( ListenableFuture<ConnectMessage.Response> session, C connection, TimeValue timeOut, ScheduledExecutorService scheduler) { this(session, connection, Listeners.create(), TimeOutServer.<Operation.Response>newTimeOutServer(TimeOutParameters.milliseconds(timeOut.value(TimeUnit.MILLISECONDS)), scheduler), new AtomicReference<Throwable>()); } protected AbstractConnectionClientExecutor( ListenableFuture<ConnectMessage.Response> session, C connection, Listeners listeners, TimeOutServer<Operation.Response> timer, AtomicReference<Throwable> failure) { this.session = session; this.connection = connection; this.listeners = listeners; this.timer = timer; this.failure = failure; new TimeOutListener(); Futures.addCallback(this.session, this.timer); this.connection.subscribe(this); this.connection.codec().subscribe(this); this.timer.run(); } public ListenableFuture<ConnectMessage.Response> session() { return session; } public C connection() { return connection; } @Override public void subscribe(SessionListener listener) { listeners.subscribe(listener); } @Override public boolean unsubscribe(SessionListener listener) { return listeners.unsubscribe(listener); } @Override public ListenableFuture<V> submit(I request) { return submit(request, SettableFuturePromise.<V>create()); } @Override public boolean send(T input) { return actor().send(input); } @Override public Actor.State state() { return actor().state(); } @Override public boolean stop() { return actor().stop(); } @Override public void run() { actor().run(); } @Override public void handleConnectionState(Automaton.Transition<Connection.State> event) { if (Connection.State.CONNECTION_CLOSED == event.to()) { switch (connection.codec().state()) { case CONNECTING: case CONNECTED: case DISCONNECTING: onFailure(new KeeperException.ConnectionLossException()); break; case ERROR: onFailure(new KeeperException.SessionExpiredException()); default: stop(); break; } } } @SuppressWarnings("unchecked") @Override public void handleConnectionRead(Operation.Response message) { logger().debug("Read {} ({})", message, this); timer.send(message); if (message instanceof Operation.ProtocolResponse<?>) { if (((Operation.ProtocolResponse<?>) message).xid() == OpCodeXid.NOTIFICATION.xid()) { listeners.handleNotification((Operation.ProtocolResponse<IWatcherEvent>) message); } } } @Override public void handleAutomatonTransition(Automaton.Transition<ProtocolState> transition) { listeners.handleAutomatonTransition(transition); } @Override public void onSuccess(O result) { } @Override public void onFailure(Throwable t) { if ((state().compareTo(State.TERMINATED) < 0) && failure.compareAndSet(null, t)) { logger().debug("{}", this, t); stop(); } } @Override public String toString() { return toStringHelper().toString(); } protected abstract Actor<? super T> actor(); protected abstract Logger logger(); protected void doStop() { timer.cancel(true); connection.unsubscribe(this); connection.codec().unsubscribe(this); session.cancel(false); connection.close(); listeners.clear(); } protected MoreObjects.ToStringHelper toStringHelper() { String sessionStr = null; if (session().isDone()) { if (session.isCancelled()) { sessionStr = "cancelled"; } else { try { sessionStr = Session.toString(session().get().getSessionId()); } catch (Exception e) { sessionStr = e.toString(); } } } return MoreObjects.toStringHelper(this).add("session", sessionStr).add("connection", connection); } public static class RequestTask<I extends Operation.Request, V extends Operation.ProtocolResponse<?>> extends PromiseTask<I,V> { public static <I extends Operation.Request, V extends Operation.ProtocolResponse<?>> RequestTask<I,V> of(I task, Promise<V> promise) { return new RequestTask<I,V>(task, promise); } public RequestTask(I task, Promise<V> promise) { super(task, promise); } public Promise<V> promise() { return delegate(); } } public static class Listeners implements Eventful<SessionListener>, SessionListener { public static Listeners create() { return new Listeners(new StrongConcurrentSet<SessionListener>()); } protected final IConcurrentSet<SessionListener> listeners; public Listeners(IConcurrentSet<SessionListener> listeners) { this.listeners = listeners; } public void clear() { Iterator<?> itr = Iterators.consumingIterator(listeners.iterator()); while (itr.hasNext()) { itr.next(); } } @Override public void subscribe(SessionListener listener) { listeners.add(listener); } @Override public boolean unsubscribe(SessionListener listener) { return listeners.remove(listener); } @Override public void handleAutomatonTransition(Automaton.Transition<ProtocolState> transition) { for (SessionListener listener: listeners) { listener.handleAutomatonTransition(transition); } } @Override public void handleNotification( ProtocolResponse<IWatcherEvent> notification) { for (SessionListener listener: listeners) { listener.handleNotification(notification); } } } protected class TimeOutListener implements Runnable { public TimeOutListener() { timer.addListener(this, MoreExecutors.directExecutor()); } @Override public void run() { if (timer.isDone()) { if (!timer.isCancelled()) { try { timer.get(); } catch (InterruptedException e) { throw new AssertionError(e); } catch (ExecutionException e) { onFailure(e.getCause()); } } else { stop(); } } } } public static class TimeOutServer<V> extends TimeOutActor<Operation.Response, V> implements FutureCallback<ConnectMessage.Response> { public static <V>TimeOutServer<V> newTimeOutServer( TimeOutParameters parameters, ScheduledExecutorService scheduler) { Logger logger = LogManager.getLogger(TimeOutServer.class); TimeOutServer<V> timeOut = new TimeOutServer<V>( parameters, scheduler, Sets.<Pair<Runnable,Executor>>newHashSet(), SettableFuturePromise.<V>create(), logger); LoggingFutureListener.listen(logger, timeOut); return timeOut; } public TimeOutServer( TimeOutParameters parameters, ScheduledExecutorService scheduler, Set<Pair<Runnable,Executor>> listeners, Promise<V> promise, Logger logger) { super(parameters, scheduler, listeners, promise, logger); } @Override public void onSuccess(ConnectMessage.Response result) { if (result instanceof ConnectMessage.Response.Valid) { synchronized (this) { parameters.setTimeOut(((ConnectMessage.Response) result).getTimeOut()); } } send(result); } @Override public void onFailure(Throwable t) { promise.setException(t); } } }