Thread: a simple example of XA (not working)
Hi all, I'm trying to build a simple "manual" example of the use of the XA extension, the following is the code: PGXADataSource dataSource = new PGXADataSource(); dataSource.setDatabaseName("hrpm"); dataSource.setUser("luca"); dataSource.setServerName("localhost"); XAConnection connection = dataSource.getXAConnection(); System.out.println("Connesso!"); XAResource resource = connection.getXAResource(); MyXid identifier = new MyXid(); identifier.setBranchQualifier(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05}); identifier.setGlobalTransactionId(new byte[] {0x05, 0x04, 0x03, 0x02, 0x01}); identifier.setFormatId(100); try{ resource.start(identifier, XAResource.TMNOFLAGS); Connection jdbcConnection = connection.getConnection(); Statement statement = jdbcConnection.createStatement(); String sql = "INSERT INTO ..."; int inserted = statement.executeUpdate(sql); resource.end(identifier, XAResource.TMSUCCESS); int commit = resource.prepare(identifier); if( commit == XAResource.XA_OK ) resource.commit(identifier, false); else resource.rollback(identifier); now the exception that is raised when the program commits the transaction is the following: javax.transaction.xa.XAException: org.postgresql.util.PSQLException: ERROR: prepared transaction with identifier "100_BQQDAgE=_AQIDBAU=" does not exist at org.postgresql.xa.PGXAConnection.commitPrepared(PGXAConnection.java:412) at org.postgresql.xa.PGXAConnection.commit(PGXAConnection.java:339) and the log of the database says: 2007-06-16 16:05:48 CEST LOG: execute <unnamed>: INSERT INTO .... 2007-06-16 16:05:48 CEST LOG: execute <unnamed>: PREPARE TRANSACTION '100_BQQDAgE=_AQIDBAU=' 2007-06-16 16:05:48 CEST WARNING: non c'è nessuna transazione in corso 2007-06-16 16:05:48 CEST LOG: execute <unnamed>: COMMIT PREPARED '100_BQQDAgE=_AQIDBAU=' 2007-06-16 16:05:48 CEST ERROR: prepared transaction with identifier "100_BQQDAgE=_AQIDBAU=" does not exist 2007-06-16 16:05:48 CEST STATEMENT: COMMIT PREPARED '100_BQQDAgE=_AQIDBAU=' 2007-06-16 16:05:49 CEST LOG: unexpected EOF on client connection that is an error since it seems no one transaction has been started. However, debugging the application, I've seen that the driver disables the autocommit mode, thus where is the problem here? I'm sorry, I'm not a XA expert, just learning. Thanks, Luca
Luca Ferrari wrote: > Hi all, > I'm trying to build a simple "manual" example of the use of the XA extension, > the following is the code: > > PGXADataSource dataSource = new PGXADataSource(); > dataSource.setDatabaseName("hrpm"); > dataSource.setUser("luca"); > dataSource.setServerName("localhost"); > > XAConnection connection = dataSource.getXAConnection(); > System.out.println("Connesso!"); > XAResource resource = connection.getXAResource(); > MyXid identifier = new MyXid(); > identifier.setBranchQualifier(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05}); > identifier.setGlobalTransactionId(new byte[] {0x05, 0x04, 0x03, 0x02, 0x01}); > identifier.setFormatId(100); > > try{ > resource.start(identifier, XAResource.TMNOFLAGS); > > > Connection jdbcConnection = connection.getConnection(); > Statement statement = jdbcConnection.createStatement(); > String sql = "INSERT INTO ..."; > int inserted = statement.executeUpdate(sql); > > resource.end(identifier, XAResource.TMSUCCESS); > > int commit = resource.prepare(identifier); > if( commit == XAResource.XA_OK ) > resource.commit(identifier, false); > else > resource.rollback(identifier); > > > now the exception that is raised when the program commits the transaction is > the following: > > javax.transaction.xa.XAException: org.postgresql.util.PSQLException: ERROR: > prepared transaction with identifier "100_BQQDAgE=_AQIDBAU=" does not exist > at org.postgresql.xa.PGXAConnection.commitPrepared(PGXAConnection.java:412) > at org.postgresql.xa.PGXAConnection.commit(PGXAConnection.java:339) > > and the log of the database says: > > 2007-06-16 16:05:48 CEST LOG: execute <unnamed>: INSERT INTO .... > 2007-06-16 16:05:48 CEST LOG: execute <unnamed>: PREPARE > TRANSACTION '100_BQQDAgE=_AQIDBAU=' > 2007-06-16 16:05:48 CEST WARNING: non c'è nessuna transazione in corso > 2007-06-16 16:05:48 CEST LOG: execute <unnamed>: COMMIT > PREPARED '100_BQQDAgE=_AQIDBAU=' > 2007-06-16 16:05:48 CEST ERROR: prepared transaction with > identifier "100_BQQDAgE=_AQIDBAU=" does not exist > 2007-06-16 16:05:48 CEST STATEMENT: COMMIT PREPARED '100_BQQDAgE=_AQIDBAU=' > 2007-06-16 16:05:49 CEST LOG: unexpected EOF on client connection > > that is an error since it seems no one transaction has been started. However, > debugging the application, I've seen that the driver disables the autocommit > mode, thus where is the problem here? > I'm sorry, I'm not a XA expert, just learning. This seems to be an undesired side-effect of this patch that was committed in December: > date: 2006/12/01 10:13:46; author: jurka; state: Exp; lines: +19 -22 > A XAConnections default autocommit state should be true. The > driver was previously setting autocommit to false and assuming it > would stay that way. This caused two problems. First, some > applications expected to be able to issue local autocommit > transactions prior to begin(). Second, some TMs (Geronimo) set the > autocommit state to true themselves, which causes problems as the > driver did not expect it to be changed. This patch correctly disables > and enables autocommit around begin, prepare, commit, and rollback > methods. getConnection sets autocommit to false, so even though start set it to true, it's reset to false in the call to getConnection. Attached patch fixes that by explicitly setting autocommit to the right mode in PGXAConnection.getConnection. As a work-around, just call jdbcConnection.setAutoCommit(false) in your program before executing any queries. -- Heikki Linnakangas EnterpriseDB http://www.enterprisedb.com Index: org/postgresql/xa/PGXAConnection.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/xa/PGXAConnection.java,v retrieving revision 1.10 diff -c -r1.10 PGXAConnection.java *** org/postgresql/xa/PGXAConnection.java 1 Dec 2006 11:53:02 -0000 1.10 --- org/postgresql/xa/PGXAConnection.java 17 Jun 2007 07:40:03 -0000 *************** *** 61,67 **** public Connection getConnection() throws SQLException { ! return super.getConnection(); } public XAResource getXAResource() { --- 61,80 ---- public Connection getConnection() throws SQLException { ! Connection conn = super.getConnection(); ! ! // When we're outside an XA transaction, autocommit ! // is supposed to be true, per usual JDBC convention. ! // When an XA transaction is in progress, it should be ! // false. ! ! // super.getConnection resets autocommit to true, so we ! // have to set it to false before handing the connection ! // to the caller, if an XA transaction is active. ! if(state == STATE_ACTIVE) ! conn.setAutoCommit(false); ! ! return conn; } public XAResource getXAResource() {
Thanks for your help, I'm happy we have identified and corrected a bug in the driver! Luca On Sunday 17 June 2007 your cat, walking on the keyboard, wrote: > This seems to be an undesired side-effect of this patch that was > > committed in December: > > date: 2006/12/01 10:13:46; author: jurka; state: Exp; lines: +19 -22 > > A XAConnections default autocommit state should be true. The > > driver was previously setting autocommit to false and assuming it > > would stay that way. This caused two problems. First, some > > applications expected to be able to issue local autocommit > > transactions prior to begin(). Second, some TMs (Geronimo) set the > > autocommit state to true themselves, which causes problems as the > > driver did not expect it to be changed. This patch correctly disables > > and enables autocommit around begin, prepare, commit, and rollback > > methods. > > getConnection sets autocommit to false, so even though start set it to > true, it's reset to false in the call to getConnection. Attached patch > fixes that by explicitly setting autocommit to the right mode in > PGXAConnection.getConnection. > > As a work-around, just call jdbcConnection.setAutoCommit(false) in your > program before executing any queries.
On Sun, 17 Jun 2007, Heikki Linnakangas wrote: > getConnection sets autocommit to false, so even though start set it to true, > it's reset to false in the call to getConnection. Attached patch fixes that > by explicitly setting autocommit to the right mode in > PGXAConnection.getConnection. > I don't think this fixes the problem completely if you have code that calls XAConnection.getConnection more than once: PGXADataSource xads = new PGXADataSource(); XAConnection xaconn = xads.getXAConnection(); XAResource xares = xaconn.getXAResource(); xares.start(xid, XAResource.TMNOFLAGS); Connection conn1 = xaconn.getConnection(); conn1.createStatement().executeUpdate(...); Connection conn2 = xaconn.getConnection(); The second call to get connection will result in: setAutoCommit(true); setAutoCommit(false); on the real underlying connection, which will end up committing that part of the transaction. Kris Jurka
Kris Jurka wrote: > > > On Sun, 17 Jun 2007, Heikki Linnakangas wrote: > >> getConnection sets autocommit to false, so even though start set it to >> true, it's reset to false in the call to getConnection. Attached patch >> fixes that by explicitly setting autocommit to the right mode in >> PGXAConnection.getConnection. >> > > I don't think this fixes the problem completely if you have code that > calls XAConnection.getConnection more than once: > > PGXADataSource xads = new PGXADataSource(); > XAConnection xaconn = xads.getXAConnection(); > XAResource xares = xaconn.getXAResource(); > > xares.start(xid, XAResource.TMNOFLAGS); > > Connection conn1 = xaconn.getConnection(); > conn1.createStatement().executeUpdate(...); > > Connection conn2 = xaconn.getConnection(); > > The second call to get connection will result in: > > setAutoCommit(true); > setAutoCommit(false); > > on the real underlying connection, which will end up committing that > part of the transaction. You got me at first, but actually the second getConnection call will *roll back* the first part of the transaction. See AbstractJdbc23PooledConnection.getConnection. I don't think that's a valid thing to do in the first place, though we could handle it more gracefully. I added a note of that in the comment in PGXAConnection.getConnection anyway. I also added a comment block describing the three states a PGXAConection object can be in. There's still corner cases like the above, where the we really should throw an error instead of getting confused. Another is that if you call end(), and then use the connection for something else, that "else" will become part of the transaction. And if you call connection.commit/rollback/setAutoCommit, that will screw up the not-yet-prepared transaction. It would be nice to catch and handle those cases better, but that's more code. -- Heikki Linnakangas EnterpriseDB http://www.enterprisedb.com ? build.local.properties ? foo.txt ? xa-PGObjectFactory-fix-2.patch ? xa-PGObjectFactory-fix.patch ? xa-autocommit-fix-2.patch ? xa-autocommit-fix.patch ? xa-endthenjoin-2.diff ? xa-endthenjoin.diff ? org/postgresql/xa/xa-autocommit-fix.patch Index: org/postgresql/test/xa/XADataSourceTest.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/test/xa/XADataSourceTest.java,v retrieving revision 1.8 diff -c -r1.8 XADataSourceTest.java *** org/postgresql/test/xa/XADataSourceTest.java 1 Dec 2006 11:53:02 -0000 1.8 --- org/postgresql/test/xa/XADataSourceTest.java 25 Jun 2007 12:55:51 -0000 *************** *** 220,227 **** --- 220,230 ---- public void testAutoCommit() throws Exception { Xid xid = new CustomXid(6); + // When not in an XA transaction, autocommit should be true + // per normal JDBC rules. assertTrue(conn.getAutoCommit()); + // When in an XA transaction, autocommit should be false xaRes.start(xid, XAResource.TMNOFLAGS); assertFalse(conn.getAutoCommit()); xaRes.end(xid, XAResource.TMSUCCESS); *************** *** 236,251 **** --- 239,270 ---- xaRes.commit(xid, false); assertTrue(conn.getAutoCommit()); + // Check that autocommit is reset to true after a 1-phase rollback xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); xaRes.rollback(xid); assertTrue(conn.getAutoCommit()); + // Check that autocommit is reset to true after a 2-phase rollback xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); xaRes.prepare(xid); xaRes.rollback(xid); assertTrue(conn.getAutoCommit()); + + // Check that autoCommit is set correctly after a getConnection-call + conn = xaconn.getConnection(); + assertTrue(conn.getAutoCommit()); + + xaRes.start(xid, XAResource.TMNOFLAGS); + + conn = xaconn.getConnection(); + assertFalse(conn.getAutoCommit()); + + xaRes.end(xid, XAResource.TMSUCCESS); + xaRes.prepare(xid); + xaRes.rollback(xid); + assertTrue(conn.getAutoCommit()); } public void testEndThenJoin() throws XAException { Index: org/postgresql/xa/PGXAConnection.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/xa/PGXAConnection.java,v retrieving revision 1.10 diff -c -r1.10 PGXAConnection.java *** org/postgresql/xa/PGXAConnection.java 1 Dec 2006 11:53:02 -0000 1.10 --- org/postgresql/xa/PGXAConnection.java 25 Jun 2007 12:55:52 -0000 *************** *** 36,41 **** --- 36,62 ---- private final BaseConnection conn; private final Logger logger; + /* + * PGXAConnection-object can be in one of three states: + * + * IDLE + * Not associated with a XA-transaction. You can still call + * getConnection and use the connection outside XA. currentXid is null. + * autoCommit is true on a connection by getConnection, per normal JDBC + * rules, though the caller can change it to false and manage transactions + * itself using Connection.commit and rollback. + * + * ACTIVE + * start has been called, and we're associated with an XA transaction. + * currentXid is valid. autoCommit is false on a connection returned by + * getConnection, and should not be messed with by the caller or the XA + * transaction will be broken. + * + * ENDED + * end has been called, but the transaction has not yet been prepared. + * currentXid is still valid. You shouldn't use the connection for anything + * else than issuing a XAResource.commit or rollback. + */ private Xid currentXid; private int state; *************** *** 61,67 **** public Connection getConnection() throws SQLException { ! return super.getConnection(); } public XAResource getXAResource() { --- 82,101 ---- public Connection getConnection() throws SQLException { ! Connection conn = super.getConnection(); ! ! // When we're outside an XA transaction, autocommit ! // is supposed to be true, per usual JDBC convention. ! // When an XA transaction is in progress, it should be ! // false. ! ! // super.getConnection rolls back any previous transaction, and resets ! // autocommit to true, so we have to set it to false before handing the ! // connection to the caller, if an XA transaction is active. ! if(state == STATE_ACTIVE) ! conn.setAutoCommit(false); ! ! return conn; } public XAResource getXAResource() {
On Mon, 25 Jun 2007, Heikki Linnakangas wrote: > Kris Jurka wrote: >> >> I don't think this fixes the problem completely if you have code that calls >> XAConnection.getConnection more than once: >> > > You got me at first, but actually the second getConnection call will *roll > back* the first part of the transaction. See > AbstractJdbc23PooledConnection.getConnection. I don't think that's a valid > thing to do in the first place, though we could handle it more gracefully. I > added a note of that in the comment in PGXAConnection.getConnection anyway. > OK. I always have trouble remembering who does what with XA (TM vs user), so in this case we can rely on the TM getting it right, while I was thinking we had to prevent the user from getting it wrong. Applied to 8.1, 8.2, and HEAD. Kris Jurka