package lsr.paxos.replica;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import lsr.common.ClientCommand;
import lsr.common.ClientCommand.CommandType;
import lsr.common.ClientReply;
import lsr.common.ClientReply.Result;
import lsr.common.ClientRequest;
import lsr.common.MovingAverage;
import lsr.common.RequestId;
import lsr.common.SingleThreadDispatcher;
import lsr.paxos.client.Client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InternalClient {
private static final int FIRST_AVERAGE = 500;
private MovingAverage averageRequestTime = new MovingAverage(0.3, FIRST_AVERAGE);
private final SingleThreadDispatcher internalClientDispatcher;
private final ClientRequestManager clientRequestManager;
private final Deque<RequestId> freeIds = new ArrayDeque<RequestId>();
public InternalClient(SingleThreadDispatcher replicaDispatcher,
ClientRequestManager clientRequestManager) {
internalClientDispatcher = new SingleThreadDispatcher("InternalClientDispatcher");
this.clientRequestManager = clientRequestManager;
}
/**
* @throws InterruptedException in case the replica has been interrupted
* while waiting to put the request on propose queue
*/
public void executeNonFifo(final byte[] request) {
internalClientDispatcher.execute(new Runnable() {
public void run() {
executeNonFifoInternal(request);
}
});
}
public void executeNonFifoInternal(byte[] request) {
internalClientDispatcher.checkInDispatcher();
RequestId reqId = freeIds.poll();
if (reqId == null)
reqId = new RequestId(NioClientProxy.idGenerator.next(), 0);
ClientRequest cr = new ClientRequest(reqId, request);
ClientCommand cc = new ClientCommand(CommandType.REQUEST, cr);
InternalClientProxy icp = new InternalClientProxy(reqId);
RequestRepeater rr = new RequestRepeater(cc, icp);
long timeout = (long) (3 * averageRequestTime.get());
timeout = Math.min(timeout, Client.MAX_TIMEOUT);
timeout = Math.max(timeout, Client.MIN_TIMEOUT);
icp.setRepeater(rr, internalClientDispatcher.schedule(rr, timeout, TimeUnit.MILLISECONDS));
logger.debug("InternalClient proposes: {}", reqId);
clientRequestManager.dispatchOnClientRequest(cc, icp);
}
protected class InternalClientProxy implements ClientProxy {
private final long cliId;
private final int seqNo;
private RequestRepeater repeater;
private ScheduledFuture<?> sf;
private final long startTime = System.currentTimeMillis();
private boolean finished = false;
public InternalClientProxy(RequestId reqId) {
cliId = reqId.getClientId();
seqNo = reqId.getSeqNumber();
}
/** Called upon generating the answer for previous request */
public void send(ClientReply clientReply) {
assert Result.OK.equals(clientReply.getResult());
internalClientDispatcher.execute(new Runnable() {
public void run() {
requestDelivered();
}
});
}
public void requestDelivered() {
internalClientDispatcher.checkInDispatcher();
if (finished)
/*
* If the request is ordered multiple times, it is scheduled to
* send the request twice to the client (?). Here it matters, as
* free ID can be returned only once to the pool.
*/
return;
finished = true;
logger.debug("InternalClient completed {}:{}", cliId, seqNo);
averageRequestTime.add(System.currentTimeMillis() - startTime);
freeIds.add(new RequestId(cliId, seqNo + 1));
internalClientDispatcher.remove(repeater);
sf.cancel(false);
}
public void setRepeater(RequestRepeater repeater, ScheduledFuture<?> sf) {
this.repeater = repeater;
this.sf = sf;
}
}
protected class RequestRepeater implements Runnable {
private final ClientCommand cc;
private final InternalClientProxy icp;
public RequestRepeater(ClientCommand cc, InternalClientProxy icp) {
this.cc = cc;
this.icp = icp;
}
public void run() {
internalClientDispatcher.checkInDispatcher();
if (!shouldRepeat())
return;
if (logger.isTraceEnabled())
logger.trace("InternalClient re-proposes: {}", cc.getRequest().getRequestId());
long timeout = (long) (3 * averageRequestTime.get());
timeout = Math.min(timeout, Client.MAX_TIMEOUT);
timeout = Math.max(timeout, Client.MIN_TIMEOUT);
icp.sf = internalClientDispatcher.schedule(this, timeout, TimeUnit.MILLISECONDS);
clientRequestManager.dispatchOnClientRequest(cc, icp);
}
private boolean shouldRepeat() {
// FIXME: (JK) check if the request deciding is in progress
/*
* The problem is that tracing each request is not easy,
*/
return true;
}
}
private final static Logger logger = LoggerFactory.getLogger(InternalClient.class);
}