[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