package edu.washington.escience.myria.parallel.ipc; import static java.util.concurrent.TimeUnit.MILLISECONDS; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelException; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; /** * A wrapper close future. <br> * It works as the following: <br> * 1. wait until the condition is set<br> * 2. if condition is true, pass all the operations directly to the wrapped close future.<br> * 3. else set the wrapper future as done and treat it as fail by default, or succeed if specified in constructor. * */ public class ConditionCloseFuture implements ChannelFuture { /** * The owner channel. * */ private final Channel channel; /** * If the condition is set or not. * */ private volatile boolean conditionSetFlag = false; /** * the condition is satisfied or not. * */ private volatile boolean condition = false; /** * condition set lock. * */ private final Object conditionSetLock = new Object(); /** The logger for this class. */ private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(ConditionCloseFuture.class); /** * If the condition is unsatisfied, treat the future as a failure. * */ private final boolean asFail; /** * listeners to notify. * */ private final ConcurrentHashMap<ChannelFutureListener, ChannelFutureListener> listeners = new ConcurrentHashMap<ChannelFutureListener, ChannelFutureListener>(); /** * @param channel the owner channel. * */ public ConditionCloseFuture(final Channel channel) { this.channel = channel; asFail = true; } /** * @param channel the owner channel. * @param conditionUnSatisfiedAsSucceed if the condition is unsatisfied the future should be treated as a succeed or * not * */ public ConditionCloseFuture(final Channel channel, final boolean conditionUnSatisfiedAsSucceed) { this.channel = channel; asFail = !conditionUnSatisfiedAsSucceed; } @Override public final void addListener(final ChannelFutureListener listener) { synchronized (conditionSetLock) { if (!conditionSetFlag) { listeners.put(listener, listener); return; } } if (conditionSetFlag) { if (condition) { channel.getCloseFuture().addListener(listener); } else { try { listener.operationComplete(this); } catch (final Throwable t) { LOGGER.warn("Exception occured when executing ChannelGroupFutureListener", t); } } } } @Override public final ChannelFuture await() throws InterruptedException { if (Thread.interrupted()) { throw new InterruptedException(); } waitForConditionSet(-1, -1); if (condition) { channel.getCloseFuture().await(); } return this; } @Override public final boolean await(final long timeoutMillis) throws InterruptedException { return this.await(timeoutMillis, MILLISECONDS); } @Override public final boolean await(final long timeout, final TimeUnit unit) throws InterruptedException { final long nano = unit.toNanos(timeout); final long milli = TimeUnit.NANOSECONDS.toMillis(nano); final int nanoRemain = (int) (nano - TimeUnit.MILLISECONDS.toNanos(milli)); final long remain = nano - waitForConditionSet(milli, nanoRemain); if (conditionSetFlag) { if (condition) { return channel.getCloseFuture().await(remain, TimeUnit.NANOSECONDS); } else { return true; } } else { return false; } } @Override public final ChannelFuture awaitUninterruptibly() { waitForConditionSetUninterruptibly(-1, -1); if (condition) { channel.getCloseFuture().awaitUninterruptibly(); } return this; } @Override public final boolean awaitUninterruptibly(final long timeoutMillis) { return this.awaitUninterruptibly(timeoutMillis, MILLISECONDS); } @Override public final boolean awaitUninterruptibly(final long timeout, final TimeUnit unit) { final long nano = unit.toNanos(timeout); final long milli = TimeUnit.NANOSECONDS.toMillis(nano); final int nanoRemain = (int) (nano - TimeUnit.MILLISECONDS.toNanos(milli)); final long remain = nano - waitForConditionSetUninterruptibly(milli, nanoRemain); if (conditionSetFlag) { if (condition) { return channel.getCloseFuture().awaitUninterruptibly(remain, TimeUnit.NANOSECONDS); } else { return true; } } else { return false; } } @Override public final boolean cancel() { return channel.getCloseFuture().cancel(); } @Override public final Throwable getCause() { if (!conditionSetFlag) { return null; } else { if (condition) { return channel.getCloseFuture().getCause(); } else if (!asFail) { return null; } else { return new ConditionUnsatisfiedException(); } } } @Override public final Channel getChannel() { return channel; } @Override public final boolean isCancelled() { return channel.getCloseFuture().isCancelled(); } @Override public final boolean isDone() { if (!conditionSetFlag) { return false; } else { if (condition) { return channel.getCloseFuture().isDone(); } else { return true; } } } @Override public final boolean isSuccess() { if (!conditionSetFlag) { return false; } else { if (condition) { return channel.getCloseFuture().isSuccess(); } else { return !asFail; } } } @Override public final void removeListener(final ChannelFutureListener listener) { synchronized (conditionSetLock) { if (!conditionSetFlag) { listeners.remove(listener); return; } } if (condition) { channel.getCloseFuture().removeListener(listener); } } @Override @Deprecated public final ChannelFuture rethrowIfFailed() throws Exception { if (!conditionSetFlag) { return this; } if (condition) { channel.getCloseFuture().rethrowIfFailed(); } else if (asFail) { throw new ConditionUnsatisfiedException(); } return this; } /** * set the condition. * * @param conditionSatisfied if the condition is true * */ public final void setCondition(final boolean conditionSatisfied) { synchronized (conditionSetLock) { condition = conditionSatisfied; conditionSetFlag = true; if (condition) { for (final ChannelFutureListener l : listeners.keySet()) { channel.getCloseFuture().addListener(l); } listeners.clear(); } else { for (final ChannelFutureListener l : listeners.keySet()) { try { l.operationComplete(this); } catch (final Throwable t) { LOGGER.warn("Exception occured when executing ChannelGroupFutureListener", t); } } } conditionSetLock.notifyAll(); } } @Override public final boolean setFailure(final Throwable cause) { throw new UnsupportedOperationException(); } @Override public final boolean setProgress(final long amount, final long current, final long total) { throw new UnsupportedOperationException(); } @Override public final boolean setSuccess() { throw new UnsupportedOperationException(); } @Override public final ChannelFuture sync() throws InterruptedException { await(); try { rethrowIfFailed(); } catch (Exception e) { throw new ChannelException(e); } return this; } @Override public final ChannelFuture syncUninterruptibly() { this.awaitUninterruptibly(); try { rethrowIfFailed(); } catch (Exception e) { throw new ChannelException(e); } return this; } /** * Wait the condition to be set for at most timeoutMS milliseconds and nanos nanoseconds. * * @param timeoutMS milliseconds * @param nanos nanoseconds. * @throws InterruptedException if interrupted. * @return the amount of waiting time remain in nanoseconds. * */ private long waitForConditionSet(final long timeoutMS, final int nanos) throws InterruptedException { final long start = System.nanoTime(); synchronized (conditionSetLock) { if (!conditionSetFlag) { if (timeoutMS >= 0) { conditionSetLock.wait(timeoutMS, nanos); } else { conditionSetLock.wait(); } } } return System.nanoTime() - start; } /** * Wait the condition to be set for at most timeoutMS milliseconds and nanos nanoseconds without interruption. * * @param timeoutMS milliseconds * @param nanos nanoseconds. * @return the amount of waiting time remain in nanoseconds. * */ private long waitForConditionSetUninterruptibly(final long timeoutMS, final int nanos) { boolean interrupted = false; final long start = System.nanoTime(); synchronized (conditionSetLock) { if (!conditionSetFlag) { try { if (timeoutMS >= 0) { conditionSetLock.wait(timeoutMS, nanos); } else { conditionSetLock.wait(); } } catch (final InterruptedException e) { interrupted = true; } } } if (interrupted) { Thread.currentThread().interrupt(); } return System.nanoTime() - start; } }