/*
* Copyright 1999-2012 Alibaba Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.cobar.mysql.bio.executor;
import static com.alibaba.cobar.route.RouteResultsetNode.DEFAULT_REPLICA_INDEX;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import com.alibaba.cobar.CobarConfig;
import com.alibaba.cobar.CobarServer;
import com.alibaba.cobar.config.ErrorCode;
import com.alibaba.cobar.exception.UnknownDataNodeException;
import com.alibaba.cobar.mysql.MySQLDataNode;
import com.alibaba.cobar.mysql.PacketUtil;
import com.alibaba.cobar.mysql.bio.Channel;
import com.alibaba.cobar.mysql.bio.MySQLChannel;
import com.alibaba.cobar.net.mysql.BinaryPacket;
import com.alibaba.cobar.net.mysql.EOFPacket;
import com.alibaba.cobar.net.mysql.ErrorPacket;
import com.alibaba.cobar.net.mysql.FieldPacket;
import com.alibaba.cobar.net.mysql.MySQLPacket;
import com.alibaba.cobar.net.mysql.OkPacket;
import com.alibaba.cobar.route.RouteResultset;
import com.alibaba.cobar.route.RouteResultsetNode;
import com.alibaba.cobar.server.ServerConnection;
import com.alibaba.cobar.server.session.BlockingSession;
import com.alibaba.cobar.util.StringUtil;
/**
* 单节点数据执行器
*
* @author xianmao.hexm
*/
public final class SingleNodeExecutor extends NodeExecutor {
private static final Logger LOGGER = Logger.getLogger(SingleNodeExecutor.class);
private static final int RECEIVE_CHUNK_SIZE = 64 * 1024;
private byte packetId;
private boolean isRunning = false;
private final ReentrantLock lock = new ReentrantLock();
private final Condition taskFinished = lock.newCondition();
@Override
public void terminate() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
while (isRunning) {
taskFinished.await();
}
} finally {
lock.unlock();
}
}
/**
* 单数据节点执行
*/
public void execute(RouteResultsetNode rrn, BlockingSession ss, int flag) {
// 初始化
final ReentrantLock lock = this.lock;
lock.lock();
try {
this.packetId = 0;
this.isRunning = true;
} finally {
lock.unlock();
}
// 检查连接是否已关闭
if (ss.getSource().isClosed()) {
endRunning();
return;
}
// 单节点处理
Channel c = ss.getTarget().get(rrn);
if (c != null) {
c.setRunning(true);
bindingExecute(rrn, ss, c, flag);
} else {
newExecute(rrn, ss, flag);
}
}
/**
* 已绑定数据通道的执行
*/
private void bindingExecute(final RouteResultsetNode rrn, final BlockingSession ss, final Channel c, final int flag) {
ss.getSource().getProcessor().getExecutor().execute(new Runnable() {
@Override
public void run() {
execute0(rrn, ss, c, flag);
}
});
}
/**
* 新数据通道的执行
*/
private void newExecute(final RouteResultsetNode rrn, final BlockingSession ss, final int flag) {
final ServerConnection sc = ss.getSource();
// 检查数据节点是否存在
CobarConfig conf = CobarServer.getInstance().getConfig();
final MySQLDataNode dn = conf.getDataNodes().get(rrn.getName());
if (dn == null) {
LOGGER.warn(new StringBuilder().append(sc).append(rrn).toString(), new UnknownDataNodeException());
handleError(ErrorCode.ER_BAD_DB_ERROR, "Unknown dataNode '" + rrn.getName() + "'", ss);
return;
}
// 提交执行任务
sc.getProcessor().getExecutor().execute(new Runnable() {
@Override
public void run() {
// 取得数据通道
int i = rrn.getReplicaIndex();
Channel c = null;
try {
c = (i == DEFAULT_REPLICA_INDEX) ? dn.getChannel() : dn.getChannel(i);
} catch (Exception e) {
LOGGER.warn(new StringBuilder().append(sc).append(rrn).toString(), e);
String msg = e.getMessage();
handleError(ErrorCode.ER_BAD_DB_ERROR, msg == null ? e.getClass().getSimpleName() : msg, ss);
return;
}
// 检查连接是否已关闭。
if (sc.isClosed()) {
c.release();
endRunning();
return;
}
// 绑定数据通道
c.setRunning(true);
Channel old = ss.getTarget().put(rrn, c);
if (old != null && old != c) {
old.close();
}
// 执行
execute0(rrn, ss, c, flag);
}
});
}
/**
* 数据通道执行
*/
private void execute0(RouteResultsetNode rrn, BlockingSession ss, Channel c, int flag) {
final ServerConnection sc = ss.getSource();
// 检查连接是否已关闭
if (sc.isClosed()) {
c.setRunning(false);
endRunning();
ss.clear();
return;
}
try {
// 执行并等待返回
MySQLChannel mc = (MySQLChannel) c;
BinaryPacket bin = mc.execute(rrn, sc, sc.isAutocommit());
// 接收和处理数据
switch (bin.data[0]) {
case OkPacket.FIELD_COUNT: {
mc.setRunning(false);
if (mc.isAutocommit()) {
ss.clear();
}
endRunning();
bin.packetId = ++packetId;// OK_PACKET
// set lastInsertId
setLastInsertId(bin, sc);
sc.write(bin.write(sc.allocate(), sc));
break;
}
case ErrorPacket.FIELD_COUNT: {
LOGGER.warn(mc.getErrLog(rrn.getStatement(), mc.getErrMessage(bin), sc));
mc.setRunning(false);
if (mc.isAutocommit()) {
ss.clear();
}
endRunning();
bin.packetId = ++packetId;// ERROR_PACKET
sc.write(bin.write(sc.allocate(), sc));
break;
}
default: // HEADER|FIELDS|FIELD_EOF|ROWS|LAST_EOF
handleResultSet(rrn, ss, mc, bin, flag);
}
} catch (IOException e) {
LOGGER.warn(new StringBuilder().append(sc).append(rrn).toString(), e);
c.close();
String msg = e.getMessage();
handleError(ErrorCode.ER_YES, msg == null ? e.getClass().getSimpleName() : msg, ss);
} catch (RuntimeException e) {
LOGGER.warn(new StringBuilder().append(sc).append(rrn).toString(), e);
c.close();
String msg = e.getMessage();
handleError(ErrorCode.ER_YES, msg == null ? e.getClass().getSimpleName() : msg, ss);
}
}
/**
* 处理结果集数据
*/
private void handleResultSet(RouteResultsetNode rrn, BlockingSession ss, MySQLChannel mc, BinaryPacket bin, int flag)
throws IOException {
final ServerConnection sc = ss.getSource();
bin.packetId = ++packetId;// HEADER
List<MySQLPacket> headerList = new LinkedList<MySQLPacket>();
headerList.add(bin);
for (;;) {
bin = mc.receive();
switch (bin.data[0]) {
case ErrorPacket.FIELD_COUNT: {
LOGGER.warn(mc.getErrLog(rrn.getStatement(), mc.getErrMessage(bin), sc));
mc.setRunning(false);
if (mc.isAutocommit()) {
ss.clear();
}
endRunning();
bin.packetId = ++packetId;// ERROR_PACKET
sc.write(bin.write(sc.allocate(), sc));
return;
}
case EOFPacket.FIELD_COUNT: {
bin.packetId = ++packetId;// FIELD_EOF
ByteBuffer bb = sc.allocate();
for (MySQLPacket packet : headerList) {
bb = packet.write(bb, sc);
}
bb = bin.write(bb, sc);
headerList = null;
handleRowData(rrn, ss, mc, bb, packetId);
return;
}
default:
bin.packetId = ++packetId;// FIELDS
switch (flag) {
case RouteResultset.REWRITE_FIELD:
StringBuilder fieldName = new StringBuilder();
fieldName.append("Tables_in_").append(ss.getSource().getSchema());
FieldPacket field = PacketUtil.getField(bin, fieldName.toString());
headerList.add(field);
break;
default:
headerList.add(bin);
}
}
}
}
/**
* 处理RowData数据
*/
private void handleRowData(RouteResultsetNode rrn, BlockingSession ss, MySQLChannel mc, ByteBuffer bb, byte id)
throws IOException {
final ServerConnection sc = ss.getSource();
this.packetId = id;
BinaryPacket bin = null;
int size = 0;
try {
for (;;) {
bin = mc.receive();
switch (bin.data[0]) {
case ErrorPacket.FIELD_COUNT:
LOGGER.warn(mc.getErrLog(rrn.getStatement(), mc.getErrMessage(bin), sc));
mc.setRunning(false);
if (mc.isAutocommit()) {
ss.clear();
}
endRunning();
bin.packetId = ++packetId;// ERROR_PACKET
bb = bin.write(bb, sc);
sc.write(bb);
return;
case EOFPacket.FIELD_COUNT:
mc.setRunning(false);
if (mc.isAutocommit()) {
ss.clear();
}
endRunning();
bin.packetId = ++packetId;// LAST_EOF
bb = bin.write(bb, sc);
sc.write(bb);
return;
default:
bin.packetId = ++packetId;// ROWS
bb = bin.write(bb, sc);
size += bin.packetLength;
if (size > RECEIVE_CHUNK_SIZE) {
handleNext(rrn, ss, mc, bb, packetId);
return;
}
}
}
} catch (IOException e) {
sc.recycle(bb);
throw e;
}
}
/**
* 下一个数据接收任务
*/
private void handleNext(final RouteResultsetNode rrn, final BlockingSession ss, final MySQLChannel mc,
final ByteBuffer bb, final byte id) {
final ServerConnection sc = ss.getSource();
sc.getProcessor().getExecutor().execute(new Runnable() {
@Override
public void run() {
try {
handleRowData(rrn, ss, mc, bb, id);
} catch (IOException e) {
LOGGER.warn(new StringBuilder().append(sc).append(rrn).toString(), e);
mc.close();
String msg = e.getMessage();
handleError(ErrorCode.ER_YES, msg == null ? e.getClass().getSimpleName() : msg, ss);
} catch (RuntimeException e) {
LOGGER.warn(new StringBuilder().append(sc).append(rrn).toString(), e);
mc.close();
String msg = e.getMessage();
handleError(ErrorCode.ER_YES, msg == null ? e.getClass().getSimpleName() : msg, ss);
}
}
});
}
/**
* 执行异常处理
*/
private void handleError(int errno, String message, BlockingSession ss) {
endRunning();
// 清理
ss.clear();
ServerConnection sc = ss.getSource();
sc.setTxInterrupt();
// 通知
ErrorPacket err = new ErrorPacket();
err.packetId = ++packetId;// ERROR_PACKET
err.errno = errno;
err.message = StringUtil.encode(message, sc.getCharset());
err.write(sc);
}
private void endRunning() {
ReentrantLock lock = this.lock;
lock.lock();
try {
isRunning = false;
taskFinished.signalAll();
} finally {
lock.unlock();
}
}
private void setLastInsertId(BinaryPacket bin, ServerConnection sc) {
OkPacket ok = new OkPacket();
ok.read(bin);
if (ok.insertId > 0) {
sc.setLastInsertId(ok.insertId);
}
}
}