/*
* 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.ConcurrentMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;
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
* @author <a href="mailto:shuo.qius@alibaba-inc.com">QIU Shuo</a>
*/
public final class MultiNodeExecutor extends NodeExecutor {
private static final Logger LOGGER = Logger.getLogger(MultiNodeExecutor.class);
private static final int RECEIVE_CHUNK_SIZE = 16 * 1024;
private AtomicBoolean isFail = new AtomicBoolean(false);
private int unfinishedNodeCount;
private int errno;
private String errMessage;
private boolean fieldEOF;
private byte packetId;
private long affectedRows;
private long insertId;
private ByteBuffer buffer;
private final ReentrantLock lock = new ReentrantLock();
private final Condition taskFinished = lock.newCondition();
private final DefaultCommitExecutor icExecutor = new DefaultCommitExecutor() {
@Override
protected String getErrorMessage() {
return "Internal commit";
}
@Override
protected Logger getLogger() {
return MultiNodeExecutor.LOGGER;
}
};
@Override
public void terminate() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
while (unfinishedNodeCount > 0) {
taskFinished.await();
}
} finally {
lock.unlock();
}
icExecutor.terminate();
}
private void decrementCountToZero() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
unfinishedNodeCount = 0;
taskFinished.signalAll();
} finally {
lock.unlock();
}
}
private boolean decrementCountAndIsZero() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
int ufc = --unfinishedNodeCount;
taskFinished.signalAll();
return ufc <= 0;
} finally {
lock.unlock();
}
}
/**
* 多数据节点执行
*
* @param nodes never null
*/
public void execute(RouteResultsetNode[] nodes, final boolean autocommit, final BlockingSession ss, final int flag) {
// 初始化
final ReentrantLock lock = this.lock;
lock.lock();
try {
this.isFail.set(false);
this.unfinishedNodeCount = nodes.length;
this.errno = 0;
this.errMessage = null;
this.fieldEOF = false;
this.packetId = 0;
this.affectedRows = 0L;
this.insertId = 0L;
this.buffer = ss.getSource().allocate();
} finally {
lock.unlock();
}
if (ss.getSource().isClosed()) {
decrementCountToZero();
ss.getSource().recycle(this.buffer);
return;
}
// 多节点处理
ConcurrentMap<RouteResultsetNode, Channel> target = ss.getTarget();
for (RouteResultsetNode rrn : nodes) {
Channel c = target.get(rrn);
if (c != null) {
c.setRunning(true);
}
}
ThreadPoolExecutor exec = ss.getSource().getProcessor().getExecutor();
for (final RouteResultsetNode rrn : nodes) {
final Channel c = target.get(rrn);
if (c != null) {
exec.execute(new Runnable() {
@Override
public void run() {
execute0(rrn, c, autocommit, ss, flag);
}
});
} else {
newExecute(rrn, autocommit, ss, flag);
}
}
}
/**
* 新通道的执行
*/
private void newExecute(final RouteResultsetNode rrn, final boolean autocommit, 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) {
handleFailure(ss, rrn, new SimpleErrInfo(new UnknownDataNodeException("Unknown dataNode '" + rrn.getName()
+ "'"), ErrorCode.ER_BAD_DB_ERROR, sc, rrn));
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 (final Exception e) {
handleFailure(ss, rrn, new SimpleErrInfo(e, ErrorCode.ER_BAD_DB_ERROR, sc, rrn));
return;
}
c.setRunning(true);
Channel old = ss.getTarget().put(rrn, c);
if (old != null && c != old) {
old.close();
}
// 执行
execute0(rrn, c, autocommit, ss, flag);
}
});
}
/**
* 执行
*/
private void execute0(RouteResultsetNode rrn, Channel c, boolean autocommit, BlockingSession ss, int flag) {
ServerConnection sc = ss.getSource();
if (isFail.get() || sc.isClosed()) {
c.setRunning(false);
handleFailure(ss, rrn, null);
return;
}
try {
// 执行并等待返回
BinaryPacket bin = ((MySQLChannel) c).execute(rrn, sc, autocommit);
// 接收和处理数据
final ReentrantLock lock = MultiNodeExecutor.this.lock;
lock.lock();
try {
switch (bin.data[0]) {
case ErrorPacket.FIELD_COUNT:
c.setRunning(false);
handleFailure(ss, rrn, new BinaryErrInfo((MySQLChannel) c, bin, sc, rrn));
break;
case OkPacket.FIELD_COUNT:
OkPacket ok = new OkPacket();
ok.read(bin);
affectedRows += ok.affectedRows;
// set lastInsertId
if (ok.insertId > 0) {
insertId = (insertId == 0) ? ok.insertId : Math.min(insertId, ok.insertId);
}
c.setRunning(false);
handleSuccessOK(ss, rrn, autocommit, ok);
break;
default: // HEADER|FIELDS|FIELD_EOF|ROWS|LAST_EOF
final MySQLChannel mc = (MySQLChannel) c;
if (fieldEOF) {
for (;;) {
bin = mc.receive();
switch (bin.data[0]) {
case ErrorPacket.FIELD_COUNT:
c.setRunning(false);
handleFailure(ss, rrn, new BinaryErrInfo(mc, bin, sc, rrn));
return;
case EOFPacket.FIELD_COUNT:
handleRowData(rrn, c, ss);
return;
default:
continue;
}
}
} else {
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:
c.setRunning(false);
handleFailure(ss, rrn, new BinaryErrInfo(mc, bin, sc, rrn));
return;
case EOFPacket.FIELD_COUNT:
bin.packetId = ++packetId;// FIELD_EOF
for (MySQLPacket packet : headerList) {
buffer = packet.write(buffer, sc);
}
headerList = null;
buffer = bin.write(buffer, sc);
fieldEOF = true;
handleRowData(rrn, c, ss);
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);
}
}
}
}
}
} finally {
lock.unlock();
}
} catch (final IOException e) {
c.close();
handleFailure(ss, rrn, new SimpleErrInfo(e, ErrorCode.ER_YES, sc, rrn));
} catch (final RuntimeException e) {
c.close();
handleFailure(ss, rrn, new SimpleErrInfo(e, ErrorCode.ER_YES, sc, rrn));
}
}
/**
* 处理RowData数据
*/
private void handleRowData(final RouteResultsetNode rrn, Channel c, BlockingSession ss) throws IOException {
final ServerConnection source = ss.getSource();
BinaryPacket bin = null;
int size = 0;
for (;;) {
bin = ((MySQLChannel) c).receive();
switch (bin.data[0]) {
case ErrorPacket.FIELD_COUNT:
c.setRunning(false);
handleFailure(ss, rrn, new BinaryErrInfo(((MySQLChannel) c), bin, source, rrn));
return;
case EOFPacket.FIELD_COUNT:
c.setRunning(false);
if (source.isAutocommit()) {
c = ss.getTarget().remove(rrn);
if (c != null) {
if (isFail.get() || source.isClosed()) {
/**
* this {@link Channel} might be closed by other
* thread in this condition, so that do not release
* this channel
*/
c.close();
} else {
c.release();
}
}
}
handleSuccessEOF(ss, bin);
return;
default:
bin.packetId = ++packetId;// ROWS
buffer = bin.write(buffer, source);
size += bin.packetLength;
if (size > RECEIVE_CHUNK_SIZE) {
handleNext(rrn, c, ss);
return;
}
}
}
}
/**
* 处理下一个任务
*/
private void handleNext(final RouteResultsetNode rrn, final Channel c, final BlockingSession ss) {
final ServerConnection sc = ss.getSource();
sc.getProcessor().getExecutor().execute(new Runnable() {
@Override
public void run() {
final ReentrantLock lock = MultiNodeExecutor.this.lock;
lock.lock();
try {
handleRowData(rrn, c, ss);
} catch (final IOException e) {
c.close();
handleFailure(ss, rrn, new SimpleErrInfo(e, ErrorCode.ER_YES, sc, rrn));
} catch (final RuntimeException e) {
c.close();
handleFailure(ss, rrn, new SimpleErrInfo(e, ErrorCode.ER_YES, sc, rrn));
} finally {
lock.unlock();
}
}
});
}
/**
* @throws nothing never throws any exception
*/
private void handleSuccessEOF(BlockingSession ss, BinaryPacket bin) {
if (decrementCountAndIsZero()) {
if (isFail.get()) {
notifyFailure(ss);
return;
}
try {
ServerConnection source = ss.getSource();
if (source.isAutocommit()) {
ss.release();
}
bin.packetId = ++packetId;// LAST_EOF
source.write(bin.write(buffer, source));
} catch (Exception e) {
LOGGER.warn("exception happens in success notification: " + ss.getSource(), e);
}
}
}
/**
* @throws nothing never throws any exception
*/
private void handleSuccessOK(BlockingSession ss, RouteResultsetNode rrn, boolean autocommit, OkPacket ok) {
if (decrementCountAndIsZero()) {
if (isFail.get()) {
notifyFailure(ss);
return;
}
try {
ServerConnection source = ss.getSource();
ok.packetId = ++packetId;// OK_PACKET
ok.affectedRows = affectedRows;
if (insertId > 0) {
ok.insertId = insertId;
source.setLastInsertId(insertId);
}
if (source.isAutocommit()) {
if (!autocommit) { // 前端非事务模式,后端事务模式,则需要自动递交后端事务。
icExecutor.commit(ok, ss, ss.getTarget().size());
} else {
ss.release();
ok.write(source);
}
} else {
ok.write(source);
}
source.recycle(buffer);
} catch (Exception e) {
LOGGER.warn("exception happens in success notification: " + ss.getSource(), e);
}
}
}
private void handleFailure(BlockingSession ss, RouteResultsetNode rrn, ErrInfo errInfo) {
try {
// 标记为执行失败,并记录第一次异常信息。
if (!isFail.getAndSet(true) && errInfo != null) {
errno = errInfo.getErrNo();
errMessage = errInfo.getErrMsg();
errInfo.logErr();
}
} catch (Exception e) {
LOGGER.warn("handleFailure failed in " + getClass().getSimpleName() + ", source = " + ss.getSource(), e);
}
if (decrementCountAndIsZero()) {
notifyFailure(ss);
}
}
/**
* 通知,执行异常
*
* @throws nothing never throws any exception
*/
private void notifyFailure(BlockingSession ss) {
try {
// 清理
ss.clear();
ServerConnection sc = ss.getSource();
sc.setTxInterrupt();
// 通知
ErrorPacket err = new ErrorPacket();
err.packetId = ++packetId;// ERROR_PACKET
err.errno = errno;
err.message = StringUtil.encode(errMessage, sc.getCharset());
sc.write(err.write(buffer, sc));
} catch (Exception e) {
LOGGER.warn("exception happens in failure notification: " + ss.getSource(), e);
}
}
protected static interface ErrInfo {
int getErrNo();
String getErrMsg();
void logErr();
}
protected static class BinaryErrInfo implements ErrInfo {
private String errMsg;
private int errNo;
private ServerConnection source;
private RouteResultsetNode rrn;
private MySQLChannel mc;
public BinaryErrInfo(MySQLChannel mc, BinaryPacket bin, ServerConnection sc, RouteResultsetNode rrn) {
this.mc = mc;
this.source = sc;
this.rrn = rrn;
ErrorPacket err = new ErrorPacket();
err.read(bin);
this.errMsg = (err.message == null) ? null : StringUtil.decode(err.message, mc.getCharset());
this.errNo = err.errno;
}
@Override
public int getErrNo() {
return errNo;
}
@Override
public String getErrMsg() {
return errMsg;
}
@Override
public void logErr() {
try {
LOGGER.warn(mc.getErrLog(rrn.getStatement(), errMsg, source));
} catch (Exception e) {
}
}
}
protected static class SimpleErrInfo implements ErrInfo {
private Exception e;
private int errNo;
private ServerConnection source;
private RouteResultsetNode rrn;
public SimpleErrInfo(Exception e, int errNo, ServerConnection sc, RouteResultsetNode rrn) {
this.e = e;
this.errNo = errNo;
this.source = sc;
this.rrn = rrn;
}
@Override
public int getErrNo() {
return errNo;
}
@Override
public String getErrMsg() {
String msg = e.getMessage();
return msg == null ? e.getClass().getSimpleName() : msg;
}
@Override
public void logErr() {
try {
LOGGER.warn(new StringBuilder().append(source).append(rrn).toString(), e);
} catch (Exception e) {
}
}
}
}