package net.spy.memcached.protocol;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.BlockingQueue;
import net.spy.SpyObject;
import net.spy.memcached.MemcachedNode;
import net.spy.memcached.ops.GetOperation;
import net.spy.memcached.ops.Operation;
import net.spy.memcached.ops.OperationState;
/**
* Represents a node with the memcached cluster, along with buffering and
* operation queues.
*/
public abstract class TCPMemcachedNodeImpl extends SpyObject
implements MemcachedNode {
private final SocketAddress socketAddress;
private final ByteBuffer rbuf;
private final ByteBuffer wbuf;
protected final BlockingQueue<Operation> writeQ;
private final BlockingQueue<Operation> readQ;
private final BlockingQueue<Operation> inputQueue;
// This has been declared volatile so it can be used as an availability
// indicator.
private volatile int reconnectAttempt=1;
private SocketChannel channel;
private int toWrite=0;
protected GetOperation getOp=null;
private SelectionKey sk=null;
public TCPMemcachedNodeImpl(SocketAddress sa, SocketChannel c,
int bufSize, BlockingQueue<Operation> rq,
BlockingQueue<Operation> wq, BlockingQueue<Operation> iq) {
super();
assert sa != null : "No SocketAddress";
assert c != null : "No SocketChannel";
assert bufSize > 0 : "Invalid buffer size: " + bufSize;
assert rq != null : "No operation read queue";
assert wq != null : "No operation write queue";
assert iq != null : "No input queue";
socketAddress=sa;
setChannel(c);
rbuf=ByteBuffer.allocate(bufSize);
wbuf=ByteBuffer.allocate(bufSize);
getWbuf().clear();
readQ=rq;
writeQ=wq;
inputQueue=iq;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#copyInputQueue()
*/
public final void copyInputQueue() {
Collection<Operation> tmp=new ArrayList<Operation>();
// don't drain more than we have space to place
inputQueue.drainTo(tmp, writeQ.remainingCapacity());
writeQ.addAll(tmp);
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#setupResend()
*/
public final void setupResend() {
// First, reset the current write op.
Operation op=getCurrentWriteOp();
if(op != null) {
op.getBuffer().reset();
}
// Now cancel all the pending read operations. Might be better to
// to requeue them.
while(hasReadOp()) {
op=removeCurrentReadOp();
getLogger().warn("Discarding partially completed op: %s", op);
op.cancel();
}
getWbuf().clear();
getRbuf().clear();
toWrite=0;
}
// Prepare the pending operations. Return true if there are any pending
// ops
private boolean preparePending() {
// Copy the input queue into the write queue.
copyInputQueue();
// Now check the ops
Operation nextOp=getCurrentWriteOp();
while(nextOp != null && nextOp.isCancelled()) {
getLogger().info("Removing cancelled operation: %s", nextOp);
removeCurrentWriteOp();
nextOp=getCurrentWriteOp();
}
return nextOp != null;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#fillWriteBuffer(boolean)
*/
public final void fillWriteBuffer(boolean optimizeGets) {
if(toWrite == 0 && readQ.remainingCapacity() > 0) {
getWbuf().clear();
Operation o=getCurrentWriteOp();
while(o != null && toWrite < getWbuf().capacity()) {
assert o.getState() == OperationState.WRITING;
ByteBuffer obuf=o.getBuffer();
int bytesToCopy=Math.min(getWbuf().remaining(),
obuf.remaining());
byte b[]=new byte[bytesToCopy];
obuf.get(b);
getWbuf().put(b);
getLogger().debug("After copying stuff from %s: %s",
o, getWbuf());
if(!o.getBuffer().hasRemaining()) {
o.writeComplete();
transitionWriteItem();
preparePending();
if(optimizeGets) {
optimize();
}
o=getCurrentWriteOp();
}
toWrite += bytesToCopy;
}
getWbuf().flip();
assert toWrite <= getWbuf().capacity()
: "toWrite exceeded capacity: " + this;
assert toWrite == getWbuf().remaining()
: "Expected " + toWrite + " remaining, got "
+ getWbuf().remaining();
} else {
getLogger().debug("Buffer is full, skipping");
}
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#transitionWriteItem()
*/
public final void transitionWriteItem() {
Operation op=removeCurrentWriteOp();
assert op != null : "There is no write item to transition";
getLogger().debug("Transitioning %s to read", op);
readQ.add(op);
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#optimize()
*/
protected abstract void optimize();
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#getCurrentReadOp()
*/
public final Operation getCurrentReadOp() {
return readQ.peek();
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#removeCurrentReadOp()
*/
public final Operation removeCurrentReadOp() {
return readQ.remove();
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#getCurrentWriteOp()
*/
public final Operation getCurrentWriteOp() {
return getOp == null ? writeQ.peek() : getOp;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#removeCurrentWriteOp()
*/
public final Operation removeCurrentWriteOp() {
Operation rv=getOp;
if(rv == null) {
rv=writeQ.remove();
} else {
getOp=null;
}
return rv;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#hasReadOp()
*/
public final boolean hasReadOp() {
return !readQ.isEmpty();
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#hasWriteOp()
*/
public final boolean hasWriteOp() {
return !(getOp == null && writeQ.isEmpty());
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#addOp(net.spy.memcached.ops.Operation)
*/
public final void addOp(Operation op) {
boolean added=inputQueue.add(op);
assert added; // documented to throw an IllegalStateException
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#getSelectionOps()
*/
public final int getSelectionOps() {
int rv=0;
if(getChannel().isConnected()) {
if(hasReadOp()) {
rv |= SelectionKey.OP_READ;
}
if(toWrite > 0 || hasWriteOp()) {
rv |= SelectionKey.OP_WRITE;
}
} else {
rv = SelectionKey.OP_CONNECT;
}
return rv;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#getRbuf()
*/
public final ByteBuffer getRbuf() {
return rbuf;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#getWbuf()
*/
public final ByteBuffer getWbuf() {
return wbuf;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#getSocketAddress()
*/
public final SocketAddress getSocketAddress() {
return socketAddress;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#isActive()
*/
public final boolean isActive() {
return reconnectAttempt == 0
&& getChannel() != null && getChannel().isConnected();
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#reconnecting()
*/
public final void reconnecting() {
reconnectAttempt++;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#connected()
*/
public final void connected() {
reconnectAttempt=0;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#getReconnectCount()
*/
public final int getReconnectCount() {
return reconnectAttempt;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#toString()
*/
@Override
public final String toString() {
int sops=0;
if(getSk()!= null && getSk().isValid()) {
sops=getSk().interestOps();
}
int rsize=readQ.size() + (getOp == null ? 0 : 1);
int wsize=writeQ.size();
int isize=inputQueue.size();
return "{QA sa=" + getSocketAddress() + ", #Rops=" + rsize
+ ", #Wops=" + wsize
+ ", #iq=" + isize
+ ", topRop=" + getCurrentReadOp()
+ ", topWop=" + getCurrentWriteOp()
+ ", toWrite=" + toWrite
+ ", interested=" + sops + "}";
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#registerChannel(java.nio.channels.SocketChannel, java.nio.channels.SelectionKey)
*/
public final void registerChannel(SocketChannel ch, SelectionKey skey) {
setChannel(ch);
setSk(skey);
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#setChannel(java.nio.channels.SocketChannel)
*/
public final void setChannel(SocketChannel to) {
assert channel == null || !channel.isOpen()
: "Attempting to overwrite channel";
channel = to;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#getChannel()
*/
public final SocketChannel getChannel() {
return channel;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#setSk(java.nio.channels.SelectionKey)
*/
public final void setSk(SelectionKey to) {
sk = to;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#getSk()
*/
public final SelectionKey getSk() {
return sk;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#getBytesRemainingInBuffer()
*/
public final int getBytesRemainingToWrite() {
return toWrite;
}
/* (non-Javadoc)
* @see net.spy.memcached.MemcachedNode#writeSome()
*/
public final int writeSome() throws IOException {
int wrote=channel.write(wbuf);
assert wrote >= 0 : "Wrote negative bytes?";
toWrite -= wrote;
assert toWrite >= 0
: "toWrite went negative after writing " + wrote
+ " bytes for " + this;
getLogger().debug("Wrote %d bytes", wrote);
return wrote;
}
public final void fixupOps() {
if(sk != null && sk.isValid()) {
int iops=getSelectionOps();
getLogger().debug("Setting interested opts to %d", iops);
sk.interestOps(iops);
} else {
getLogger().debug("Selection key is not valid.");
}
}
}