package hep.io.root.daemon.xrootd;
import hep.io.root.daemon.xrootd.MultiplexorManager.MultiplexorReadyCallback;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Dispatches messages using a thread pool.
* @author tonyj
*/
class Dispatcher {
private static Logger logger = Logger.getLogger(Dispatcher.class.getName());
private static final int WAIT_TIMEOUT = Integer.getInteger("hep.io.root.deamon.xrootd.timeout", 3000).intValue();
private static final int WAIT_LIMIT = Integer.getInteger("hep.io.root.deamon.xrootd.waitLimit", 1000).intValue();
private static Dispatcher theDispatcher = new Dispatcher();
private ScheduledThreadPoolExecutor scheduler;
private MultiplexorManager manager;
private Dispatcher() {
scheduler = new ScheduledThreadPoolExecutor(1,new DaemonThreadFactory());
manager = new MultiplexorManager(scheduler);
}
private static class DaemonThreadFactory implements ThreadFactory
{
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "xrootd-dispatcher");
t.setDaemon(true);
return t;
}
}
static Dispatcher instance() {
return theDispatcher;
}
<V> FutureResponse<V> send(Destination destination, Operation<V> operation) {
MessageExecutor executor = new MessageExecutor(destination, operation);
//scheduler.execute(executor);
executor.run();
return new FutureMessageResponse<V>(executor);
}
private void resend(MessageExecutor executor) {
resend(executor, 0, TimeUnit.SECONDS);
}
private void resend(MessageExecutor executor, long time, TimeUnit units) {
scheduler.schedule(executor, time, units);
}
/**
* The chain callback is used when a prerequisite message has been scheduled.
* It fires the callback associated with the prerequisite, and
* then resends the original message.
*/
private class ChainCallback<V> extends Callback<V> {
private MessageExecutor originalMessageExecutor;
private Callback<V> chain;
ChainCallback(Callback<V> chain, MessageExecutor originalMessageExecutor) {
this.originalMessageExecutor = originalMessageExecutor;
this.chain = chain;
}
public V responseReady(Response response) throws IOException {
V result = chain.responseReady(response);
if (response.isComplete()) {
// If the prerequisite was redirected we need the original message to be
// redirected too.
originalMessageExecutor.destination = response.getDestination();
resend(originalMessageExecutor);
}
return result;
}
@Override
public void clear() {
chain.clear();
}
}
private static class FutureMessageResponse<V> extends FutureResponse<V> {
private final MessageExecutor<V> listener;
FutureMessageResponse(MessageExecutor<V> listener) {
this.listener = listener;
}
public V getResponse(long timeout, TimeUnit timeUnit) throws IOException {
long start = System.nanoTime();
long timeoutNS = timeUnit.toNanos(timeout);
long waitTimeoutNS = TimeUnit.NANOSECONDS.convert(WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
try {
for (;;) {
synchronized (listener) {
if (listener.isDone()) return listener.getResult();
TimeUnit.NANOSECONDS.timedWait(listener, Math.min(timeoutNS,waitTimeoutNS));
if (listener.isDone()) return listener.getResult();
}
long totalWaitNS = System.nanoTime() - start;
if (totalWaitNS > timeoutNS) return null;
logger.warning("Waiting for response for " + TimeUnit.SECONDS.convert(totalWaitNS,TimeUnit.NANOSECONDS) + " secs " + listener.toString());
if (totalWaitNS >= WAIT_LIMIT * waitTimeoutNS) {
throw new IOException("Timeout waiting for response after " + TimeUnit.SECONDS.convert(totalWaitNS,TimeUnit.NANOSECONDS) + "secs");
}
}
} catch (InterruptedException x) {
IOException iio = new InterruptedIOException("Xrootd IO interrupted");
iio.initCause(x);
throw iio;
}
}
public boolean isDone() {
return listener.isDone();
}
}
/**
* A MessageExecutor consists of a message, a destination, plus a callback
* to be called when the response from the message is available. The message executor
* is also a runnable which when executed submits the message to an appropriate multiplexor.
* The destination associated with the message executor may be changed as a result of
* a "redirect" response from the server, or because the multiplexor associated with
* the destination reports a problem. Some messages have "prerequisites" which must be
* executed when the destination changes, in which case a separate MessageExecutor is created
* for the prerequsite, with the callback for the new MessageExecutor set to reexecute the
* original MessageExecutor.
* @param <V>
*/
private class MessageExecutor<V> implements ResponseListener, Runnable, MultiplexorReadyCallback {
private Operation<V> operation;
private V result;
private IOException exception;
private boolean isDone = false;
private int errors = 0;
private Destination destination;
private long startTime = System.currentTimeMillis();
MessageExecutor(Destination destination, Operation<V> operation) {
this.destination = destination;
this.operation = operation;
}
public void run() {
try {
Multiplexor multiplexor = manager.getMultiplexor(destination,this);
if (multiplexor == null)
{
return;
}
Multiplexor expectedMultiplexor = operation.getMultiplexor();
if (expectedMultiplexor != null && multiplexor != expectedMultiplexor)
{
Operation preReq = operation.getPrerequisite();
ChainCallback cc = new ChainCallback(preReq.getCallback(), this);
MessageExecutor executor = new MessageExecutor(destination, new Operation(preReq.getName() + "-chain", preReq.getMessage(), cc));
resend(executor);
}
else
{
multiplexor.sendMessage(operation.getMessage(), this);
logger.fine(String.format("Sent %s to %s after %,dms",operation,multiplexor,System.currentTimeMillis()-startTime));
}
} catch (IOException x) {
handleSocketError(x);
} catch (Throwable x) {
logger.log(Level.SEVERE, "Unexpected error while sending message", x);
}
}
public void multiplexorReady(Multiplexor multiplexor) {
resend(this);
}
public synchronized void handleError(IOException exception) {
this.exception = exception;
isDone = true;
notify();
logger.fine(String.format("Received error for %s after %,dms",operation,System.currentTimeMillis()-startTime));
}
public void reschedule(long time, TimeUnit units) {
resend(this, time, units);
}
public void handleRedirect(String host, int port) throws UnknownHostException {
Destination redirected = destination.getRedirected(host, port);
operation.getCallback().clear();
destination = redirected;
resend(this);
}
public synchronized void handleResponse(Response response) throws IOException {
result = operation.getCallback().responseReady(response);
if (response.isComplete()) {
isDone = true;
notify();
logger.fine(String.format("Received response %s from %s after %,dms",operation,response.getMultiplexor(),System.currentTimeMillis()-startTime));
}
}
public void handleSocketError(IOException iOException) {
errors++;
if (errors > 1 && destination.getPrevious() != null) {
destination = destination.getPrevious();
}
operation.getCallback().clear();
resend(this,1,TimeUnit.SECONDS);
}
synchronized V getResult() throws IOException {
if (exception != null) {
throw exception;
}
return result;
}
synchronized boolean isDone() {
return isDone;
}
@Override
public String toString()
{
return operation+"@"+destination;
}
}
}