[Mulgara-svn] r561 - branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver

andrae at mulgara.org andrae at mulgara.org
Fri Nov 16 02:59:01 UTC 2007


Author: andrae
Date: 2007-11-15 20:59:00 -0600 (Thu, 15 Nov 2007)
New Revision: 561

Modified:
   branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java
   branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResource.java
Log:
After a surprising number of false starts the MulgaraXAResource is now pretty
much done.  This commit moves much of the error handling into the Transaction
object.  The XAResource is a per-Session object, the Transaction is a
per-Transaction object.

Note: start/end RESUME/SUSPEND will permit multiple overlapping transactions on
a single session provided they are never active concurrently - this behaviour is
related to the Resource-Sharing requirements in the JTA spec (3.4.6).

Note: By far the most significant and complex part of this interaction is
ensuring the accurate detection of mixed transaction resolutions.  I am still
not convinced I have provided for all the subtlties of heuristic completion.



Modified: branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java
===================================================================
--- branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java	2007-11-15 21:08:31 UTC (rev 560)
+++ branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java	2007-11-16 02:59:00 UTC (rev 561)
@@ -44,14 +44,22 @@
  */
 public class MulgaraExternalTransaction implements MulgaraTransaction {
   private Xid xid;
-  private Set<EnlistableResource> resources;
 
+  private Set<EnlistableResource> enlisted;
+  private Set<EnlistableResource> prepared;
+  private Set<EnlistableResource> committed;
+  private Set<EnlistableResource> rollbacked;
+
   private MulgaraXAResource resource;
 
   MulgaraExternalTransaction(Xid xid, MulgaraXAResource resource) {
     this.xid = xid;
-    this.resources = new HashSet<EnlistableResource>();
     this.resource = resource;
+
+    this.enlisted = new HashSet<EnlistableResource>();
+    this.prepared = new HashSet<EnlistableResource>();
+    this.committed = new HashSet<EnlistableResource>();
+    this.rollbacked = new HashSet<EnlistableResource>();
   }
 
   // We ignore reference counting in external transactions
@@ -107,4 +115,145 @@
       resources.add(enlistable);
     }
   }
