package fm.liu.timo.server.session; import java.io.File; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap.KeySetView; import fm.liu.timo.TimoServer; import fm.liu.timo.backend.Node; import fm.liu.timo.config.Isolations; import fm.liu.timo.config.model.Database; import fm.liu.timo.mysql.handler.xa.XACommitHandler; import fm.liu.timo.mysql.handler.xa.XAEndHandler; import fm.liu.timo.mysql.handler.xa.XAPrepareHandler; import fm.liu.timo.mysql.handler.xa.XARollbackHandler; import fm.liu.timo.mysql.handler.xa.XAStartHandler; import fm.liu.timo.mysql.packet.OkPacket; import fm.liu.timo.net.connection.BackendConnection; import fm.liu.timo.server.ServerConnection; import fm.liu.timo.server.session.handler.ResultHandler; /** * @author liuhuanting */ public class XATransactionSession extends TransactionSession { private String XID; private XAState state; private File prepareLog; public enum XAState { ACTIVE, IDLE, PREPARED, FINISHED } public XATransactionSession(ServerConnection front) { super(front); variables.setIsolationLevel(Isolations.SERIALIZABLE); } public void start(Database database) { this.XID = TimoServer.getInstance().nextXID(); Map<Integer, Node> nodes = TimoServer.getInstance().getConfig().getNodes(); ResultHandler handler = new XAStartHandler(this, database.getNodes().size()); while (TimoServer.getInstance().isXACommiting()) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } TimoServer.getInstance().setXAStarting(true); database.getNodes() .forEach(id -> nodes.get(id).getSource().query("XA START " + XID, handler)); } @Override public void commit() { if (getConnections().isEmpty()) { super.commit(); return; } Collection<BackendConnection> cons = availableConnections(); if (cons.size() == getConnections().size()) { ResultHandler handler = new XAEndHandler(this, cons, true); cons.forEach(con -> con.query("XA END " + XID, handler)); } else { onError(); } } public void xaPrepare() { if (getConnections().isEmpty()) { super.commit(); return; } Collection<BackendConnection> cons = availableConnections(); if (cons.size() == getConnections().size()) { ResultHandler handler = new XAPrepareHandler(this, cons); cons.forEach(con -> con.query("XA PREPARE " + XID, handler)); } else { onError(); } } public void xaCommit() { if (getConnections().isEmpty()) { super.commit(); return; } while (TimoServer.getInstance().isXAStarting()) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } TimoServer.getInstance().setXACommiting(true); Collection<BackendConnection> cons = availableConnections(); if (cons.size() == getConnections().size()) { ResultHandler handler = new XACommitHandler(this, cons); cons.forEach(con -> con.query("XA COMMIT " + XID, handler)); } else { onError(); } } private void rollback() { if (getConnections().isEmpty()) { super.commit(); return; } Collection<BackendConnection> cons = availableConnections(); if (cons.size() == getConnections().size()) { ResultHandler handler = new XAEndHandler(this, cons, false); cons.forEach(con -> con.query("XA END " + XID, handler)); } else { onError(); } } public void xaRollback() { if (getConnections().isEmpty()) { super.commit(); return; } Collection<BackendConnection> cons = availableConnections(); if (cons.size() == getConnections().size()) { ResultHandler handler = new XARollbackHandler(this, cons, true); cons.forEach(con -> con.query("XA ROLLBACK " + XID, handler)); } else { onError(); } } @Override public void rollback(boolean response) { switch (state) { case ACTIVE: rollback(); break; default: front.write(OkPacket.OK); break; } } @Override public void clear() { front.reset(); KeySetView<Integer, BackendConnection> keys = connections.keySet(); for (Integer id : keys) { BackendConnection con = connections.remove(id); if (con.isClosed()) { continue; } con.setHandler(null); con.close("cleared"); } this.state = XAState.FINISHED; this.XID = null; } public void release() { front.reset(); KeySetView<Integer, BackendConnection> keys = connections.keySet(); for (Integer id : keys) { BackendConnection con = connections.remove(id); con.release(); } this.state = XAState.FINISHED; this.XID = null; } public XAState getState() { return state; } public void setState(XAState state) { this.state = state; if (this.prepareLog != null && state == XAState.FINISHED) { this.prepareLog.delete(); } } public String getXID() { return XID; } public void setPrepareLog(File file) { this.prepareLog = file; } }