package com.techq.available.quorum.handler;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.jute.BinaryInputArchive;
import org.apache.jute.BinaryOutputArchive;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.forest.ape.nio.ServerCnxnFactory;
import com.forest.ape.server.ApeServer;
import com.forest.ape.server.Type;
import com.techq.available.AvailableConfig;
import com.techq.available.connector.impl.DataCnxManager;
import com.techq.available.data.BasicPacket;
import com.techq.available.quorum.DebugConfig;
import com.techq.available.quorum.Election;
import com.techq.available.quorum.Notification;
import com.techq.available.quorum.QuorumPeer;
import com.techq.available.quorum.QuorumPeer.QuorumServer;
import com.techq.available.quorum.ServerState;
/**
*
* @author CHQ
* 2012-2-3
*/
public class Follower implements Learner {
private static final Logger LOG = LoggerFactory.getLogger(Follower.class);
LinkedBlockingQueue<BasicPacket> sendqueue = new LinkedBlockingQueue<BasicPacket>();
LinkedBlockingQueue<BasicPacket> recvqueue = new LinkedBlockingQueue<BasicPacket>();
DataCnxManager.MessageReceiver recvThread;
DataCnxManager.MessageSender sendThread;
long leaderId;
long xid;
long myId;
long defaultClock = -1;
volatile boolean followLeaderFailed = false;
PINGWorker worker;
static Follower instance = null;
volatile boolean following = true;
long tick = 0;
volatile long curTick = 0;
Socket socket;
InetSocketAddress clientAddr;
@Override
public void addPackets(BasicPacket buf) {
sendqueue.add(buf);
}
public Follower(long leaderId, long xid, long myId, InetSocketAddress addr) throws IOException, InterruptedException {
super();
this.leaderId = leaderId;
this.xid = xid;
this.myId = myId;
LOG.info("follower[id="+myId+"] connnect to leader[id="+leaderId+"] port:" + addr.getPort());
socket = connectToLeader(addr);
recvThread = new DataCnxManager.MessageReceiver(this.socket, recvqueue, myId);
sendThread = new DataCnxManager.MessageSender(this.socket, sendqueue, myId);
worker = new PINGWorker(myId);
this.clientAddr = new InetSocketAddress(AvailableConfig.CLIENT_SERVING_IP, AvailableConfig.clientPorts.get(myId));
}
/**
* Establish a connection with the Leader found by findLeader. Retries
* 5 times before giving up.
* @param addr - the address of the Leader to connect to.
* @throws IOException - if the socket connection fails on the 5th attempt
* @throws ConnectException
* @throws InterruptedException
*/
protected Socket connectToLeader(InetSocketAddress addr)
throws IOException, ConnectException, InterruptedException {
Socket sock = new Socket();
sock.setSoTimeout(AvailableConfig.tickTime * AvailableConfig.syncTick);
for (int tries = 0; tries < 5; tries++) {
try {
sock.connect(addr, AvailableConfig.tickTime * AvailableConfig.syncTick);
sock.setTcpNoDelay(true);
break;
} catch (IOException e) {
if (tries == 4) {
LOG.error("Unexpected exception",e);
throw e;
} else {
LOG.warn("Unexpected exception, tries="+tries+
", connecting to " + addr,e);
sock = new Socket();
sock.setSoTimeout(AvailableConfig.tickTime * AvailableConfig.syncTick);
}
}
Thread.sleep(1000);
}
return sock;
}
public void shutdown() {
LOG.warn("#shut down ping worker");
recvThread.halt();
sendThread.halt();
following = false;
this.worker.finish();
}
// public void followLeader() {
// if (!worker.hasWorked) {
// worker.start();
// try {
// latch.await();
// } catch (InterruptedException e) {
// LOG.error("latch await error", e);
// }
// }
// else {
// LOG.debug("ping worker is still running...");
// }
// }
public boolean followLeader() {
recvThread.start();
sendThread.start();
if (!worker.hasWorked) {
worker.start();
}
long time = 0;
ServerCnxnFactory factory = null;
try {
factory = ServerCnxnFactory.createFactory();
factory.configure(this.clientAddr, AvailableConfig.MAX_CLIENT_CNXN);
factory.setServer(new ApeServer(this));
factory.start();
} catch (Exception e) {
LOG.error("error happened when starting serving for clients, caused:", e);
if (factory != null)
factory.shutdown();
shutdown();
return false;
}
while(following) {
try {
long lastAck = curTick;
TimeUnit.MILLISECONDS.sleep(AvailableConfig.tickTime);
this.tick++;
time++;
LOG.debug("tick:" + this.tick + ", lastAck:" + this.curTick);
if (!sync()) {
shutdown();
LOG.error("can't sync with leader, tick:" + this.tick + ", curTick:" + this.curTick);
return false;
} else {
LOG.info("sync time:" + time + " following leader[id=" +this.leaderId+ "]");
//if find any deviation, correct it
//correct(lastAck);
}
} catch (InterruptedException e) {
shutdown();
LOG.error("InterruptedException when following leader", e);
return false;
}
}
shutdown();
return true;
}
public void stopFollowing() {
LOG.info("stop following leader gracefully");
following = false;
}
public void correct(long lastAck) {
if (this.tick != curTick && curTick > lastAck)
curTick = this.tick;
}
public boolean sync() {
return worker.isAlive() && curTick > tick - AvailableConfig.syncTick;
}
class PINGWorker extends Thread {
boolean hasWorked = false;
volatile boolean isRunning = false;
int monitorTime = 0;
boolean isFirstTime = true;
int failedCnt = 0;
int rubbishCnt = 0;
AtomicInteger i = new AtomicInteger(0);
public PINGWorker(long id) {
super("PINGWorker[myid=" + id + "]");
}
public void run() {
try {
hasWorked = true;
BasicPacket n = null;
isRunning = true;
while(isRunning) {
if (isFirstTime) {
isFirstTime = false;
n = new BasicPacket(Type.AGREEMENT, xid, myId, null);
LOG.info("send AGREEMENT to leader[id=" + leaderId + "]");
sendqueue.offer(n);
} else {
n = new BasicPacket(Type.PING, xid, myId, null);
LOG.debug("send Not to leader:" + n);
sendqueue.offer(n);
}
n = recvqueue.poll(AvailableConfig.followTimeOut, AvailableConfig.pollTimeUnit);
if (n == null) {
//restart
// failedCnt++;
// while(failedCnt < AvailableConfig.followFailedCntLimit && n == null) {
// n = this.election.pollPing(AvailableConfig.followTimeOut, AvailableConfig.pollTimeUnit);
// }
// if (failedCnt == AvailableConfig.followFailedCntLimit) {
// LOG.error("fail polling times:" +failedCnt );
// followLeaderFailed = true;
// return;
// }
// latch.countDown();
continue;
}
LOG.debug("recv Mes from leader["+leaderId+"]:" + n);
switch (n.getType()) {
case Type.CONFIRM:
monitorTime++;
if (monitorTime == 1) {
LOG.info("recv leader[id="+n.getFrom()+"] confirmed, cheer!");
} else {
LOG.error("there must be something wrong with this code, recv confirm from leader too many times : " + monitorTime);
followLeaderFailed = true;
return;
}
curTick++;
break;
case Type.ACK:
LOG.debug("recv leader ACK, myid is " + myId + ", xid is " + n.getXid());
curTick++;
break;
case Type.DATA:
LOG.debug("recv data myid is " + myId);
throw new IOException("what data you send!!! Damn it!!, type:" + Type.DATA);
default:
rubbishCnt++;
LOG.debug("recv rubbish msg:" + n + ", rubbish cnt : " + rubbishCnt);
break;
}
TimeUnit.MILLISECONDS.sleep(AvailableConfig.tickTime);
}
} catch (InterruptedException e) {
LOG.error("follow leader InterruptedException, reason:" , e);
shutdown();
} catch (IOException e) {
LOG.error(e.getMessage() , e);
shutdown();
}
}
public void finish() {
LOG.debug("finish PINGWorker");
if (!isRunning)
return;
isRunning = false;
this.interrupt();
}
}
public class DataProcessor extends Thread {
volatile boolean isRunning = true;
public void run() {
while(isRunning) {
}
}
public void halt() {
if (!isRunning)
return;
isRunning = false;
this.interrupt();
}
}
@Override
public boolean isLeader() {
return false;
}
@Override
public long getId() {
return myId;
}
}