/* * 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.server.session; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.ConcurrentHashMap; import org.apache.log4j.Logger; import com.alibaba.cobar.config.ErrorCode; import com.alibaba.cobar.exception.UnknownPacketException; import com.alibaba.cobar.mysql.bio.Channel; import com.alibaba.cobar.mysql.bio.MySQLChannel; import com.alibaba.cobar.mysql.bio.executor.DefaultCommitExecutor; import com.alibaba.cobar.mysql.bio.executor.MultiNodeExecutor; import com.alibaba.cobar.mysql.bio.executor.NodeExecutor; import com.alibaba.cobar.mysql.bio.executor.RollbackExecutor; import com.alibaba.cobar.mysql.bio.executor.SingleNodeExecutor; import com.alibaba.cobar.net.FrontendConnection; import com.alibaba.cobar.net.mysql.BinaryPacket; import com.alibaba.cobar.net.mysql.ErrorPacket; 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.parser.ServerParse; /** * 由前后端参与的一次执行会话过程 * * @author xianmao.hexm */ public class BlockingSession implements Session { private static final Logger LOGGER = Logger.getLogger(BlockingSession.class); private final ServerConnection source; private final ConcurrentHashMap<RouteResultsetNode, Channel> target; private final SingleNodeExecutor singleNodeExecutor; private final MultiNodeExecutor multiNodeExecutor; private final DefaultCommitExecutor commitExecutor; private final RollbackExecutor rollbackExecutor; public BlockingSession(ServerConnection source) { this.source = source; this.target = new ConcurrentHashMap<RouteResultsetNode, Channel>(); this.singleNodeExecutor = new SingleNodeExecutor(); this.multiNodeExecutor = new MultiNodeExecutor(); this.commitExecutor = new DefaultCommitExecutor(); this.rollbackExecutor = new RollbackExecutor(); } @Override public ServerConnection getSource() { return source; } @Override public int getTargetCount() { return target.size(); } public ConcurrentHashMap<RouteResultsetNode, Channel> getTarget() { return target; } @Override public void execute(RouteResultset rrs, int type) { if (LOGGER.isDebugEnabled()) { StringBuilder s = new StringBuilder(); LOGGER.debug(s.append(source).append(rrs).toString()); } // 检查路由结果是否为空 RouteResultsetNode[] nodes = rrs.getNodes(); if (nodes == null || nodes.length == 0) { source.writeErrMessage(ErrorCode.ER_NO_DB_ERROR, "No dataNode selected"); return; } // 选择执行方式 if (nodes.length == 1) { singleNodeExecutor.execute(nodes[0], this, rrs.getFlag()); } else { // 多数据节点,非事务模式下,执行的是可修改数据的SQL,则后端为事务模式。 boolean autocommit = source.isAutocommit(); if (autocommit && isModifySQL(type)) { autocommit = false; } multiNodeExecutor.execute(nodes, autocommit, this, rrs.getFlag()); } } @Override public void commit() { final int initCount = target.size(); if (initCount <= 0) { ByteBuffer buffer = source.allocate(); buffer = source.writeToBuffer(OkPacket.OK, buffer); source.write(buffer); return; } commitExecutor.commit(null, this, initCount); } @Override public void rollback() { rollbackExecutor.rollback(this); } @Override public void cancel(FrontendConnection sponsor) { // TODO terminate session source.writeErrMessage(ErrorCode.ER_QUERY_INTERRUPTED, "Query execution was interrupted"); if (sponsor != null) { OkPacket packet = new OkPacket(); packet.packetId = 1; packet.affectedRows = 0; packet.serverStatus = 2; packet.write(sponsor); } } @Override public void terminate() { // 终止所有正在执行的任务 kill(); // 等待所有任务结束,包括还未执行的,执行中的,执行完的。 try { singleNodeExecutor.terminate(); multiNodeExecutor.terminate(); commitExecutor.terminate(); rollbackExecutor.terminate(); } catch (InterruptedException e) { for (RouteResultsetNode rrn : target.keySet()) { Channel c = target.remove(rrn); if (c != null) { c.close(); } } LOGGER.warn("termination interrupted: " + source, e); } // 清理绑定的资源 clear(false); } /** * 释放session关联的资源 */ public void release() { for (RouteResultsetNode rrn : target.keySet()) { Channel c = target.remove(rrn); if (c != null) { if (c.isRunning()) { c.close(); try { throw new IllegalStateException("running connection is found: " + c); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } } else if (!c.isClosed()) { if (source.isClosed()) { c.close(); } else { c.release(); } } } } } public void clear() { clear(true); } /** * MUST be called at the end of {@link NodeExecutor} * * @param pessimisticRelease true if this method might be invoked * concurrently with {@link #kill()} */ private void clear(boolean pessimisticRelease) { for (RouteResultsetNode rrn : target.keySet()) { Channel c = target.remove(rrn); // 通道不存在或者已被关闭 if (c == null || c.isClosed()) { continue; } // 如果通道正在运行中,则关闭当前通道。 if (c.isRunning() || (pessimisticRelease && source.isClosed())) { c.close(); continue; } // 非事务中的通道,直接释放资源。 if (c.isAutocommit()) { c.release(); continue; } // 事务中的通道,需要先回滚后再释放资源。 MySQLChannel mc = (MySQLChannel) c; try { BinaryPacket bin = mc.rollback(); switch (bin.data[0]) { case OkPacket.FIELD_COUNT: mc.release(); break; case ErrorPacket.FIELD_COUNT: mc.close(); break; default: throw new UnknownPacketException(bin.toString()); } } catch (IOException e) { StringBuilder s = new StringBuilder(); LOGGER.warn(s.append(mc).append("rollback").toString(), e); mc.close(); } catch (RuntimeException e) { StringBuilder s = new StringBuilder(); LOGGER.warn(s.append(mc).append("rollback").toString(), e); mc.close(); } } } /** * 终止执行中的通道 */ private void kill() { for (RouteResultsetNode rrn : target.keySet()) { Channel c = target.get(rrn); if (c != null && c.isRunning()) { c.kill(); } } } /** * 检查是否会引起数据变更的语句 */ private static boolean isModifySQL(int type) { switch (type) { case ServerParse.INSERT: case ServerParse.DELETE: case ServerParse.UPDATE: case ServerParse.REPLACE: return true; default: return false; } } }