package edu.uw.zookeeper.protocol.client;
import java.lang.ref.Reference;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.Logger;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Queues;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import edu.uw.zookeeper.common.Actors;
import edu.uw.zookeeper.common.Automaton;
import edu.uw.zookeeper.common.Promise;
import edu.uw.zookeeper.common.PromiseTask;
import edu.uw.zookeeper.common.TimeValue;
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.ProtocolConnection;
import edu.uw.zookeeper.protocol.proto.OpCodeXid;
public abstract class PendingQueueClientExecutor<
I extends Operation.Request,
O extends Operation.ProtocolResponse<?>,
T extends PendingQueueClientExecutor.RequestTask<I,O>,
C extends ProtocolConnection<? super Message.ClientSession, ? extends Operation.Response,?,?,?>,
V extends PendingQueueClientExecutor.PendingTask>
extends AbstractConnectionClientExecutor<I,O,T,C,V> {
protected final Pending<O,V,C> pending;
protected PendingQueueClientExecutor(
Logger logger,
ListenableFuture<ConnectMessage.Response> session,
C connection,
TimeValue timeOut,
ScheduledExecutorService scheduler) {
super(session, connection, timeOut, scheduler);
this.pending = Pending.<O,V,C>create(connection, this, logger);
}
protected PendingQueueClientExecutor(
Logger logger,
ListenableFuture<ConnectMessage.Response> session,
C connection,
Listeners listeners,
TimeOutServer<Operation.Response> timer,
AtomicReference<Throwable> failure) {
super(session, connection, listeners, timer, failure);
this.pending = Pending.<O,V,C>create(connection, this, logger);
}
@Override
public void handleConnectionRead(Operation.Response message) {
super.handleConnectionRead(message);
pending.handleConnectionRead(message);
}
@Override
public void handleConnectionState(Automaton.Transition<Connection.State> event) {
super.handleConnectionState(event);
pending.handleConnectionState(event);
}
@Override
protected void doStop() {
pending.stop();
super.doStop();
}
protected Executor executor() {
return connection;
}
public static class PendingTask
extends PromiseTask<Reference<? extends Message.ClientRequest<?>>, Message.ServerResponse<?>>
implements Operation.RequestId, FutureCallback<Message.ClientRequest<?>> {
public static PendingTask create(
Reference<? extends Message.ClientRequest<?>> task,
Promise<Message.ServerResponse<?>> delegate) {
return new PendingTask(task, delegate);
}
protected final int xid;
public PendingTask(
Reference<? extends Message.ClientRequest<?>> task,
Promise<Message.ServerResponse<?>> delegate) {
super(task, delegate);
this.xid = task().get().xid();
}
@Override
public int xid() {
return xid;
}
public Message.ClientRequest<?> getRequest() {
return task().get();
}
@Override
public void onSuccess(Message.ClientRequest<?> result) {
}
@Override
public void onFailure(Throwable t) {
setException(t);
}
@Override
protected MoreObjects.ToStringHelper toStringHelper(MoreObjects.ToStringHelper toString) {
return super.toStringHelper(toString.add("xid", xid));
}
}
public static class PendingPromiseTask extends PendingTask {
public static PendingPromiseTask create(
Promise<Message.ServerResponse<?>> promise,
Reference<? extends Message.ClientRequest<?>> task,
Promise<Message.ServerResponse<?>> delegate) {
return new PendingPromiseTask(promise, task, delegate);
}
protected final Promise<Message.ServerResponse<?>> promise;
public PendingPromiseTask(
Promise<Message.ServerResponse<?>> promise,
Reference<? extends Message.ClientRequest<?>> task,
Promise<Message.ServerResponse<?>> delegate) {
super(task, delegate);
this.promise = promise;
}
public Promise<Message.ServerResponse<?>> getPromise() {
return promise;
}
}
public static class Pending<
O extends Operation.ProtocolResponse<?>,
T extends PendingTask,
C extends ProtocolConnection<? super Message.ClientSession, ? extends Operation.Response,?,?,?>> extends Actors.ExecutedPeekingQueuedActor<T> implements Connection.Listener<Operation.Response> {
public static <O extends Operation.ProtocolResponse<?>,T extends PendingTask,C extends ProtocolConnection<? super Message.ClientSession, ? extends Operation.Response,?,?,?>> Pending<O,T,C> create(
C connection,
FutureCallback<? super T> callback,
Logger logger) {
return new Pending<O,T,C>(connection, callback, Queues.<T>newConcurrentLinkedQueue(), logger);
}
protected final FutureCallback<? super T> callback;
protected final C connection;
protected Pending(
C connection,
FutureCallback<? super T> callback,
Queue<T> mailbox,
Logger logger) {
super(connection, mailbox, logger);
this.callback = callback;
this.connection = connection;
}
@Override
protected synchronized boolean doSend(T message) {
// we use synchronized to preserve queue/send ordering
// task needs to be in the queue before calling write
if (! mailbox.offer(message)) {
return false;
}
try {
if (! message.isDone()) {
// mark pings as done on send because ZooKeeper doesn't care about their ordering
if (message.xid() == OpCodeXid.PING.xid()) {
message.set(null);
}
Message.ClientRequest<?> request = message.getRequest();
if (request != null) {
ListenableFuture<? extends Message.ClientRequest<?>> writeFuture = connection.write(request);
Futures.addCallback(writeFuture, message);
}
}
} catch (Throwable t) {
message.onFailure(t);
}
if (state() == State.TERMINATED) {
message.cancel(true);
apply(message);
}
if (! message.isDone()) {
message.addListener(this, executor);
} else {
run();
}
return true;
}
@Override
public void handleConnectionRead(Operation.Response message) {
if (message instanceof Message.ServerResponse<?>) {
int xid = ((Message.ServerResponse<?>) message).xid();
if (! ((xid == OpCodeXid.PING.xid()) || (xid == OpCodeXid.NOTIFICATION.xid()))) {
Iterator<T> tasks = mailbox.iterator();
T task = null;
while (tasks.hasNext()) {
T next = tasks.next();
if ((next.xid() == xid) && !next.isDone()) {
task = next;
break;
}
}
if (task != null) {
task.set((Message.ServerResponse<?>) message);
} else if (state() != State.TERMINATED) {
// This could happen if someone submitted a message without
// going through us
// or, it could be a bug
logger.warn("{} xid doesn't match {} ({})", message, mailbox.peek(), this);
}
}
}
}
@Override
public void handleConnectionState(Automaton.Transition<Connection.State> state) {
// TODO
}
public boolean isReady() {
T next = mailbox.peek();
return ((next != null) && next.isDone());
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).addValue(callback).toString();
}
@Override
protected void doStop() {
T task;
while ((task = mailbox.peek()) != null) {
task.cancel(true);
apply(task);
}
}
@Override
protected synchronized boolean apply(T input) {
if (input.isDone()) {
if (mailbox.remove(input)) {
callback.onSuccess(input);
return true;
}
}
return false;
}
}
public abstract class ForwardingActor extends Actors.ExecutedQueuedActor<T> {
protected ForwardingActor(Executor executor,
Logger logger) {
super(executor, new ConcurrentLinkedQueue<T>(), logger);
}
public Logger logger() {
return logger;
}
@Override
protected void doStop() {
PendingQueueClientExecutor.this.doStop();
T request;
while ((request = mailbox.poll()) != null) {
request.cancel(true);
}
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).addValue(PendingQueueClientExecutor.this).toString();
}
}
}