package edu.uw.zookeeper.net.intravm; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; import java.util.Deque; import java.util.Queue; import java.util.concurrent.Executor; import org.apache.logging.log4j.Logger; import com.google.common.base.MoreObjects; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import edu.uw.zookeeper.common.Actor; import edu.uw.zookeeper.common.Actors; import edu.uw.zookeeper.common.Automaton; import edu.uw.zookeeper.common.Automatons; import edu.uw.zookeeper.common.Eventful; import edu.uw.zookeeper.common.ForwardingPromise; import edu.uw.zookeeper.common.LoggingFutureListener; import edu.uw.zookeeper.common.Promise; import edu.uw.zookeeper.common.SettableFuturePromise; import edu.uw.zookeeper.common.Stateful; import edu.uw.zookeeper.net.Connection; public abstract class AbstractIntraVmEndpoint<I,O,U,V> implements Stateful<Connection.State>, Eventful<Connection.Listener<? super O>>, Executor { protected final Logger logger; protected final Executor executor; protected final IntraVmPublisher<O> publisher; protected final SocketAddress address; protected final Automaton<Connection.State, Connection.State> state; /** * @param executor execute order must preserve submit order */ protected AbstractIntraVmEndpoint( SocketAddress address, Executor executor, Logger logger, IntraVmPublisher<O> publisher) { super(); this.logger = logger; this.executor = executor; this.address = address; this.publisher = publisher; this.state = Automatons.createSynchronized( Automatons.createSimple(Connection.State.CONNECTION_OPENING)); } @Override public Connection.State state() { return state.state(); } public SocketAddress address() { return address; } public abstract Actor<V> reader(); public abstract AbstractEndpointWriter writer(); public boolean close() { if (state.apply(Connection.State.CONNECTION_CLOSING).isPresent()) { execute(new EndpointClose()); return true; } return false; } @Override public void execute(Runnable command) { executor.execute(command); } @Override public void subscribe(Connection.Listener<? super O> listener) { publisher.subscribe(listener); } @Override public boolean unsubscribe(Connection.Listener<? super O> listener) { return publisher.unsubscribe(listener); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("address", address) .add("state", state()).toString(); } public abstract class AbstractEndpointActor<T> extends Actors.ExecutedQueuedActor<T> { protected AbstractEndpointActor(Queue<T> mailbox) { super(AbstractIntraVmEndpoint.this.executor, mailbox, AbstractIntraVmEndpoint.this.logger); } protected AbstractEndpointActor(Executor executor, Queue<T> mailbox, Logger logger) { super(executor, mailbox, logger); } @Override public String toString() { return MoreObjects.toStringHelper(this).addValue(AbstractIntraVmEndpoint.this).toString(); } } public abstract class AbstractEndpointReader extends AbstractEndpointActor<V> { protected AbstractEndpointReader(Deque<V> mailbox) { super(mailbox); } protected Deque<V> mailbox() { return (Deque<V>) mailbox; } @Override protected void doRun() { Connection.State connectionState = AbstractIntraVmEndpoint.this.state.state(); switch (connectionState) { case CONNECTION_OPENING: { Connection.State nextState = Connection.State.CONNECTION_OPENED; if (AbstractIntraVmEndpoint.this.state.apply(nextState).isPresent()) { publisher.send( Automaton.Transition.create(connectionState, nextState)); } break; } case CONNECTION_CLOSED: { // we want to make sure that we don't post any messages // after declaring that we are closed stop(); return; } default: break; } V last = mailbox().peekLast(); try { super.doRun(); } catch (Exception e) { close(); stop(); return; } if (last != mailbox().peekLast()) { schedule(); } } @Override protected void runExit() { state.compareAndSet(State.RUNNING, State.WAITING); } } public abstract class AbstractEndpointWriter extends AbstractEndpointActor<EndpointWrite<? extends I,U>> { protected AbstractEndpointWriter(Queue<EndpointWrite<? extends I,U>> mailbox) { super(mailbox); } public <T extends I> ListenableFuture<T> submit(T message, AbstractIntraVmEndpoint<?,?,?,? super U> remote) { if (AbstractIntraVmEndpoint.this.state().compareTo(Connection.State.CONNECTION_CLOSING) < 0) { EndpointWrite<T,U> task = LoggingFutureListener.listen(logger, EndpointWrite.create(message, remote)); if (!send(task)) { task.cancel(false); } return task; } else { return Futures.immediateFailedFuture(new ClosedChannelException()); } } @Override protected void doRun() { if (AbstractIntraVmEndpoint.this.state.state() == Connection.State.CONNECTION_CLOSED) { stop(); return; } try { super.doRun(); } catch (Exception e) { close(); stop(); } } @Override protected void doStop() { Exception exception = new ClosedChannelException(); EndpointWrite<?,?> next; while ((next = mailbox.poll()) != null) { next.setException(exception); } } } public final static class EndpointWrite<T,U> extends ForwardingPromise.SimpleForwardingPromise<T> implements Runnable { public static <T,U> EndpointWrite<T,U> create( T message, AbstractIntraVmEndpoint<?,?,?,? super U> remote) { return new EndpointWrite<T,U>(message, remote, SettableFuturePromise.<T>create()); } private final AbstractIntraVmEndpoint<?,?,?,? super U> remote; private final T message; public EndpointWrite( T message, AbstractIntraVmEndpoint<?,?,?,? super U> remote, Promise<T> promise) { super(promise); this.message = message; this.remote = remote; } public T message() { return message; } public AbstractIntraVmEndpoint<?,?,?,? super U> remote() { return remote; } @Override public void run() { set(message()); } @Override public String toString() { return MoreObjects.toStringHelper(this).addValue(message).addValue(remote).addValue(super.toString()).toString(); } } public final class EndpointClose implements Runnable { public EndpointClose() {} @Override public void run() { Connection.State prevState = Connection.State.CONNECTION_OPENED; Connection.State nextState = Connection.State.CONNECTION_CLOSING; if (state.state() == nextState) { publisher.send( Automaton.Transition.create(prevState, nextState)); reader().stop(); writer().stop(); prevState = nextState; nextState = Connection.State.CONNECTION_CLOSED; if (state.apply(nextState).isPresent()) { publisher.send( Automaton.Transition.create(prevState, nextState)); } } } @Override public String toString() { return MoreObjects.toStringHelper(this).addValue(AbstractIntraVmEndpoint.this).toString(); } } }