Bug #46925 XAConnection Map never updated in class SuspendableXAConnection
Submitted: 25 Aug 2009 19:58 Modified: 16 Sep 2009 13:43
Reporter: Tony Bussieres Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / J Severity:S2 (Serious)
Version:5.1.8 OS:Any
Assigned to: Tony Bedford CPU Architecture:Any
Tags: pinGlobalTxToPhysicalConnection, SuspendableXAConnection, xa

[25 Aug 2009 19:58] Tony Bussieres
Description:
In the class com.mysql.jdbc.jdbc2.optional.SuspendableXAConnection (used when  pinGlobalTxToPhysicalConnection=true). There's a static Map (XIDS_TO_PHYSICAL_CONNECTIONS) that tracks the Xid with the XAConnection, however this map is never populated.

The effect is that the SuspendableXAConnection is never pinned to the real XA connection. 

Instead its creating new connections at each start/end/resume/prepare/... calls.

Affect versions (tested) 5.0.6, 5.0.8, 5.1.8

How to repeat:
Test case that will fail with an unpatched version of the driver :

import javax.sql.XAConnection;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import com.mysql.jdbc.jdbc2.optional.SuspendableXAConnection;

import junit.framework.TestCase;

/**
 * 
 * @author Tony Bussieres
 *
 */
public class TestPinnedXAConnection extends TestCase {

	public void testXAPinnedConnection() throws Exception {
		MysqlXADataSource xads1 = new MysqlXADataSource();
		MysqlXADataSource xads2 = new MysqlXADataSource();

		xads1.setPinGlobalTxToPhysicalConnection(true);
		xads1.setUrl("jdbc:mysql://172.16.13.74:4040/TestDB");
		xads1.setUser("testuser");
		xads1.setPassword("testpass");
		xads1.setLogXaCommands(true);
		
		xads2.setPinGlobalTxToPhysicalConnection(true);
		xads2.setUrl("jdbc:mysql://172.16.13.74:4040/TestDB2");
		xads2.setUser("testuser");
		xads2.setPassword("testpass");
		xads2.setLogXaCommands(true);
		
		Xid txid = new MyXid(new byte[] { 0x1 }, new byte[] {0xF}); // fake Xid
		
		
		XAConnection c1 = xads1.getXAConnection();
		assertTrue(c1 instanceof SuspendableXAConnection);
		// start a transaction on one connection
		c1.getXAResource().start(txid, XAResource.TMNOFLAGS);
		c1.getXAResource().end(txid, XAResource.TMSUCCESS);
		
		XAConnection c2 = xads2.getXAConnection();
		assertTrue(c2 instanceof SuspendableXAConnection);
		// prepare on another one. Since we are using a "pinned" connection we should have the same "currentXAConnection" for both SuspendableXAConnection 
		c2.getXAResource().prepare(txid); // this will fail without the patch.
		c2.getXAResource().commit(txid,false);

		/* stack trace generated without the patch 	:
		 * 	
		com.mysql.jdbc.jdbc2.optional.MysqlXAException: XAER_RMFAIL: The command cannot be executed when global transaction is in the  NON-EXISTING state
		at com.mysql.jdbc.jdbc2.optional.MysqlXAConnection.mapXAExceptionFromSQLException(MysqlXAConnection.java:600)
		at com.mysql.jdbc.jdbc2.optional.MysqlXAConnection.dispatchCommand(MysqlXAConnection.java:583)
		at com.mysql.jdbc.jdbc2.optional.MysqlXAConnection.prepare(MysqlXAConnection.java:385)
		at com.mysql.jdbc.jdbc2.optional.SuspendableXAConnection.prepare(SuspendableXAConnection.java:136)
		at TestXA.testXAPinnedConnection(TestXA.java:40)
		at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
		at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
		at java.lang.reflect.Method.invoke(Method.java:597)
		at junit.framework.TestCase.runTest(TestCase.java:154)
		at junit.framework.TestCase.runBare(TestCase.java:127)
		at junit.framework.TestResult$1.protect(TestResult.java:106)
		at junit.framework.TestResult.runProtected(TestResult.java:124)
		at junit.framework.TestResult.run(TestResult.java:109)
		at junit.framework.TestCase.run(TestCase.java:118)
		at junit.framework.TestSuite.runTest(TestSuite.java:208)
		at junit.framework.TestSuite.run(TestSuite.java:203)
		at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:130)
		at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
		at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
		at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
		at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
		at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
		
		*/

	}
	
