package hep.io.root.daemon.xrootd;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.BitSet;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A multiplexor is responsible for managing a single socket connection to an
* xrootd server. Many clients may use a single multiplexor at the same time.
* @author tonyj
*/
class Multiplexor implements MultiplexorMBean {
private static final int MAX_IDLE = Integer.getInteger("hep.io.root.daemon.xrootd.ConnectionTimeout", 60000);
private static final int SEND_BUFFER_SIZE = Integer.getInteger("hep.io.root.daemon.xrootd.SendBufferSize", 65536);
private static final int RECEIVE_BUFFER_SIZE = Integer.getInteger("hep.io.root.daemon.xrootd.ReceivedBufferSize", 65536);
private static Logger logger = Logger.getLogger(Multiplexor.class.getName());
private Destination descriptor;
private SocketChannel channel;
private Response response;
private BitSet handles = new BitSet();
private Thread thread;
private Map<Short, ResponseListener> responseMap = new HashMap<Short, ResponseListener>();
private boolean socketClosed = false;
private long bytesSent;
private long bytesReceived;
private Date createDate = new Date();
private Date lastActive = new Date();
private int pval;
private int flag;
Multiplexor(Destination desc) throws IOException {
logger.fine(desc + " Creating multiplexor");
this.descriptor = desc;
channel = SocketChannel.open();
channel.socket().setReceiveBufferSize(RECEIVE_BUFFER_SIZE);
channel.socket().setSendBufferSize(SEND_BUFFER_SIZE);
thread = new Thread(new SocketReader(), "XrootdReader-" + this);
thread.setDaemon(true);
response = new Response(this, channel);
}
/**
* Connect to the remote socket. The callback will be called
* after the initial handshake is complete, or if an error occurs.
* @param callback
*/
void connect(ResponseListener listener) {
addListener(listener);
thread.start();
}
void handleInitialHandshakeResponse(Response response) throws IOException {
if (response.getLength() != 8) {
throw new IOException("Unexpected initial handshake length");
}
pval = response.readInt();
flag = response.readInt();
}
private void sendInitialHandshake() throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(20);
buffer.putInt(12, 4);
buffer.putInt(16, 2012);
bytesSent += channel.write(buffer);
}
boolean isSocketClosed() {
return socketClosed;
}
public long getBytesReceived() {
return bytesReceived;
}
public long getBytesSent() {
return bytesSent;
}
public Date getCreateDate() {
return createDate;
}
public String getUserName() {
return descriptor.getUserName();
}
public String getHostAndPort() {
return descriptor.getAddressAndPort();
}
public Date getLastActive() {
return lastActive;
}
public int getOutstandingResponseCount() {
return handles.cardinality();
}
public long getIdleTime() {
return System.currentTimeMillis() - lastActive.getTime();
}
public int getProtocolVersion() {
return pval;
}
public int getServerFlag() {
return flag;
}
boolean isIdle() {
return getOutstandingResponseCount() == 0 && getIdleTime() > MAX_IDLE;
}
Destination getDestination() {
return descriptor;
}
void sendMessage(Message message, ResponseListener listener) throws IOException {
short id = addListener(listener);
try {
sendMessage(id, message);
} catch (IOException x) {
removeListener(id);
throw x;
}
}
void close() {
socketClosed = true;
try {
if (channel.isConnected()) {
channel.close();
}
} catch (IOException x) {
logger.log(Level.WARNING, "Error during socket close", x);
}
}
@Override
public String toString() {
return descriptor.toString()+";"+channel.socket().getLocalPort();
}
private synchronized short addListener(ResponseListener listener) {
short handle = (short) handles.nextClearBit(0);
handles.set(handle);
responseMap.put(handle, listener);
return handle;
}
private synchronized void removeListener(short id) {
responseMap.remove(id);
handles.clear(id);
}
private void sendMessage(short id, Message message) throws IOException {
bytesSent += message.send(id, channel);
lastActive.setTime(System.currentTimeMillis());
}
private void handleResponse() throws IOException {
int status = response.getStatus();
final Short handle = response.getHandle();
final ResponseListener handler;
lastActive.setTime(System.currentTimeMillis());
synchronized (Multiplexor.this) {
handler = responseMap.get(handle);
}
if (handler == null && status != XrootdProtocol.kXR_attn) {
if (status == XrootdProtocol.kXR_error) {
int rc = response.readInt();
String message = response.getDataAsString();
logger.log(Level.SEVERE, this + " Out-of-band error " + rc + ": " + message);
return; // Just carry on in this case??
}
throw new IOException(this + " No handler found for handle " + handle + " (status=" + status + ")");
}
switch (status) {
case XrootdProtocol.kXR_error:
int rc = response.readInt();
String message = response.getDataAsString();
handler.handleError(new IOException("Xrootd error " + rc + ": " + message));
removeListener(handle);
break;
case XrootdProtocol.kXR_wait:
int seconds = response.readInt();
message = response.getDataAsString();
logger.info(this + " wait: " + message + " seconds=" + seconds);
handler.reschedule(seconds, TimeUnit.SECONDS);
removeListener(handle);
break;
case XrootdProtocol.kXR_waitresp:
seconds = response.readInt();
message = response.getDataAsString();
logger.fine(this + " waitresp: " + message + " seconds=" + seconds);
break;
case XrootdProtocol.kXR_redirect:
int port = response.readInt();
String host = response.getDataAsString();
logger.fine(this + " redirect: " + host + " " + port);
handler.handleRedirect(host, port);
removeListener(handle);
break;
case XrootdProtocol.kXR_attn:
int code = response.readInt();
if (code == XrootdProtocol.kXR_asynresp) {
response.readInt(); // reserved
response.regurgitate();
handleResponse();
return;
} else {
throw new IOException("Xrootd: Unimplemented asycn message received: " + code);
}
case XrootdProtocol.kXR_ok:
case XrootdProtocol.kXR_oksofar:
handler.handleResponse(response);
if (response.isComplete()) {
removeListener(handle);
}
break;
default:
throw new IOException("Xrootd: Unimplemented status received: " + status);
}
}
private void handleSocketException(IOException x) {
if (!socketClosed) {
logger.log(Level.WARNING, this + " Unexpected IO exception on socket", x);
close();
// Notify anyone listening for a response that we are dead
for (ResponseListener listener : responseMap.values()) {
logger.fine(this + " sending handleSocketError to " + listener);
listener.handleSocketError(x);
}
responseMap.clear();
}
}
private class SocketReader implements Runnable {
public void run() {
try {
channel.connect(descriptor.getSocketAddress());
sendInitialHandshake();
for (; !thread.isInterrupted();) {
bytesReceived += response.read();
handleResponse();
}
logger.log(Level.FINE, this + " multiplexor thread exiting due to interrupt!");
} catch (IOException x) {
handleSocketException(x);
} catch (Throwable x) {
logger.log(Level.SEVERE, this + " multiplexor thread dead!", x);
}
}
}
}