/* * Copyright (c) 2013, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software;Designed and Developed mainly by many Chinese * opensource volunteers. you can redistribute it and/or modify it under the * terms of the GNU General Public License version 2 only, as published by the * Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Any questions about this component can be directed to it's project Web address * https://code.google.com/p/opencloudb/. * */ package org.opencloudb.server; 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 org.opencloudb.MycatConfig; import org.opencloudb.MycatServer; import org.opencloudb.backend.ConnectionMeta; import org.opencloudb.backend.BackendConnection; import org.opencloudb.backend.PhysicalDBNode; import org.opencloudb.config.ErrorCode; import org.opencloudb.mpp.DataMergeService; import org.opencloudb.mysql.nio.handler.CommitNodeHandler; import org.opencloudb.mysql.nio.handler.KillConnectionHandler; import org.opencloudb.mysql.nio.handler.MultiNodeQueryHandler; import org.opencloudb.mysql.nio.handler.RollbackNodeHandler; import org.opencloudb.mysql.nio.handler.RollbackReleaseHandler; import org.opencloudb.mysql.nio.handler.SingleNodeHandler; import org.opencloudb.mysql.nio.handler.Terminatable; import org.opencloudb.net.FrontendConnection; import org.opencloudb.net.mysql.OkPacket; import org.opencloudb.route.RouteResultset; import org.opencloudb.route.RouteResultsetNode; import org.opencloudb.server.parser.ServerParse; /** * @author mycat * @author mycat */ public class NonBlockingSession implements Session { private static final Logger LOGGER = Logger .getLogger(NonBlockingSession.class); private final ServerConnection source; private final ConcurrentHashMap<RouteResultsetNode, BackendConnection> 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, BackendConnection>( 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 BackendConnection getTarget(RouteResultsetNode key) { return target.get(key); } public BackendConnection removeTarget(RouteResultsetNode key) { return target.remove(key); } @Override public void execute(RouteResultset rrs, int type) { // clear prev execute resources clearHandlesResources(); if (LOGGER.isDebugEnabled()) { StringBuilder s = new StringBuilder(); LOGGER.debug(s.append(source).append(rrs).toString() + " rrs "); } // 检查路由结果是否为空 RouteResultsetNode[] nodes = rrs.getNodes(); if (nodes == null || nodes.length == 0 || nodes[0].getName() == null || nodes[0].getName().equals("")) { source.writeErrMessage(ErrorCode.ER_NO_DB_ERROR, "No dataNode found ,please check tables defined in schema:" + source.getSchema()); return; } if (nodes.length == 1) { singleNodeHandler = new SingleNodeHandler(nodes[0], this); try { singleNodeHandler.execute(); } catch (Exception e) { LOGGER.warn(new StringBuilder().append(source).append(rrs), e); source.writeErrMessage(ErrorCode.ERR_HANDLE_DATA, e.toString()); } } else { boolean autocommit = source.isAutocommit(); DataMergeService dataMergeSvr = null; if (ServerParse.SELECT == type && rrs.needMerge()) { dataMergeSvr = new DataMergeService(rrs); } multiNodeHandler = new MultiNodeQueryHandler(rrs, autocommit, this, dataMergeSvr); try { multiNodeHandler.execute(); } catch (Exception e) { LOGGER.warn(new StringBuilder().append(source).append(rrs), e); source.writeErrMessage(ErrorCode.ERR_HANDLE_DATA, e.toString()); } } } 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) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("no session bound connections found ,no need send rollback cmd "); } 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) { } /** * {@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 void releaseConnectionIfSafe(BackendConnection conn, boolean debug) { RouteResultsetNode node = (RouteResultsetNode) conn.getAttachment(); if (node != null) { if (this.source.isAutocommit() || conn.isFromSlaveDB() || !conn.isModifiedSQLExecuted()) { releaseConnection((RouteResultsetNode) conn.getAttachment(), LOGGER.isDebugEnabled()); } } } public void releaseConnection(RouteResultsetNode rrn, boolean debug) { BackendConnection c = target.remove(rrn); if (c != null) { if (debug) { LOGGER.debug("release connection " + c); } if (c.getAttachment() != null) { c.setAttachment(null); } if (c.isRunning()) { LOGGER.warn("close running connection is found " + c); c.close("abnomal"); } else if (!c.isClosedOrQuit()) { if (c.isAutocommit()) { c.release(); } else { c.setResponseHandler(new RollbackReleaseHandler()); c.rollback(); } } } } public void releaseConnections() { boolean debug = LOGGER.isDebugEnabled(); for (RouteResultsetNode rrn : target.keySet()) { releaseConnection(rrn, debug); } } /** * @return previous bound connection */ public BackendConnection bindConnection(RouteResultsetNode key, BackendConnection conn) { // System.out.println("bind connection "+conn+ // " to key "+key.getName()+" on sesion "+this); 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(); } } } } public boolean tryExistsCon(final BackendConnection conn, RouteResultsetNode node, Runnable runable) { if (conn == null) { return false; } if (!conn.isFromSlaveDB() || node.canRunnINReadDB(getSource().isAutocommit())) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("found connections in session to use " + conn + " for " + node); } conn.setAttachment(node); getSource().getProcessor().getExecutor().execute(runable); return true; } else { // slavedb connection and can't use anymore ,release it if (LOGGER.isDebugEnabled()) { LOGGER.debug("release slave connection,can't be used in trasaction " + conn + " for " + node); } releaseConnection(node, LOGGER.isDebugEnabled()); } return false; } private void kill(Runnable run) { boolean hooked = false; AtomicInteger count = null; Map<RouteResultsetNode, BackendConnection> killees = null; for (RouteResultsetNode node : target.keySet()) { BackendConnection c = target.get(node); if (c != null && c.isRunning()) { if (!hooked) { hooked = true; killees = new HashMap<RouteResultsetNode, BackendConnection>(); count = new AtomicInteger(0); } killees.put(node, c); count.incrementAndGet(); } } if (hooked) { ConnectionMeta conMeta = new ConnectionMeta(null, null, -1, true); for (Entry<RouteResultsetNode, BackendConnection> en : killees .entrySet()) { KillConnectionHandler kill = new KillConnectionHandler( en.getValue(), this, run, count); MycatConfig conf = MycatServer.getInstance().getConfig(); PhysicalDBNode dn = conf.getDataNodes().get( en.getKey().getName()); try { dn.getConnectionFromSameSource(conMeta, en.getValue(), 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()) { BackendConnection c = target.remove(node); if (c == null || c.isClosedOrQuit()) { continue; } // 如果通道正在运行中,则关闭当前通道。 if (c.isRunning() || (pessimisticRelease && source.isClosed())) { c.close("source closed"); continue; } // 非事务中的通道,直接释放资源。 if (c.isAutocommit()) { c.release(); continue; } c.setResponseHandler(new RollbackReleaseHandler()); c.rollback(); } } private void clearHandlesResources() { if (this.singleNodeHandler != null) { singleNodeHandler.clearResources(); singleNodeHandler = null; } if (this.multiNodeHandler != null) { multiNodeHandler.clearResources(); multiNodeHandler = null; } } public void clearResources() { if (LOGGER.isDebugEnabled()) { LOGGER.debug("clear session resources " + this); } this.releaseConnections(); clearHandlesResources(); } public boolean closed() { return source.isClosed(); } }