package fm.liu.timo.heartbeat;
import java.util.concurrent.atomic.AtomicInteger;
import org.pmw.tinylog.Logger;
import fm.liu.timo.backend.Node;
import fm.liu.timo.backend.Source;
import fm.liu.timo.net.connection.BackendConnection;
import fm.liu.timo.server.session.handler.HeartbeatHandler;
import fm.liu.timo.server.session.handler.HeartbeatInitHandler;
import fm.liu.timo.server.session.handler.ResultHandler;
import fm.liu.timo.util.TimeUtil;
public class Heartbeat {
private static final int RETRY = 3;
private static final String SQL = "REPLACE INTO timo_heartbeat SET id=1";
private final Source source;
private final int period;
private volatile HeartbeatStatus status;
private volatile BackendConnection connection;
private volatile long lastActiveTime;
private AtomicInteger counter = new AtomicInteger(0);
private Node node;
private HeartbeatHandler handler;
public enum HeartbeatStatus {
CHECKING, OK, PAUSED, STOPED
}
public Heartbeat(Source source, int period) {
this.source = source;
this.period = period;
this.handler = new HeartbeatHandler(this);
}
public void heartbeat(Node node) {
this.node = node;
long now = TimeUtil.currentTimeMillis();
if ((now - lastActiveTime) < period) {
return;
}
if (this.status == HeartbeatStatus.CHECKING) {
if (counter.incrementAndGet() > RETRY) {
handover();
} else {
this.updateStatus(HeartbeatStatus.OK);
this.connection.close("heartbeat timeout");
this.connection = null;
connect();
}
} else {
check();
}
}
private void check() {
if (this.connection == null) {
if (counter.incrementAndGet() > RETRY) {
handover();
} else {
connect();
}
} else {
this.updateStatus(HeartbeatStatus.CHECKING);
this.connection.query(SQL, handler);
}
}
private void handover() {
this.stop();
if (this.connection != null) {
this.connection.close("heartbeat error");
this.connection = null;
}
if (source != node.getSource()) {
source.getConfig().ban();
source.clear("clear datasource due to heartbeat error");
return;
}
if (node.handover(false)) {
Logger.info("datanode {} handover datasource{} to '{}' due to heartbeat error!",
node.getID(), source.getConfig(), node.getSource().getConfig());
} else {
Logger.error("datanode {} handover datasource:{} failed!", node.getID(),
source.getConfig());
}
}
private void connect() {
ResultHandler initHandler = new HeartbeatInitHandler(this);
try {
source.query("SET sql_log_bin=0", initHandler);
} catch (Throwable e) {
counter.incrementAndGet();
Logger.error("datasource {} error due to {}", source.getConfig().getID(), e);
return;
}
}
public void pause() {
this.updateStatus(HeartbeatStatus.PAUSED);
}
public void updateStatus(HeartbeatStatus status) {
this.status = status;
}
public void updateConnection(BackendConnection connection) {
this.connection = connection;
}
public Source getSource() {
return source;
}
public boolean isStoped() {
return status == HeartbeatStatus.STOPED;
}
public void update() {
this.lastActiveTime = TimeUtil.currentTimeMillis();
this.counter.set(0);
this.status = HeartbeatStatus.OK;
}
public int getErrorCount() {
return counter.intValue();
}
public HeartbeatStatus getStatus() {
return status;
}
public long getLastActiveTime() {
return lastActiveTime;
}
public void stop() {
this.status = HeartbeatStatus.STOPED;
}
}