/*
* 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 io.mycat.server;
import io.mycat.MycatServer;
import io.mycat.backend.BackendConnection;
import io.mycat.backend.PhysicalDBNode;
import io.mycat.route.RouteResultset;
import io.mycat.route.RouteResultsetNode;
import io.mycat.server.config.node.MycatConfig;
import io.mycat.server.config.node.SystemConfig;
import io.mycat.server.executors.*;
import io.mycat.server.packet.OkPacket;
import io.mycat.server.sqlcmd.SQLCmdConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author mycat
* @author mycat
*/
public class NonBlockingSession{
public static final Logger LOGGER = LoggerFactory
.getLogger(NonBlockingSession.class);
private final MySQLFrontConnection source;
private final ConcurrentHashMap<RouteResultsetNode, BackendConnection> target;
// life-cycle: each sql execution
private volatile SingleNodeHandler singleNodeHandler;
private volatile MultiNodeQueryHandler multiNodeHandler;
private volatile RollbackNodeHandler rollbackHandler;
private final MultiNodeCoordinator multiNodeCoordinator;
private final CommitNodeHandler commitHandler;
private volatile String xaTXID;
private boolean prepared;
public NonBlockingSession(MySQLFrontConnection source) {
this.source = source;
this.target = new ConcurrentHashMap<RouteResultsetNode, BackendConnection>(
2, 0.75f);
multiNodeCoordinator = new MultiNodeCoordinator(this);
commitHandler = new CommitNodeHandler(this);
}
public MySQLFrontConnection getSource() {
return source;
}
public int getTargetCount() {
return target.size();
}
public Set<RouteResultsetNode> getTargetKeys() {
return target.keySet();
}
public BackendConnection getTarget(RouteResultsetNode key) {
return target.get(key);
}
public Map<RouteResultsetNode, BackendConnection> getTargetMap() {
return this.target;
}
public BackendConnection removeTarget(RouteResultsetNode key) {
return target.remove(key);
}
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(rrs, this);
if(this.isPrepared()) {
singleNodeHandler.setPrepared(true);
}
try {
singleNodeHandler.execute();
} catch (Exception e) {
LOGGER.warn("{} {}", source, rrs, e);
source.writeErrMessage(ErrorCode.ERR_HANDLE_DATA, e.toString());
}
} else {
boolean autocommit = source.isAutocommit();
// SystemConfig sysConfig = MycatServer.getInstance().getConfig()
// .getSystem();
multiNodeHandler = new MultiNodeQueryHandler(type, rrs, autocommit,
this);
if(this.isPrepared()) {
multiNodeHandler.setPrepared(true);
}
try {
multiNodeHandler.execute();
} catch (Exception e) {
LOGGER.warn("{} {}", source, rrs, e);
source.writeErrMessage(ErrorCode.ERR_HANDLE_DATA, e.toString());
}
}
if(this.isPrepared()) {
this.setPrepared(false);
}
}
public void commit() {
final int initCount = target.size();
if (initCount <= 0) {
source.write(OkPacket.OK);
return;
} else if (initCount == 1) {
BackendConnection con = target.elements().nextElement();
commitHandler.commit(con);
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("multi node commit to send ,total " + initCount);
}
multiNodeCoordinator.executeBatchNodeCmd(SQLCmdConstant.COMMIT_CMD);
}
}
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 ");
}
source.write(OkPacket.OK);
return;
}
rollbackHandler = new RollbackNodeHandler(this);
rollbackHandler.rollback();
}
public void cancel(MySQLFrontConnection sponsor) {
}
/**
* {@link ServerConnection#isClosed()} must be true before invoking this
*/
public void terminate() {
for (BackendConnection node : target.values()) {
node.close("client closed ");
}
target.clear();
clearHandlesResources();
}
public void closeAndClearResources(String reason) {
for (BackendConnection node : target.values()) {
node.close(reason);
}
target.clear();
clearHandlesResources();
}
public void releaseConnectionIfSafe(BackendConnection conn, boolean debug,
boolean needRollback) {
RouteResultsetNode node = (RouteResultsetNode) conn.getAttachment();
if (node != null) {
if (this.source.isAutocommit() || conn.isFromSlaveDB()
|| !conn.isModifiedSQLExecuted()) {
releaseConnection((RouteResultsetNode) conn.getAttachment(),
LOGGER.isDebugEnabled(), needRollback);
}
}
}
public void releaseConnection(RouteResultsetNode rrn, boolean debug,
final boolean needRollback) {
BackendConnection c = target.remove(rrn);
if (c != null) {
if (debug) {
LOGGER.debug("release connection " + c);
}
if (c.getAttachment() != null) {
c.setAttachment(null);
}
if (!c.isClosedOrQuit()) {
if (c.isAutocommit()) {
c.release();
} else
// if (needRollback)
{
c.setResponseHandler(new RollbackReleaseHandler());
c.rollback();
}
// else {
// c.release();
// }
}
}
}
public void releaseConnections(final boolean needRollback) {
boolean debug = LOGGER.isDebugEnabled();
for (RouteResultsetNode rrn : target.keySet()) {
releaseConnection(rrn, debug, needRollback);
}
}
public void releaseConnection(BackendConnection con) {
Iterator<Entry<RouteResultsetNode, BackendConnection>> itor = target
.entrySet().iterator();
while (itor.hasNext()) {
BackendConnection theCon = itor.next().getValue();
if (theCon == con) {
itor.remove();
con.release();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("realse connection " + con);
}
break;
}
}
}
/**
* @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);
}
/**
* 该连接是否符合 RouteResultsetNode node 的要求而能够被重用
* @param conn
* @param node
* @return
*/
public boolean tryExistsCon(final BackendConnection conn, RouteResultsetNode node) {
if (conn == null) {
return false;
}
boolean canReUse = false;
// conn 是 slave db 的
if(conn.isFromSlaveDB() && ( node.canRunnINReadDB(getSource().isAutocommit())
&& (node.getRunOnSlave() == null || node.getRunOnSlave()) ) )
canReUse = true;
// conn 是 master db 的
if(!conn.isFromSlaveDB() && (node.getRunOnSlave() == null || !node.getRunOnSlave()) )
canReUse = true;
if (canReUse) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("found connections in session to use " + conn
+ " for " + node);
}
conn.setAttachment(node);
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(), false);
}
return false;
}
protected void kill() {
boolean hooked = false;
AtomicInteger count = null;
Map<RouteResultsetNode, BackendConnection> killees = null;
for (RouteResultsetNode node : target.keySet()) {
BackendConnection c = target.get(node);
if (c != null) {
if (!hooked) {
hooked = true;
killees = new HashMap<RouteResultsetNode, BackendConnection>();
count = new AtomicInteger(0);
}
killees.put(node, c);
count.incrementAndGet();
}
}
if (hooked) {
for (Entry<RouteResultsetNode, BackendConnection> en : killees
.entrySet()) {
KillConnectionHandler kill = new KillConnectionHandler(
en.getValue(), this);
MycatConfig conf = MycatServer.getInstance().getConfig();
PhysicalDBNode dn = conf.getDataNodes().get(
en.getKey().getName());
try {
dn.getConnectionFromSameSource(null, true, en.getValue(),
kill, en.getKey());
} catch (Exception e) {
LOGGER.error(
"get killer connection failed for " + en.getKey(),
e);
kill.connectionError(e, null);
}
}
}
}
private void clearHandlesResources() {
SingleNodeHandler singleHander = singleNodeHandler;
if (singleHander != null) {
singleHander.clearResources();
singleNodeHandler = null;
}
MultiNodeQueryHandler multiHandler = multiNodeHandler;
if (multiHandler != null) {
multiHandler.clearResources();
multiNodeHandler = null;
}
}
public void clearResources(final boolean needRollback) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("clear session resources " + this);
}
this.releaseConnections(needRollback);
clearHandlesResources();
}
public boolean closed() {
return source.isClosed();
}
private String genXATXID() {
return MycatServer.getInstance().genXATXID();
}
public void setXATXEnabled(boolean xaTXEnabled) {
LOGGER.info("XA Transaction enabled ,con " + this.getSource());
if (xaTXEnabled && this.xaTXID == null) {
xaTXID = genXATXID();
}
}
public String getXaTXID() {
return xaTXID;
}
public boolean isPrepared() {
return prepared;
}
public void setPrepared(boolean prepared) {
this.prepared = prepared;
}
}