package org.opencloudb.backend;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import org.opencloudb.config.Alarms;
import org.opencloudb.config.model.DBHostConfig;
import org.opencloudb.config.model.DataHostConfig;
import org.opencloudb.heartbeat.DBHeartbeat;
import org.opencloudb.mysql.nio.handler.DelegateResponseHandler;
import org.opencloudb.mysql.nio.handler.ResponseHandler;
import org.opencloudb.route.RouteResultsetNode;
import org.opencloudb.server.ServerConnection;
import org.opencloudb.util.TimeUtil;
public abstract class PhysicalDatasource {
private static final Logger LOGGER = Logger
.getLogger(PhysicalDatasource.class);
private final String name;
private final ReentrantLock lock = new ReentrantLock();
private final int size;
private final DBHostConfig config;
private final PhysicalConnection[] items;
private DBHeartbeat heartbeat;
private final boolean readNode;
private volatile long heartbeatRecoveryTime;
private final DataHostConfig hostConfig;
private PhysicalDBPool dbPool;
private long executeCount;
public PhysicalDatasource(DBHostConfig config, DataHostConfig hostConfig,
boolean isReadNode) {
this.size = config.getMaxCon();
this.items = new PhysicalConnection[size];
this.config = config;
this.name = config.getHostName();
this.hostConfig = hostConfig;
heartbeat = this.createHeartBeat();
this.readNode = isReadNode;
}
public boolean isMyConnection(PhysicalConnection con) {
PhysicalConnection[] theItems = null;
final ReentrantLock lock = this.lock;
lock.lock();
try {
theItems = this.items;
} finally {
lock.unlock();
}
for (PhysicalConnection myCon : theItems) {
if (myCon == con) {
return true;
}
}
return false;
}
public DataHostConfig getHostConfig() {
return hostConfig;
}
public boolean isReadNode() {
return readNode;
}
public int getSize() {
return size;
}
public void setDbPool(PhysicalDBPool dbPool) {
this.dbPool = dbPool;
}
public PhysicalDBPool getDbPool() {
return dbPool;
}
public abstract DBHeartbeat createHeartBeat();
public String getName() {
return name;
}
public long getExecuteCount() {
return executeCount;
}
public int getActiveCount() {
int running = 0;
for (PhysicalConnection con : this.items) {
if (con != null && con.isBorrowed()) {
running++;
}
}
return running;
}
public DBHeartbeat getHeartbeat() {
return heartbeat;
}
public int getIdleCount() {
int idle = 0;
for (PhysicalConnection con : this.items) {
if (con != null && !con.isBorrowed()) {
idle++;
}
}
return idle;
}
public void idleCheck(long timeout) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final PhysicalConnection[] items = this.items;
long time = TimeUtil.currentTimeMillis() - timeout;
for (int i = 0; i < items.length; i++) {
PhysicalConnection c = items[i];
if (c != null && time > c.getLastTime()) {
c.closeNoActive(" idle ");
items[i] = null;
}
}
} finally {
lock.unlock();
}
}
public void clearCons(String reason) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final PhysicalConnection[] items = this.items;
for (int i = 0; i < items.length; i++) {
PhysicalConnection c = items[i];
if (c != null) {
c.closeNoActive(reason);
items[i] = null;
}
}
} finally {
lock.unlock();
}
}
public void startHeartbeat() {
heartbeat.start();
}
public void stopHeartbeat() {
heartbeat.stop();
}
public void doHeartbeat() {
// 未到预定恢复时间,不执行心跳检测。
if (TimeUtil.currentTimeMillis() < heartbeatRecoveryTime) {
return;
}
if (!heartbeat.isStop()) {
try {
heartbeat.heartbeat();
} catch (Throwable e) {
LOGGER.error(name + " heartbeat error.", e);
}
}
}
private PhysicalConnection takeCon(PhysicalConnection conn,
final ResponseHandler handler, final Object attachment,
String schema) {
conn.setBorrowed(true);
if (schema != null) {
conn.setSchema(schema);
}
executeCount++;
conn.setAttachment(attachment);
handler.connectionAcquired(conn);
return conn;
}
public PhysicalConnection getConnection(final ResponseHandler handler,
final Object attachment, final String schema) throws Exception {
int activeCount = this.getActiveCount();
// used to store new created connection
int emptyIndex = -1;
final ReentrantLock lock = this.lock;
lock.lock();
try {
// get connection from pool
final PhysicalConnection[] items = this.items;
int oldestIdleConIndx = -1;
long oldestConTime = Long.MAX_VALUE;
for (int i = 0; i < items.length; i++) {
if (items[i] == null) {
emptyIndex = i;
} else if (items[i] != null) {
PhysicalConnection conn = items[i];
// closed or quit
if (conn.isClosedOrQuit()) {
items[i] = null;
emptyIndex = i;
continue;
} else if (!conn.isBorrowed()) {
// can use
if (schema.equals(conn.getSchema())) {
return takeCon(items[i], handler, attachment, null);
} else if (conn.getLastTime() < oldestConTime) {
oldestIdleConIndx = i;
oldestConTime = conn.getLastTime();
}
}
}
}
if (oldestIdleConIndx != -1) {
return takeCon(items[oldestIdleConIndx], handler, attachment,
schema);
} else if (emptyIndex == -1) {
StringBuilder s = new StringBuilder();
s.append(Alarms.DEFAULT).append("DATASOURCE EXCEED [name=")
.append(name).append(",active=");
s.append(activeCount).append(",size=").append(size).append(']');
LOGGER.warn(s.toString());
throw new IOException("datasource is full,can't get any more "
+ this.getName());
} else {
// creat new connection
items[emptyIndex] = new FakeConnection();
}
} finally {
lock.unlock();
}
LOGGER.info("not ilde connection in pool,create new connection for "
+ this.name);
final int insertIndex = emptyIndex;
// create connection
return this.createNewConnection(new DelegateResponseHandler(handler) {
@Override
public void connectionError(Throwable e, PhysicalConnection conn) {
handler.connectionError(e, conn);
lock.lock();
try {
items[insertIndex] = null;
} finally {
lock.unlock();
}
}
@Override
public void connectionAcquired(PhysicalConnection conn) {
lock.lock();
try {
items[insertIndex] = conn;
takeCon(conn, handler, attachment, schema);
} finally {
lock.unlock();
}
}
});
}
private void returnCon(PhysicalConnection c) {
c.setBorrowed(false);
c.setLastTime(TimeUtil.currentTimeMillis());
}
public void releaseChannel(PhysicalConnection c) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("release channel " + c);
}
// release connection
returnCon(c);
}
public abstract PhysicalConnection createNewConnection(
ResponseHandler handler) throws IOException;
public void deActive(PhysicalConnection con) {
returnCon(con);
}
public long getHeartbeatRecoveryTime() {
return heartbeatRecoveryTime;
}
public void setHeartbeatRecoveryTime(long heartbeatRecoveryTime) {
this.heartbeatRecoveryTime = heartbeatRecoveryTime;
}
public DBHostConfig getConfig() {
return config;
}
}
class FakeConnection implements PhysicalConnection {
@Override
public boolean isFromSlaveDB() {
return false;
}
@Override
public String getSchema() {
return null;
}
@Override
public void setSchema(String newSchema) {
}
@Override
public long getLastTime() {
return System.currentTimeMillis();
}
@Override
public void closeNoActive(String reason) {
}
@Override
public boolean isClosedOrQuit() {
return false;
}
@Override
public void setAttachment(Object attachment) {
}
@Override
public void quit() {
}
@Override
public void setLastTime(long currentTimeMillis) {
}
@Override
public void release() {
}
@Override
public void close(String reason) {
}
@Override
public void setRunning(boolean running) {
}
@Override
public boolean setResponseHandler(ResponseHandler commandHandler) {
return false;
}
@Override
public void commit() {
}
@Override
public void query(String sql) throws UnsupportedEncodingException {
}
@Override
public Object getAttachment() {
return null;
}
@Override
public long getThreadId() {
return 0;
}
@Override
public String getCharset() {
return null;
}
@Override
public void execute(RouteResultsetNode node, ServerConnection source,
boolean autocommit) throws IOException {
}
@Override
public void recordSql(String host, String schema, String statement) {
}
@Override
public boolean syncAndExcute() throws UnsupportedEncodingException {
return false;
}
@Override
public void rollback() {
}
@Override
public boolean isSuppressReadTemporay() {
return false;
}
@Override
public void setSuppressReadTemporay(boolean b) {
}
@Override
public boolean isRunning() {
return false;
}
@Override
public boolean isBorrowed() {
return true;
}
@Override
public void setBorrowed(boolean borrowed) {
}
@Override
public boolean isAutocommit() {
return false;
}
@Override
public boolean isModifiedSQLExecuted() {
return false;
}
}