	class MyXid implements Xid {
		
		byte[] gtxid;
		byte[] brid;

		public MyXid(byte[] gtxid, byte[] brid) {
			this.gtxid = gtxid;
			this.brid  = brid;
		}

		public byte[] getBranchQualifier() {
			return brid;
		}

		public int getFormatId() {
			return 0;
		}

		public byte[] getGlobalTransactionId() {
			return gtxid;
		}
		
	}
}

Suggested fix:
patch (for 5.1.8)

--- mysql-connector-java-5.1.8/src/com/mysql/jdbc/jdbc2/optional/SuspendableXAConnection.java   2009-07-14 13:12:13.000000000 -0400
+++ /work/download/mysql-connector-java-5.1.8/src/com/mysql/jdbc/jdbc2/optional/SuspendableXAConnection.java    2009-08-25 13:29:30.000000000 -0400
@@ -75,6 +75,7 @@
                if (conn == null) {
                        conn = new MysqlXAConnection(connectionToWrap,
                                        connectionToWrap.getLogXaCommands());
+                       addXAConnectionMapping(xid,conn);
                }
 
                return conn;
@@ -83,6 +84,10 @@
        private static synchronized void removeXAConnectionMapping(Xid xid) {
                XIDS_TO_PHYSICAL_CONNECTIONS.remove(xid);
        }
+
+       private static synchronized void addXAConnectionMapping(Xid xid,XAConnection conn) {
+               XIDS_TO_PHYSICAL_CONNECTIONS.put(xid,conn);
+       }
 
        private synchronized void switchToXid(Xid xid) throws XAException {
                if (xid == null) {
[25 Aug 2009 20:02] Tony Bussieres
Patch for driver 5.1.8

Attachment: SuspendableXAConnection.java.patch (text/x-diff), 866 bytes.

[25 Aug 2009 20:03] Tony Bussieres
TestCase to repeat the problem.

Attachment: TestPinnedXAConnection.java (text/x-java), 3.86 KiB.

[25 Aug 2009 20:07] Tony Bussieres
How to patch :

Copy the patch in /tmp 

cd /where/your/mysql-5.1.8/dist/is

patch -p1 < /tmp/SuspendableXAConnection.java.patch

rebuild your driver using ant.
[25 Aug 2009 23:28] Mark Matthews
Fixed for 5.1.9 (available in nightly snapshot builds after 00:00 GMT Aug-26 from http://downloads.mysql.com/snapshots.php)
[26 Aug 2009 13:32] Tony Bussieres
This test case is more documented and makes more sense than the first one

Attachment: TestPinnedXAConnection.java (text/x-java), 3.20 KiB.

[27 Aug 2009 12:46] Tonci Grgin
Tony, you're right. We'll see what went wrong. Btw, you do not need to hide comments like that :-)
[29 Aug 2009 17:03] Tonci Grgin
Tony, snapshot's added.
[16 Sep 2009 13:43] Tony Bedford
An entry was added to the 5.1.9 changelog:

In the class com.mysql.jdbc.jdbc2.optional.SuspendableXAConnection, which is used when pinGlobalTxToPhysicalConnection=true, there is a static map (XIDS_TO_PHYSICAL_CONNECTIONS) that tracks the Xid with the XAConnection, however this map was not populated. The effect was that the SuspendableXAConnection was never pinned to the real XA connection. Instead it created new connections on calls to start, end, resume, and prepare.