+
+  //
+  // Methods used to manage transaction from XAResource.
+  //
+
+  boolean isHeuristicallyRollbacked() {
+    return false;
+  }
+
+  boolean isHeuristicallyCommitted() {
+    return false;
+  }
+
+  void prepare(Xid xid) throws XAException {
+    for (EnlistableResource er : xa.getEnlistedResources()) {
+      er.getXAResource().prepare(xid);
+      prepared.add(er);
+    }
+    // status = PREPARED; ?
+  }
+
+  void rollback(Xid xid) throws XAException {
+    Map<EnlistableResource, XAException> rollbackFailed = new HashMap<EnlistableResource, XAException>();
+
+    assert exception != null;
+
+    for (EnlistableResource er : xa.getEnlistedResources()) {
+      try {
+        if (!committed.contains(er)) {
+          er.getXAResource().rollback(xid, false);
+          rollbacked.add(er);
+        }
+      } catch (XAException ex) {
+        logger.error("Attempt to rollback resource failed", ex);
+        rollbackFailed.put(er, ex);
+      }
+    }
+
+    if (rollbackFailed.isEmpty()) {
+      if (committed.isEmpty()) {        // Clean failure and rollback - rethrow cause
+        // status = ROLLBACK_COMPLETED;
+      } else {                          // No rollback-failure, but partial commit
+        heurCode = XAException.XA_HEURMIX;
+        throw new XAException(heurCode);
+      }
+    } else {
+      // Something went wrong - start by assuming if one committed all committed
+      heurCode = (committed.isEmpty()) ? 0 : XAException.XA_HEURCOM;
+      // Then check every rollback failure code for a contradiction to all committed.
+      for (XAException xaex : rollbackFailed.values()) {
+        switch (xaex.errorCode) {
+          case XAException.XA_HEURHAZ:  
+          case XAException.XAER_NOTA:
+          case XAException.XAER_RMERR:
+          case XAException.XAER_RMFAIL:
+          case XAException.XAER_INVAL:
+          case XAException.XAER_PROTO:
+            // All these amount to not knowing the result - so we have a hazard
+            // unless we already know we have a mixed result.
+            if (heurCode != XAException.XA_HEURMIX) {
+              XAException.XA_HEURHAZ;
+            }
+            break;
+          case XAException.XA_HEURCOM:
+            if (!rollbacked.isEmpty() || heurCode == XAException.XA_HEURRB) {
+              // We know something else was rollbacked, so we know we have a mixed result.
+              heurCode = XAException.XA_HEURMIX;
+            } else if (heurCode == 0) {
+              heurCode = XAException.XA_HEURCOM;
+            } // else it's a HEURHAZ or a HEURCOM and stays that way.
+            break;
+          case XAException.XA_HEURRB:
+            if (!committed.isEmpty() || heurCode == XAException.XA_HEURCOM) {
+              heurCode = XAException.XA_HEURMIX;
+            } else if (heurCode == 0) {
+              heurCode = XAException.XA_HEURRB;
+            } // else it's a HEURHAZ or a HEURRB and stays that way.
+            break;
+          case XAException.XA_HEURMIX:
+            // It can't get worse than, we know we have a mixed result.
+            heurCode = XAException.XA_HEURMIX;
+            break;
+          default:
+            // The codes above are the only codes permitted from a rollback() so
+            // anything else indicates a serious error in the resource-manager.
+            throw new XAException(XAException.XAER_RMERR);
+        }
+      }
+
+      throw new XAException(heurCode);
+    }
+  }
+
+  boolean isRollbackOnly() {
+    return rollbackOnly;
+  }
+
+  private void doOnePhaseCommit(Xid xid, MulgaraExternalTransaction xa) throws XAException {
+    try {
+      boolean success = true;
+      XAException exception = null;
+      Set<EnlistableResource> prepared = new HashSet<EnlistableResource>();
+
+      try {
+        for (EnlistableResource er : xa.getEnlistedResources()) {
+          er.getXAResource().prepare(xid);
+          prepared.add(er);
+        }
+      } catch (XAResource ex) {
+        exception = ex;
+        success = false;
+      } catch (Throwable th) {
+        logger.error("Error preparing transaction in onePhase commit", th);
+        exception = new XAException(XAException.XAER_RMERR);
+        success = false;
+      }
+
+      Set<EnlistableResource> committed = new HashSet<EnlistableResource>();
+
+      if (success) {
+        try {
+          for (EnlistableResource er : xa.getEnlistedResources()) {
+            er.getXAResource().commit(xid, false);
+            committed.add(er);
+          }
+        } catch (XAResource ex) {
+          logger.error("Error committing resource in onePhase commit", ex);
+          exception = ex;
+          success = false;
+        }
+      }
+
+      }
+    } catch (XAException ex) {
+      logger.error("Attempt to perform one-phase-commit failed", ex);
+      throw ex;
+    } catch (Throwable th) {
+      logger.error("Exception while performing one-phase-commit", th);
+      throw new XAException(XAException.XAER_RMERR);
+    }
+  }
 }

Modified: branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResource.java
===================================================================
--- branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResource.java	2007-11-15 21:08:31 UTC (rev 560)
+++ branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResource.java	2007-11-16 02:59:00 UTC (rev 561)
@@ -54,34 +54,95 @@
     this.xa2xid = new Assoc1toNMap<MulgaraExternalTransaction, Xid>();
   }
 
