Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 3A4A7200B40 for ; Sat, 2 Jul 2016 00:02:13 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 38C17160A61; Fri, 1 Jul 2016 22:02:13 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 59BCF160A6D for ; Sat, 2 Jul 2016 00:02:12 +0200 (CEST) Received: (qmail 55177 invoked by uid 500); 1 Jul 2016 22:02:11 -0000 Mailing-List: contact derby-dev-help@db.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: Delivered-To: mailing list derby-dev@db.apache.org Received: (qmail 55122 invoked by uid 99); 1 Jul 2016 22:02:11 -0000 Received: from arcas.apache.org (HELO arcas) (140.211.11.28) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 01 Jul 2016 22:02:11 +0000 Received: from arcas.apache.org (localhost [127.0.0.1]) by arcas (Postfix) with ESMTP id 05E2B2C02A1 for ; Fri, 1 Jul 2016 22:02:11 +0000 (UTC) Date: Fri, 1 Jul 2016 22:02:11 +0000 (UTC) From: "Brett Bergquist (JIRA)" To: derby-dev@db.apache.org Message-ID: In-Reply-To: References: Subject: [jira] [Created] (DERBY-6896) XA Transaction not rolled back when client disconnects without finalizing the transaction MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-JIRA-FingerPrint: 30527f35849b9dde25b450d4833f0394 archived-at: Fri, 01 Jul 2016 22:02:13 -0000 Brett Bergquist created DERBY-6896: -------------------------------------- Summary: XA Transaction not rolled back when client disconnect= s without finalizing the transaction Key: DERBY-6896 URL: https://issues.apache.org/jira/browse/DERBY-6896 Project: Derby Issue Type: Bug Components: JDBC Affects Versions: 10.10.2.0 Environment: Network server being access by a Network client Reporter: Brett Bergquist Originally in version 54764 of XATransactionState.java (introduced in DERB= Y-2432), the =E2=80=9CisFinished=E2=80=9D attribute was set to =E2=80=9Cfal= se" in the constructor and then then set to =E2=80=9Ctrue=E2=80=9D in the = =E2=80=9Cxa_finalize=E2=80=9D method. So the effect of this was that when = the =E2=80=9Ccancel=E2=80=9D method was called, no matter from where, the X= A transaction was rolled back. In DRDAXAProtocol.java, the method =E2=80= =9CrollbackTransaction=E2=80=9D looked like=20 void rollbackCurrentTransaction() { if (xid !=3D null) { boolean local =3D ( xid.getFormatId() =3D=3D -1); try { // if the transaction is not local disassociate the transac= tion from // the connection first because the rollback can not be per= formed // on a transaction associated with the XAResource try { if (!local) { XAResource xaResource =3D getXAResource(); // this will throw the XAException (because TMFAIL // will throw an exception) xaResource.end(xid, XAResource.TMFAIL); } } catch (XAException e) { // do not print out the exception generally thrown // when TMFAIL flag is present if (e.errorCode < XAException.XA_RBBASE || e.errorCode > XAException.XA_RBEND) { connThread.getServer().consoleExceptionPrint(e); } } rollbackTransaction(xid, false); } catch (DRDAProtocolException e) { // because we do not dump any DRDA stuff to the socket // the exception can not be thrown in this case // However, we will dump the exception to the console connThread.getServer().consoleExceptionPrint(e); } xid =3D null; } } DERBY-2871 changed this to look like: void rollbackCurrentTransaction() { if (xid !=3D null) { boolean local =3D ( xid.getFormatId() =3D=3D -1); if (!local) { try { XAXactId xid_im =3D new XAXactId(xid); getResourceAdapter().cancelXATransaction( xid_im, MessageId.CONN_CLOSE_XA_TRANSACTION_ROLLED_BACK ); } catch (XAException e) { Monitor.logThrowable(e); } } else { try { rollbackTransaction(xid, false); } catch (DRDAProtocolException e) { // because we do not dump any DRDA stuff to the socket // the exception can not be thrown in this case // However, we will log the exception to the monitor Monitor.logThrowable(e); } } xid =3D null; } } So you can see that now =E2=80=9CrollbackTransaction=E2=80=9D is only being= called if the transaction is local and =E2=80=9CResourceAdapter.cancelXATr= ansaction=E2=80=9D is being called when the transaction is not local (a XA = transaction). So now looking at what XATransactionState.cancel=E2=80=9D wh= ich is ultimately what is called by =E2=80=9CResourceAdapter.cancelXATransa= ction=E2=80=9D, we find that =E2=80=9CisFinished=E2=80=9D has been replaced= by =E2=80=9CperformTimeoutRollback=E2=80=9D and now the =E2=80=9Ccancel=E2= =80=9D looks like: synchronized void cancel(String messageId) throws XAException { // Check performTimeoutRollback just to be sure that // the cancellation task was not started // just before the xa_commit/rollback // obtained this object's monitor. if (performTimeoutRollback) { // Log the message about the transaction cancelled if (messageId !=3D null) Monitor.logTextMessage(messageId, xid.toString()); // Check whether the transaction is associated // with any EmbedXAResource instance. if (associationState =3D=3D XATransactionState.T1_ASSOCIATED) { conn.cancelRunningStatement(); EmbedXAResource assocRes =3D associatedResource; end(assocRes, XAResource.TMFAIL, true); } // Rollback the global transaction try { conn.xa_rollback(); } catch (SQLException sqle) { XAException ex =3D new XAException(XAException.XAER_RMERR); ex.initCause(sqle); throw ex; } // Do the cleanup on the resource creatingResource.returnConnectionToResource(this, xid); } } and =E2=80=9CperformTimeoutRollback=E2=80=9D is only set in=20 synchronized void scheduleTimeoutTask(long timeoutMillis) { // Mark the transaction to be rolled back bby timeout performTimeoutRollback =3D true; // schedule a time out task if the timeout was specified if (timeoutMillis > 0) { // take care of the transaction timeout TimerTask cancelTask =3D new CancelXATransactionTask(); TimerFactory timerFactory =3D Monitor.getMonitor().getTimerFact= ory(); Timer timer =3D timerFactory.getCancellationTimer(); timer.schedule(cancelTask, timeoutMillis); } else { timeoutTask =3D null; } } so the logic when from processing =E2=80=9Ccancel=E2=80=9D as long as the X= A transaction was not completed to processing =E2=80=9Ccancel=E2=80=9D only= when invoked from a timeout. At the same time the "DRDAXAProtocol.rollbac= kCurrentTransaction" went from always rolling back the transaction by calli= ng =E2=80=9CrollbackTransaction=E2=80=9D to it calling =E2=80=9CXATransctio= nState.cancel=E2=80=9D (ultimately) which will do nothing from this code pa= th as =E2=80=9CperformTimeoutRollback=E2=80=9D will never be true. =20 I understand what the check is =E2=80=9Ccancel=E2=80=9D was trying to do, i= t was trying to prevent =E2=80=9Ccancel=E2=80=9D to be processed when invok= ed by the timer right after it had been processed and completed by normal X= A transaction work. Unfortunately what it now does is not correct. For e= xample, I just wrote a simple test program that looks like: private static void startTransactionButDontComplete(XAConnection connXa= , Connection conn, String schema, String table) throws SQLException, XAExce= ption { XAResource resXa =3D connXa.getXAResource(); Xid xid =3D new MyXid(100, new byte[]{0x01}, new byte[]{0x02}) = ; =20 resXa.start(xid, XAResource.TMNOFLAGS); Statement stmt =3D conn.createStatement(); stmt.executeUpdate("DELETE FROM " + schema + "." + table); resXa.end(xid, XAResource.TMSUCCESS); System.exit(1); } What this does is to start an XA transaction, execute an update, end the XA= transaction, but kill the database client before the XA transaction is eit= her committed or rollback. With the code before DERBY-2871, when the the d= atabase engine detected the client disconnect, the transaction would be rol= led back. Now what happens is that there is a XA transaction that will nev= er complete (commit or rollback) that is left in the database. So this is = what I see in the database after the application exits: Bretts-MacBook-Pro:~ brett$ /Applications/db-derby-10.9.1.0-bin/bin/ij ij version 10.9 ij> connect 'jdbc:derby://192.169.1.31:1527/csemdb'; ij> select * from syscs_diag.transaction_table where status !=3D 'IDLE'; XID |GLOBAL_XID = |USERN= AME = |TYPE = |STATUS |FIRST_INSTANT |SQL_TEXT = = =20 ---------------------------------------------------------------------------= ---------------------------------------------------------------------------= ---------------------------------------------------------------------------= ---------------------------------------------------------------------------= ---------------------------------------------------------------------------= ---------------------------------------------------------------------------= ------------- 1420140985 |(100,01,02) = |CSEM = = |UserTransaction = |ACTIVE |(113348,692000) |NULL = = =20 1 row selected ij>=20 Doing some searching it does not seem that this is the correct behavior: http://stackoverflow.com/questions/30608378/what-happens-to-my-jdbc-transac= tion-if-i-lose-my-session http://dba.stackexchange.com/questions/60001/what-happens-to-a-transaction-= if-the-network-connection-fails -- This message was sent by Atlassian JIRA (v6.3.4#6332)