package com.techq.available.quorum.handler;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.forest.ape.nio.ServerCnxnFactory;
import com.forest.ape.server.ApeServer;
import com.techq.available.AvailableConfig;
import com.techq.available.data.BasicPacket;
import com.techq.available.quorum.Election;
import com.techq.available.quorum.Notification;
public class Leader implements Learner {
private static final Logger LOG = LoggerFactory.getLogger(Leader.class);
LinkedBlockingQueue<Notification> rubbishPacket = new LinkedBlockingQueue<Notification>();
volatile boolean reachQuorums = false;
volatile long tick = 0;
long leaderId = -1;
volatile long expectClock = -1;
int quorumSize = 0;
boolean isWorkStarted = false;
int pingTick = 0;
volatile boolean hasPrepared = false;
int correctDeviation = 0;
ServerSocket ss;
Set<Long> followers;
Set<Long> quorums = new HashSet<Long>();
private Set<LearnerHandler> learnerHandlers = new HashSet<LearnerHandler>();
Leader self;
DataHandler dataHandler;
InetSocketAddress address;
@Override
public void addPackets(BasicPacket packet) {
dataHandler.mqQueue.add(packet);
}
class LearnerCnxAcceptor extends Thread {
private volatile boolean stop = false;
@Override
public void run() {
try {
LOG.info("leader[id=" +leaderId + "] listen on:" + ss.getLocalPort());
while (!stop) {
try {
Socket s = ss.accept();
s.setSoTimeout(AvailableConfig.tickTime * AvailableConfig.connectedTick);
s.setTcpNoDelay(true);
LearnerHandler fh = new LearnerHandler(s, self, leaderId);
fh.start();
} catch (SocketException e) {
if (stop) {
LOG.info("exception while shutting down acceptor: " + e);
// When Leader.shutdown() calls ss.close(),
// the call to accept throws an exception.
// We catch and set stop to true.
stop = true;
} else {
throw e;
}
}
}
} catch (Exception e) {
LOG.warn("Exception while accepting follower", e);
}
}
public void halt() {
stop = true;
if (this.isAlive())
this.interrupt();
LOG.debug("halt accepter");
}
}
LearnerCnxAcceptor acceptor;
public Leader(long leaderId, Set<Long> followers, InetSocketAddress addr) throws Exception {
self = this;
ss = new ServerSocket(addr.getPort());
this.address = new InetSocketAddress(AvailableConfig.CLIENT_SERVING_IP, AvailableConfig.clientPorts.get(leaderId));
tick = 0;
this.leaderId = leaderId;
this.followers = followers;
reachQuorums = true;
dataHandler = new DataHandler(this);
quorumSize = followers.size();
}
public boolean leading() throws IOException, InterruptedException{
//wait for the followers
acceptor = new LearnerCnxAcceptor();
acceptor.start();
/**
* reach quorums first
*/
this.tick = 0;
while(quorums.size() * 2 > followers.size()) {
if (tick >= AvailableConfig.connectedTick) {
LOG.error("restart looking for leader, reason: can't reach quorums before started");
shutdown();
return false;
}
TimeUnit.MILLISECONDS.sleep(AvailableConfig.tickTime);
this.tick++;
}
for (LearnerHandler leaner : learnerHandlers) {
leaner.start();
}
dataHandler.start();
this.tick = 0;
int time = 0;
ServerCnxnFactory factory = ServerCnxnFactory.createFactory();
factory.configure(this.address, AvailableConfig.MAX_CLIENT_CNXN);
try {
factory.setServer(new ApeServer(this));
factory.start();
} catch (Exception e) {
LOG.error("error happened when starting serving for clients, caused:", e);
factory.shutdown();
shutdown();
return false;
}
while (reachQuorums) {
TimeUnit.MILLISECONDS.sleep(AvailableConfig.tickTime);
int cnt = 1;
for (LearnerHandler leaner : learnerHandlers) {
if (leaner.sync(this.tick)) {
cnt++;
//leaner.correct(this.tick);
}
}
if (cnt * 2 < quorumSize){
LOG.error("restart looking for leader, reason: expect quorum num is : " + quorumSize
+ ", but now it is " + cnt);
shutdown();
return false;
} else {
LOG.info("sync time:" + (++time) + ", tick:" + this.tick + ", quorumNum:" + cnt);
}
this.tick++;
}
shutdown();
if (reachQuorums == true && this.tick < AvailableConfig.syncTick)
return true;
return false;
}
public void shutdown() throws IOException {
LOG.warn("#shutdown");
if (!ss.isClosed()) {
ss.close();
}
if (acceptor.isAlive()) {
acceptor.halt();
}
if (dataHandler.isAlive())
dataHandler.halt();
CopyOnWriteArraySet<LearnerHandler> copyLeaders =
new CopyOnWriteArraySet<LearnerHandler>(learnerHandlers);
for (LearnerHandler leader : copyLeaders) {
leader.shutdown();
}
}
class PreparedWorker extends Thread {
volatile boolean isRunning = true;
Election election;
PreparedWorker(Election election) {
this.election = election;
}
public void run() {
while(isRunning) {
Set<Long> quorumSids = new HashSet<Long>();
quorumSids.add(leaderId);
try {
Notification n = this.election.pollPing(AvailableConfig.pollTimeout, AvailableConfig.pollTimeUnit);
if (n == null || n != null && !followers.contains(n.getSid())) {
if (n != null) {
LOG.warn("rubbish message find in preparing:" + n);
}
continue;
}
if (n.getType().equals(Notification.mType.AGREEMENT) && n.getLeader() == leaderId) {
quorumSids.add(n.getFrom());
n.setType(Notification.mType.CONFIRM);
n.setFrom(leaderId);
this.election.offerACK(n);
if (quorumSids.size() * 2 > quorumSize) {
hasPrepared = true;
isRunning = false;
break;
}
} else {
LOG.warn("rubbish message find in preparing:" + n);
}
} catch (InterruptedException e) {
e.printStackTrace(System.out);
}
}
}
public void finish() {
if (!isRunning)
return;
isRunning = false;
this.interrupt();
}
}
public void addLearnerHandler(LearnerHandler learnerHandler) {
synchronized (learnerHandlers ) {
learnerHandlers.add(learnerHandler);
}
}
public void removeLearnerHandler(LearnerHandler learnerHandler) {
synchronized (learnerHandlers ) {
learnerHandlers.remove(learnerHandler);
}
}
@Override
public boolean isLeader() {
return true;
}
@Override
public long getId() {
return leaderId;
}
}