package edu.washington.escience.myria.parallel.ipc;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import edu.washington.escience.myria.operator.network.Producer;
import edu.washington.escience.myria.parallel.ipc.IPCEvent.EventType;
import edu.washington.escience.myria.util.concurrent.OrderedExecutorService;
import edu.washington.escience.myria.util.concurrent.ReentrantSpinLock;
/**
*
* An {@link StreamOutputChannel} represents a partition of {@link Producer}.
*
* @param <PAYLOAD> the type of payload that this output channel will send.
* */
public class StreamOutputChannel<PAYLOAD> extends StreamIOChannel {
/** The logger for this class. */
static final org.slf4j.Logger LOGGER =
org.slf4j.LoggerFactory.getLogger(StreamOutputChannel.class);
/**
* Output disabled listeners.
* */
private final ConcurrentLinkedQueue<IPCEventListener> outputDisableListeners;
/**
* Output recovered listeners.
* */
private final ConcurrentLinkedQueue<IPCEventListener> outputRecoverListeners;
/**
* owner IPC pool.
* */
private final IPCConnectionPool ownerPool;
/**
* Output disabled event.
* */
public static final EventType OUTPUT_DISABLED = new EventType("Output disabled");
/**
* Output recovered event.
* */
public static final EventType OUTPUT_RECOVERED = new EventType("Output recovered");
/**
* Channel release future.
* */
private ChannelFuture releaseFuture = null;
/**
* @param ecID stream output channel ID
* @param ownerPool the owner of this output channel.
* @param initialPhysicalChannel the physical channel associated to this output channel in the beginning
* */
StreamOutputChannel(
final StreamIOChannelID ecID,
final IPCConnectionPool ownerPool,
final Channel initialPhysicalChannel) {
super(ecID);
outputDisableListeners = new ConcurrentLinkedQueue<IPCEventListener>();
outputRecoverListeners = new ConcurrentLinkedQueue<IPCEventListener>();
this.ownerPool = ownerPool;
ChannelContext.getChannelContext(initialPhysicalChannel)
.getRegisteredChannelContext()
.getIOPair()
.mapOutputChannel(this);
}
/**
* Callback from the physical IO layer if the channel interest changed.
* */
final void channelInterestChangedCallback() {
Channel ch = getIOChannel();
if (ch != null) {
boolean writable = ch.isWritable();
eventSerializeLock.lock();
try {
if (previousEvent == OUTPUT_DISABLED && writable) {
fireOutputRecovered();
} else if (previousEvent == OUTPUT_RECOVERED && !writable) {
fireOutputDisabled();
}
} finally {
eventSerializeLock.unlock();
}
}
}
@Override
public final String toString() {
return "StreamOutputChannel{ ID: "
+ getID()
+ ",IOChannel: "
+ ChannelContext.channelToString(getIOChannel())
+ " }";
}
/**
* serialize the events.
* */
private final ReentrantSpinLock eventSerializeLock = new ReentrantSpinLock();
/**
* protected by the event serialize lock.
* */
private EventType previousEvent = OUTPUT_RECOVERED;
/**
* The output disabled event.
* */
private final IPCEvent outputDisabledEvent =
new IPCEvent() {
@Override
public Object getAttachment() {
return StreamOutputChannel.this;
}
@Override
public EventType getType() {
return OUTPUT_DISABLED;
}
};
/**
* The output recover event.
* */
private final IPCEvent outputRecoveredEvent =
new IPCEvent() {
@Override
public Object getAttachment() {
return StreamOutputChannel.this;
}
@Override
public EventType getType() {
return OUTPUT_RECOVERED;
}
};
/**
* Fire a buffer full event. All the buffer full event listeners will be notified.
* */
private void fireOutputDisabled() {
previousEvent = OUTPUT_DISABLED;
ownerPool
.getIPCEventProcessor()
.execute(
new OrderedExecutorService.KeyRunnable<StreamOutputChannel<PAYLOAD>>() {
@Override
public void run() {
for (IPCEventListener l : outputDisableListeners) {
l.triggered(outputDisabledEvent);
}
}
@Override
public StreamOutputChannel<PAYLOAD> getKey() {
return StreamOutputChannel.this;
}
});
}
/**
* Fire a buffer full event. All the buffer full event listeners will be notified.
* */
private void fireOutputRecovered() {
previousEvent = OUTPUT_RECOVERED;
ownerPool
.getIPCEventProcessor()
.execute(
new OrderedExecutorService.KeyRunnable<StreamOutputChannel<PAYLOAD>>() {
@Override
public void run() {
for (IPCEventListener l : outputRecoverListeners) {
l.triggered(outputRecoveredEvent);
}
}
@Override
public StreamOutputChannel<PAYLOAD> getKey() {
return StreamOutputChannel.this;
}
});
}
/**
* @param t event type.
* @param l event listener.
* */
public final void addListener(final EventType t, final IPCEventListener l) {
if (t == OUTPUT_DISABLED) {
outputDisableListeners.add(l);
} else if (t == OUTPUT_RECOVERED) {
outputRecoverListeners.add(l);
} else {
throw new IllegalArgumentException("Unsupported event: " + t);
}
}
/**
* @param message the message to write
* @return the write future.
* */
public final ChannelFuture write(final PAYLOAD message) {
Channel ch = getIOChannel();
if (ch != null) {
this.ownerPool.getShutdownLock().readLock().lock();
try {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace(
"OutputChannel {} write a message through {}",
getID(),
ChannelContext.channelToString(ch));
}
return ch.write(message);
} finally {
this.ownerPool.getShutdownLock().readLock().unlock();
}
} else {
LOGGER.error("No usable physical IO channel for id={}.", getID());
throw new IllegalStateException("No usable physical IO channel.");
}
}
/**
* @return release future.
* */
public final synchronized ChannelFuture release() {
if (releaseFuture == null) {
releaseFuture = ownerPool.releaseLongTermConnection(this);
}
return releaseFuture;
}
/**
* @return If the output channel is writable.
* */
public final boolean isWritable() {
Channel ch = getIOChannel();
return ch != null && ch.isWritable();
}
}