Обсуждение: a simple example of XA (not working)

Поиск
Список
Период
Сортировка

a simple example of XA (not working)

От
Luca Ferrari
Дата:
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

Re: a simple example of XA (not working)

От
Heikki Linnakangas
Дата:
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() {

Re: a simple example of XA (not working)

От
Luca Ferrari
Дата:
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.


Re: a simple example of XA (not working)

От
Kris Jurka
Дата:

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

Re: a simple example of XA (not working)

От
Heikki Linnakangas
Дата:
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() {

Re: a simple example of XA (not working)

От
Kris Jurka
Дата:

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