package edu.washington.escience.myria.parallel.ipc;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
import org.jboss.netty.channel.group.ChannelGroupFutureListener;
import org.jboss.netty.channel.group.DefaultChannelGroup;
/**
* A {@link ChannelGroupFuture} that waits for a condition to be satisfied before actually do the channel group event
* checking.
* */
public class ConditionChannelGroupFuture implements ChannelGroupFuture {
/** The logger for this class. */
private static final org.slf4j.Logger LOGGER =
org.slf4j.LoggerFactory.getLogger(ConditionChannelGroupFuture.class);
/**
* the actual channel group future that monitors the channel group event.
* */
private volatile ChannelGroupFuture backedChannelGroupFuture;
/**
* if the condition is set.
* */
private volatile boolean conditionSetFlag = false;
/**
* if the condition is satisfied.
* */
private volatile boolean condition = false;
/**
* protect the condition .
* */
private final Object conditionSetLock = new Object();
/**
* empty channel group.
* */
public static final ChannelGroup EMPTY_GROUP = new DefaultChannelGroup();
/**
* listeners which will be called back if the action is done.
* */
private final ConcurrentHashMap<ChannelGroupFutureListener, ChannelGroupFutureListener>
listeners = new ConcurrentHashMap<ChannelGroupFutureListener, ChannelGroupFutureListener>();
/**
* @param wrappedCGF the wrapped ChannelGroupFuture
* */
public final void setBackedChannelGroupFuture(final ChannelGroupFuture wrappedCGF) {
backedChannelGroupFuture = wrappedCGF;
}
@Override
public final void addListener(final ChannelGroupFutureListener listener) {
synchronized (conditionSetLock) {
if (!conditionSetFlag) {
listeners.put(listener, listener);
return;
}
}
if (condition) {
backedChannelGroupFuture.addListener(listener);
} else {
try {
listener.operationComplete(this);
} catch (final Throwable t) {
LOGGER.warn("Exception occured when executing ChannelGroupFutureListener", t);
}
}
}
/**
* Make sure that the condition variable is set before trying to use its value.
* */
private void ensureConditionSet() {
if (!conditionSetFlag) {
throw new IllegalStateException("Condition not set yet");
}
}
@Override
public final ChannelGroup getGroup() {
ensureConditionSet();
if (condition) {
return backedChannelGroupFuture.getGroup();
} else {
return EMPTY_GROUP;
}
}
@Override
public final ChannelFuture find(final Integer channelId) {
ensureConditionSet();
if (condition) {
return backedChannelGroupFuture.find(channelId);
} else {
return null;
}
}
@Override
public final ChannelFuture find(final Channel channel) {
if (channel == null) {
return null;
}
return this.find(channel.getId());
}
@Override
public final boolean isCompleteSuccess() {
ensureConditionSet();
if (condition) {
return backedChannelGroupFuture.isCompleteSuccess();
}
return false;
}
@Override
public final boolean isPartialSuccess() {
ensureConditionSet();
if (condition) {
return backedChannelGroupFuture.isPartialSuccess();
}
return false;
}
@Override
public final boolean isCompleteFailure() {
ensureConditionSet();
if (condition) {
return backedChannelGroupFuture.isCompleteFailure();
}
return true;
}
@Override
public final boolean isPartialFailure() {
ensureConditionSet();
if (condition) {
return backedChannelGroupFuture.isPartialFailure();
}
return false;
}
@Override
public final Iterator<ChannelFuture> iterator() {
ensureConditionSet();
if (condition) {
return backedChannelGroupFuture.iterator();
} else {
return null;
}
}
@Override
public final ChannelGroupFuture await() throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
waitForConditionSet(-1, -1);
if (condition) {
backedChannelGroupFuture.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 = NANOSECONDS.toMillis(nano);
final int nanoRemain = (int) (nano - MILLISECONDS.toNanos(milli));
final long remain = nano - waitForConditionSet(milli, nanoRemain);
if (conditionSetFlag) {
if (condition) {
return backedChannelGroupFuture.await(remain, NANOSECONDS);
} else {
return true;
}
} else {
return false;
}
}
@Override
public final ChannelGroupFuture awaitUninterruptibly() {
waitForConditionSetUninterruptibly(-1, -1);
if (condition) {
backedChannelGroupFuture.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 = NANOSECONDS.toMillis(nano);
final int nanoRemain = (int) (nano - MILLISECONDS.toNanos(milli));
final long remain = nano - waitForConditionSetUninterruptibly(milli, nanoRemain);
if (conditionSetFlag) {
if (condition) {
return backedChannelGroupFuture.awaitUninterruptibly(remain, NANOSECONDS);
} else {
return true;
}
} else {
return false;
}
}
@Override
public final boolean isDone() {
if (!conditionSetFlag) {
return false;
} else {
if (condition) {
return backedChannelGroupFuture.isDone();
} else {
return true;
}
}
}
@Override
public final void removeListener(final ChannelGroupFutureListener listener) {
synchronized (conditionSetLock) {
if (!conditionSetFlag) {
listeners.remove(listener);
}
}
if (conditionSetFlag) {
if (condition) {
backedChannelGroupFuture.removeListener(listener);
}
}
}
/**
* Set the condition value.
*
* @param conditionSatisfied if the condition is satisfied.
* */
public final void setCondition(final boolean conditionSatisfied) {
synchronized (conditionSetLock) {
condition = conditionSatisfied;
conditionSetFlag = true;
if (condition) {
for (final ChannelGroupFutureListener l : listeners.keySet()) {
backedChannelGroupFuture.addListener(l);
}
listeners.clear();
} else {
for (final ChannelGroupFutureListener l : listeners.keySet()) {
try {
l.operationComplete(this);
} catch (final Throwable t) {
LOGGER.warn("Exception occured when executing ChannelGroupFutureListener", t);
}
}
}
conditionSetLock.notifyAll();
}
}
/**
* @return the time elapse in nano seconds in this method.
* @param timeoutMS timeout in milliseconds
* @param nanos remaining nanos.
* @throws InterruptedException if interrupted.
* */
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;
}
/**
* @return the time elapse in nano seconds in this method.
* @param timeoutMS timeout in milliseconds
* @param nanos remaining nanos.
* */
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;
}
}