/* * 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.nio.ByteBuffer; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; 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.mysql.MySQLDataNode; import com.alibaba.cobar.mysql.nio.MySQLConnection; import com.alibaba.cobar.mysql.nio.handler.CommitNodeHandler; import com.alibaba.cobar.mysql.nio.handler.KillConnectionHandler; import com.alibaba.cobar.mysql.nio.handler.MultiNodeQueryHandler; import com.alibaba.cobar.mysql.nio.handler.RollbackNodeHandler; import com.alibaba.cobar.mysql.nio.handler.RollbackReleaseHandler; import com.alibaba.cobar.mysql.nio.handler.SingleNodeHandler; import com.alibaba.cobar.mysql.nio.handler.Terminatable; import com.alibaba.cobar.net.FrontendConnection; 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 2012-4-12 * @author <a href="mailto:shuo.qius@alibaba-inc.com">QIU Shuo</a> */ public class NonBlockingSession implements Session { private static final Logger LOGGER = Logger.getLogger(NonBlockingSession.class); private final ServerConnection source; private final ConcurrentHashMap<RouteResultsetNode, MySQLConnection> target; private final AtomicBoolean terminating; // life-cycle: each sql execution private volatile SingleNodeHandler singleNodeHandler; private volatile MultiNodeQueryHandler multiNodeHandler; private volatile CommitNodeHandler commitHandler; private volatile RollbackNodeHandler rollbackHandler; public NonBlockingSession(ServerConnection source) { this.source = source; this.target = new ConcurrentHashMap<RouteResultsetNode, MySQLConnection>(2, 1); this.terminating = new AtomicBoolean(false); } @Override public ServerConnection getSource() { return source; } @Override public int getTargetCount() { return target.size(); } public Set<RouteResultsetNode> getTargetKeys() { return target.keySet(); } public MySQLConnection getTarget(RouteResultsetNode key) { return target.get(key); } public MySQLConnection removeTarget(RouteResultsetNode key) { return target.remove(key); } @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) { singleNodeHandler = new SingleNodeHandler(nodes[0], this); // singleNodeHandler.execute(); } else { boolean autocommit = source.isAutocommit(); if (autocommit && isModifySQL(type)) { autocommit = false; } multiNodeHandler = new MultiNodeQueryHandler(nodes, autocommit, this); // multiNodeHandler.execute(); } } 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; } commitHandler = new CommitNodeHandler(this); commitHandler.commit(); } public void rollback() { final int initCount = target.size(); if (initCount <= 0) { ByteBuffer buffer = source.allocate(); buffer = source.writeToBuffer(OkPacket.OK, buffer); source.write(buffer); return; } rollbackHandler = new RollbackNodeHandler(this); rollbackHandler.rollback(); } @Override public void cancel(FrontendConnection sponsor) { // TODO Auto-generated method stub } /** * {@link ServerConnection#isClosed()} must be true before invoking this */ public void terminate() { if (!terminating.compareAndSet(false, true)) { return; } kill(new Runnable() { @Override public void run() { new Terminator().nextInvocation(singleNodeHandler) .nextInvocation(multiNodeHandler) .nextInvocation(commitHandler) .nextInvocation(rollbackHandler) .nextInvocation(new Terminatable() { @Override public void terminate(Runnable runnable) { clearConnections(false); } }) .nextInvocation(new Terminatable() { @Override public void terminate(Runnable runnable) { terminating.set(false); } }) .invoke(); } }); } public boolean closeConnection(RouteResultsetNode key) { MySQLConnection conn = target.remove(key); if (conn != null) { conn.close(); return true; } return false; } public void setConnectionRunning(RouteResultsetNode[] route) { for (RouteResultsetNode rrn : route) { MySQLConnection c = target.get(rrn); if (c != null) { c.setRunning(true); } } } public void clearConnections() { clearConnections(true); } public void releaseConnections() { for (RouteResultsetNode rrn : target.keySet()) { MySQLConnection 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.isClosedOrQuit()) { if (source.isClosed()) { c.quit(); } else { c.release(); } } } } } /** * @return previous bound connection */ public MySQLConnection bindConnection(RouteResultsetNode key, MySQLConnection conn) { return target.put(key, conn); } private static class Terminator { private LinkedList<Terminatable> list = new LinkedList<Terminatable>(); private Iterator<Terminatable> iter; public Terminator nextInvocation(Terminatable term) { list.add(term); return this; } public void invoke() { iter = list.iterator(); terminate(); } private void terminate() { if (iter.hasNext()) { Terminatable term = iter.next(); if (term != null) { term.terminate(new Runnable() { @Override public void run() { terminate(); } }); } else { terminate(); } } } } private void kill(Runnable run) { boolean hooked = false; AtomicInteger count = null; Map<RouteResultsetNode, MySQLConnection> killees = null; for (RouteResultsetNode node : target.keySet()) { MySQLConnection c = target.get(node); if (c != null && c.isRunning()) { if (!hooked) { hooked = true; killees = new HashMap<RouteResultsetNode, MySQLConnection>(); count = new AtomicInteger(0); } killees.put(node, c); count.incrementAndGet(); } } if (hooked) { for (Entry<RouteResultsetNode, MySQLConnection> en : killees.entrySet()) { KillConnectionHandler kill = new KillConnectionHandler(en.getValue(), this, run, count); CobarConfig conf = CobarServer.getInstance().getConfig(); MySQLDataNode dn = conf.getDataNodes().get(en.getKey().getName()); try { dn.getConnection(kill, en.getKey()); } catch (Exception e) { LOGGER.error("get killer connection failed for " + en.getKey(), e); kill.connectionError(e, null); } } } else { run.run(); } } private void clearConnections(boolean pessimisticRelease) { for (RouteResultsetNode node : target.keySet()) { MySQLConnection c = target.remove(node); if (c == null || c.isClosedOrQuit()) { continue; } // 如果通道正在运行中,则关闭当前通道。 if (c.isRunning() || (pessimisticRelease && source.isClosed())) { c.close(); continue; } // 非事务中的通道,直接释放资源。 if (c.isAutocommit()) { c.release(); continue; } c.setResponseHandler(new RollbackReleaseHandler()); c.rollback(); } } public boolean closed() { return source.isClosed(); } 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; } } }