/*
* Copyright 2012-2015 org.opencloudb.
*
* 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 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.PhysicalConnection;
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, PhysicalConnection> 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;
private boolean openWRFluxContrl = false;
public NonBlockingSession(ServerConnection source, int openWRFluxContrl) {
this.source = source;
this.target = new ConcurrentHashMap<RouteResultsetNode, PhysicalConnection>(
2, 1);
this.terminating = new AtomicBoolean(false);
this.openWRFluxContrl = (openWRFluxContrl == 1);
}
/**
* temporary supress channel read event ,because front connection is blocked
*/
public void supressTargetChannelReadEvent() {
if (!openWRFluxContrl) {
return;
}
final boolean isDebug = LOGGER.isDebugEnabled();
for (PhysicalConnection con : target.values()) {
if (!con.isSuppressReadTemporay()) {
if (isDebug) {
LOGGER.debug("supress backend connection read event ,for front con blocked write "
+ source + " backcon:" + con);
}
con.setSuppressReadTemporay(true);
((org.opencloudb.net.AbstractConnection) con).disableRead();
}
}
}
/**
* temporary upsupress channel read event ,because front connection is
* blocked
*/
public void unSupressTargetChannelReadEvent() {
if (!openWRFluxContrl) {
return;
}
final boolean isDebug = LOGGER.isDebugEnabled();
for (PhysicalConnection con : target.values()) {
if (con.isSuppressReadTemporay()) {
if (isDebug) {
LOGGER.debug("upsupress backend connection read event ,for front con can write more "
+ source + " backcon:" + con);
}
con.setSuppressReadTemporay(false);
((org.opencloudb.net.AbstractConnection) con).enableRead();
}
}
}
@Override
public ServerConnection getSource() {
return source;
}
@Override
public int getTargetCount() {
return target.size();
}
public Set<RouteResultsetNode> getTargetKeys() {
return target.keySet();
}
public PhysicalConnection getTarget(RouteResultsetNode key) {
return target.get(key);
}
public PhysicalConnection 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() + " 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 selected");
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(nodes, 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(PhysicalConnection conn, boolean debug) {
if (this.source.isAutocommit() || conn.isFromSlaveDB()
|| !conn.isModifiedSQLExecuted()) {
releaseConnection((RouteResultsetNode) conn.getAttachment(),
LOGGER.isDebugEnabled());
}
}
public void releaseConnection(RouteResultsetNode rrn, boolean debug) {
PhysicalConnection c = target.remove(rrn);
if (c != null) {
if (debug) {
LOGGER.debug("relase 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 PhysicalConnection bindConnection(RouteResultsetNode key,
PhysicalConnection 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 PhysicalConnection 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, PhysicalConnection> killees = null;
for (RouteResultsetNode node : target.keySet()) {
PhysicalConnection c = target.get(node);
if (c != null && c.isRunning()) {
if (!hooked) {
hooked = true;
killees = new HashMap<RouteResultsetNode, PhysicalConnection>();
count = new AtomicInteger(0);
}
killees.put(node, c);
count.incrementAndGet();
}
}
if (hooked) {
for (Entry<RouteResultsetNode, PhysicalConnection> 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(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()) {
PhysicalConnection 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();
}
}
public void clearResources() {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("clear session resources " + this);
}
this.releaseConnections();
if (this.singleNodeHandler != null) {
singleNodeHandler.clearResources();
}
if (this.multiNodeHandler != null) {
multiNodeHandler.clearResources();
}
}
public boolean closed() {
return source.isClosed();
}
}