-  public void commit(Xid xid, boolean onePhase) {
+  /**
+   * Commit transaction identified by xid.
+   *
+   * Transaction must be Idle, Prepared, or Heuristically-Completed.
+   * If transaction not Heuristically-Completed we are required to finish it,
+   * clean up, and forget it.
+   * If transaction is Heuristically-Completed we throw an exception and wait
+   * for a call to forget().
+   */
+  public void commit(Xid xid, boolean onePhase) throws XAException {
     MulgaraExternalTransaction xa = xa2xid.get1(xid);
-    if (xa.isHeuristicallyRollbacked()) {
+    if (xa == null) {
+      throw new XAException(XAException.XAER_NOTA);
+    } else if (xa.isHeuristicallyRollbacked()) {
       throw new XAException(XAException.XA_HEURRB);
+    } else if (xa.isHeuristicallyCommitted()) {
+      throw new XAException(XAException.XA_HEURCOM);
     }
-    for (EnlistableResource er : xa.getEnlistedResources()) {
-      er.getXAResource().commit(xid, onePhase);
+
+    if (onePhase) {
+      try {
+        xa.prepare(xid);
+      } catch (XAException ex) {
+        if (ex.errorCode != XAException.XA_RDONLY) {
+          xa.rollback(xid);
+        }
+        throw ex;
+      }
     }
+
+    try {
+      xa.commit(xid);
+      xa2xid.remove1(xa);
+    } catch (XAException ex) {
+      // We are not allowed to forget this transaction if we completed
+      // heuristically.
+      switch (ex.errorCode) { 
+        case XA_HEURHAZ:
+        case XA_HEURCOM:
+        case XA_HEURRB:
+        case XA_HEURMIX:
+          throw ex;
+        default:
+          xa2xid.remove1(xa);
+          throw ex;
+      }
+    }
   }
 
-  public void end(Xid xid, int flags) {
+  /**
+   * Deactivate a transaction.
+   *
+   * TMSUCCESS: Move to Idle and await call to rollback, prepare, or commit.
+   * TMFAIL: Move to RollbackOnly; await call to rollback.
+   * TMSUSPEND: Move to Idle and await start(TMRESUME)
+   *
+   * In all cases disassociate from current session.
+   */
+  public void end(Xid xid, int flags) throws XAException {
     MulgaraExternalTransaction xa = xa2xid.get1(xid);
+    if (xa == null) {
+      throw new XAException(XAException.XAER_NOTA);
+    }
     switch (flags) {
+      case TMFAIL:
+        xa.setRollbackOnly();
+        break;
       case TMSUCCESS:
-      case TMFAIL:
+        break;
       case TMSUSPEND:
-        factory.disassociate(session, xa);
+        break;
+      default:
+        logger.error("Invalid flag passed to end() : " + flags);
+        throw new XAException(XAException.XAER_INVAL);
     }
+
+    factory.disassociate(session, xa);
   }
 
   public void forget(Xid xid) {
     MulgaraExternalTransaction xa = xa2xid.get1(xid);
     if (xa == null) {
       throw new XAException(XAException.XAER_NOTA);
-    } else {
+    }
+    try {
       if (!xa.isHeuristicRollbackOnly()) {
         xa.abortTransaction("External XA Manager specified 'forget'", new Throwable());
       }
+    } finally {
       xa2xid.remove1(xa);
     }
   }
@@ -92,11 +153,16 @@
   public boolean isSameRM(XAResource xares) {
   }
 
-  public int prepare(Xid xid) {
+
+  public int prepare(Xid xid) throws XAException {
     MulgaraExternalTransaction xa = xa2xid.get1(xid);
-    // forall xa.enlistedResources():
-    //   enlisted.prepare(xid);
-    // return XA_OK
+    if (xa == null) {
+      throw new XAException(XAException.XAER_NOTA);
+    } else if (xa.isRollbacked()) {
+      throw new XAException(XAException.XA_RBROLLBACK);
+    }
+
+    xa.prepare(xid);
   }
 
   /**
@@ -108,10 +174,21 @@
     return new Xid[] {};
   }
 
+
+  public void rollback(Xid xid) throws XAException {
+    MulgaraExternalTransaction xa = xa2xid.get1(xid);
+    if (xa == null) {
+      throw new XAException(XAException.XAER_NOTA);
+    }
+
+    xa.rollback(xid);
+  }
+
+
   public boolean setTransactionTimeout(int seconds) {
   }
 
-  public void start(Xid xid, int flags) {
+  public void start(Xid xid, int flags) throws XAException {
     switch (flags) {
       case TMNOFLAGS:
         if (xa2xid.containsN(xid)) {
@@ -127,7 +204,7 @@
         if (!existing transaction(session)) {
           throw new XAException(XAException.XAER_NOTA);
         } else {
-          // Do stuff.
+          FIXME! // Do stuff.
         }
         break;
       case TMRESUME:
@@ -137,7 +214,10 @@
         } else if (xa.isRollbackOnly()) {
           throw new XAException(XAException.XA_RBROLLBACK);
         } else {
-          factory.associate(session, xa);
+          if (!factory.associate(session, xa)) {
+            // session already associated with a transaction.
+            throw new XAException(XAException.XAER_PROTO);
+          }
         }
         break;
     }




More information about the Mulgara-svn mailing list