User: osh
Date: 01/01/23 21:59:57
Modified: src/main/org/jboss/tm TransactionImpl.java
TransactionManagerService.java TxCapsule.java
TxManager.java XidImpl.java
Log:
General cleanup.
Preparations for 1:1 TransactionImpl:TxCapsule relation.
Catching unexpected throwables from Synchronization ans XAResource.
Revision Changes Path
1.12 +2 -2 jboss/src/main/org/jboss/tm/TransactionImpl.java
Index: TransactionImpl.java
===================================================================
RCS file: /products/cvs/ejboss/jboss/src/main/org/jboss/tm/TransactionImpl.java,v
retrieving revision 1.11
retrieving revision 1.12
diff -u -r1.11 -r1.12
--- TransactionImpl.java 2000/12/07 15:45:17 1.11
+++ TransactionImpl.java 2001/01/24 05:59:56 1.12
@@ -35,7 +35,7 @@
* @author Rickard �berg ([EMAIL PROTECTED])
* @author <a href="mailto:[EMAIL PROTECTED]">Marc Fleury</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Ole Husgaard</a>
- * @version $Revision: 1.11 $
+ * @version $Revision: 1.12 $
*/
public class TransactionImpl
implements Transaction, Serializable
@@ -188,7 +188,7 @@
/**
* Setter for property done.
- * No argument for this mutator; we can only set to false.
+ * No argument for this mutator; we can only set to true.
* This will also clear the txCapsule reference.
*/
synchronized void setDone()
1.6 +3 -3 jboss/src/main/org/jboss/tm/TransactionManagerService.java
Index: TransactionManagerService.java
===================================================================
RCS file:
/products/cvs/ejboss/jboss/src/main/org/jboss/tm/TransactionManagerService.java,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -r1.5 -r1.6
--- TransactionManagerService.java 2000/12/07 15:45:17 1.5
+++ TransactionManagerService.java 2001/01/24 05:59:56 1.6
@@ -36,7 +36,7 @@
*
* @see TxManager
* @author Rickard �berg ([EMAIL PROTECTED])
- * @version $Revision: 1.5 $
+ * @version $Revision: 1.6 $
*/
public class TransactionManagerService
extends ServiceMBeanSupport
@@ -69,8 +69,8 @@
protected void startService()
throws Exception
{
- // Create a new TM
- tm = new TxManager();
+ // Get a reference to the TxManager singleton.
+ tm = TxManager.getInstance();
// Set timeout
tm.setTransactionTimeout(timeout);
1.22 +332 -175 jboss/src/main/org/jboss/tm/TxCapsule.java
Index: TxCapsule.java
===================================================================
RCS file: /products/cvs/ejboss/jboss/src/main/org/jboss/tm/TxCapsule.java,v
retrieving revision 1.21
retrieving revision 1.22
diff -u -r1.21 -r1.22
--- TxCapsule.java 2000/12/07 15:45:17 1.21
+++ TxCapsule.java 2001/01/24 05:59:56 1.22
@@ -45,7 +45,7 @@
* @author <a href="mailto:[EMAIL PROTECTED]">Marc Fleury</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Ole Husgaard</a>
*
- * @version $Revision: 1.21 $
+ * @version $Revision: 1.22 $
*/
class TxCapsule implements TimeoutTarget
{
@@ -53,17 +53,39 @@
// Trace enabled flag
static private final boolean trace = false;
- // Constructor for Xid instances
- private static Constructor xidConstructor = null;
// Code meaning "no heuristics seen", must not be XAException.XA_HEURxxx
- static private final int HEUR_NONE = XAException.XA_RETRY;
+ static private final int HEUR_NONE = XAException.XA_RETRY;
// Attributes ----------------------------------------------------
- private HashMap branchXids;
// Static --------------------------------------------------------
+ /**
+ * Constructor for Xid instances of specified class, or null.
+ */
+ private static Constructor xidConstructor = null;
+
+ /**
+ * Initialize the Xid constructor.
+ */
+ static {
+ String name = System.getProperty("jboss.xa.xidclass",
+ "org.jboss.tm.XidImpl");
+
+ if (!name.equals("org.jboss.tm.XidImpl")) {
+ try {
+ Class cls = Class.forName(name);
+
+ xidConstructor = cls.getConstructor(new Class[]{ Integer.TYPE,
+ byte[].class,
+ byte[].class });
+ } catch (Exception e) {
+ System.out.println("Unable to load Xid class '"+name+"': " + e);
+ }
+ }
+ }
+
// Constructors --------------------------------------------------
/**
@@ -75,24 +97,21 @@
*/
TxCapsule(TxManager tm, long timeout)
{
- this(tm);
+ xid = new XidImpl();
+
+ if (xidConstructor != null) {
+ // Allocate constructor argument array.
+ xidConstructorArgs = new Object[3];
+ // First constructor argument is always the same: Pre-init it.
+ xidConstructorArgs[0] = new Integer(xid.getFormatId());
+ }
+ this.tm = tm;
+
status = Status.STATUS_ACTIVE;
start = System.currentTimeMillis();
this.timeout = TimeoutFactory.createTimeout(start+timeout, this);
- branchXids = new HashMap();
- }
-
- /**
- * Create a new TxCapsule.
- *
- * @param tm The transaction manager for this transaction.
- */
- private TxCapsule(TxManager tm)
- {
- xid = createXid();
- this.tm = tm;
if (trace)
Logger.debug("TxCapsule: Created new instance for tx=" + toString());
@@ -120,7 +139,8 @@
done = false;
resourcesEnded = false;
- xid = createXid();
+ xid = new XidImpl();
+ lastBranchId = 0; // BQIDs start over again in scope of the new GID.
status = Status.STATUS_ACTIVE;
heuristicCode = HEUR_NONE;
@@ -163,7 +183,7 @@
// We are in the second commit phase, and have decided
// to commit, but now we get a timeout and should rollback.
// So we end up with a mixed decision.
- gotHeuristic(null, XAException.XA_HEURMIX);
+ gotHeuristic(-1, XAException.XA_HEURMIX);
status = Status.STATUS_MARKED_ROLLBACK;
return; // commit will fail
@@ -200,11 +220,6 @@
// Package protected ---------------------------------------------
- Xid getXid()
- {
- return xid;
- }
-
/**
* Import a transaction encapsulated here.
*/
@@ -486,14 +501,7 @@
}
try {
- endResource(xaRes, flag);
- if (flag == XAResource.TMSUSPEND)
- resourceState[idx] = RS_SUSPENDED;
- else {
- if (flag == XAResource.TMFAIL)
- status = Status.STATUS_MARKED_ROLLBACK;
- resourceState[idx] = RS_ENDED;
- }
+ endResource(idx, flag);
return true;
} catch(XAException e) {
Logger.warning("XAException: tx=" + toString() + " errorCode=" +
@@ -561,37 +569,30 @@
int idx = findResource(xaRes);
if (idx != -1) {
- if (resourceState[idx] == RS_SUSPENDED) {
- startResource(xaRes, XAResource.TMRESUME,
(Xid)branchXids.get(xaRes));
- resourceState[idx] = RS_ENLISTED;
- return true;
- } else if (resourceState[idx] == RS_ENDED) {
- startResource(xaRes, XAResource.TMJOIN,
(Xid)branchXids.get(xaRes));
- resourceState[idx] = RS_ENLISTED;
- return true;
- } else
+ if (resourceState[idx] == RS_ENLISTED)
return false; // already enlisted
+
+ startResource(idx);
+ return true;
}
/* This optimization hangs the Oracle XAResource
when you perform xaCon.start(Xid, TMJOIN)
+*/
+if (xidConstructor == null) {
for (int i = 0; i < resourceCount; ++i) {
- if (xaRes.isSameRM(resources[i])) {
- Xid parentXid = (Xid)branchXids.get(resources[i]);
- startResource(xaRes, XAResource.TMJOIN, parentXid);
- addResource(xaRes); // Do we still need to do this?
+ if (resourceSameRM[i] == -1 && xaRes.isSameRM(resources[i])) {
+ // The xaRes is new. We register the xaRes with the Xid
+ // that the RM has previously seen from this transaction,
+ // and note that it has the same RM.
+ startResource(addResource(xaRes, resourceXids[i], i));
+
return true;
}
- }
-*/
- // New resource
- // According to the JTA spec we should create a new
- // transaction branch here.
- Xid useXid = xid;
- if(resourceCount > 0) {
- useXid = createXid((Xid)branchXids.get(resources[resourceCount-1]));
}
- startResource(xaRes, XAResource.TMNOFLAGS, useXid);
- addResource(xaRes);
+}
+
+ // New resource and new RM: Create a new transaction branch.
+ startResource(addResource(xaRes, createXidBranch(), -1));
return true;
} catch(XAException e) {
Logger.warning("XAException: tx=" + toString() + " errorCode=" +
@@ -644,10 +645,17 @@
case Status.STATUS_COMMITTED:
throw new IllegalStateException("Already committed.");
case Status.STATUS_MARKED_ROLLBACK:
- //throw new RollbackException("Already marked for rollback");
+// OSH: EntitySynchronizationInterceptor bug is fixed long ago,
+// and since nobody seems to get the warning anymore it should
+// be safe to be JTA-conformant.
+// In case of trouble, try changing "true" below to "false".
+if (true)
+ throw new RollbackException("Already marked for rollback");
+else {
// Workaround for EntitySynchronizationInterceptor bug.
Logger.warning("TxCapsule: Violating JTA by adding synchronization to a
transaction marked for rollback.");
break;
+}
case Status.STATUS_ROLLING_BACK:
throw new RollbackException("Already started rolling back.");
case Status.STATUS_ROLLEDBACK:
@@ -706,7 +714,7 @@
private int syncAllocSize = 1;
/**
- * Count of ynchronizations for this transaction.
+ * Count of synchronizations for this transaction.
*/
private int syncCount = 0;
@@ -721,6 +729,7 @@
*/
private int[] resourceState = new int[1];
+ private final static int RS_NEW = 0; // not yet enlisted
private final static int RS_ENLISTED = 1; // enlisted
private final static int RS_SUSPENDED = 2; // suspended
private final static int RS_ENDED = 3; // not associated
@@ -728,6 +737,18 @@
private final static int RS_VOTE_OK = 5; // voted ok
/**
+ * Index of the first XAResource representing the same resource manager,
+ * or <code>-1</code> if this XAResource is the first XAResource in this
+ * transaction that represents its resource manager.
+ */
+ private int[] resourceSameRM = new int[1];
+
+ /**
+ * A list of the XARessources that have participated in this transaction.
+ */
+ private Xid[] resourceXids = new Xid[1];
+
+ /**
* Size of allocated resource arrays.
*/
private int resourceAllocSize = 1;
@@ -743,10 +764,23 @@
*/
private boolean resourcesEnded = false;
+ /**
+ * The ID of this transaction. This Xid corresponds to the root branch
+ * of the transaction branch tree, and is never passed outside this
+ * package.
+ */
+ private XidImpl xid;
+
+ /**
+ * Xid constructor arguments. Never used unless <code>xidConstructor</code>
+ * is <code>null</code>.
+ */
+ private Object[] xidConstructorArgs = null;
+
/**
- * The ID of this transaction.
+ * Last branch id used.
*/
- private Xid xid; // Transaction id
+ private int lastBranchId = 0;
/**
* Status of this transaction.
@@ -900,15 +934,12 @@
while (locked) {
try {
+ // Wakeup happens when:
+ // - notify() is called from unlock()
+ // - notifyAll is called from instanceDone()
wait();
} catch (InterruptedException ex) {}
- // MF FIXME: don't we need a notify() in this case?
- // we need to release all the thread waiting on this lock
-
- // OSH: notifyAll() is done in instanceDone()
- // and notify() is done in unlock().
-
if (done || myIncarnation != incarnationCount)
throw new IllegalStateException("Transaction has now terminated");
}
@@ -957,18 +988,31 @@
* Return index of XAResource, or <code>-1</code> if not found.
*/
private int findResource(XAResource xaRes) {
- int pos = -1;
- for (int i = 0; i < resourceCount; ++i)
- if(xaRes == resources[i])
- pos = i;
+ // A linear search may seem slow, but please note that
+ // the number of XA resources registered with a transaction
+ // are usually low.
+ for (int idx = 0; idx < resourceCount; ++idx)
+ if (xaRes == resources[idx])
+ return idx;
- return pos;
+ return -1;
}
/**
* Add a resource, expanding tables if needed.
+ *
+ * @param xaRes The new XA resource to add. It is assumed that the
+ * resource is not already in the table of XA resources.
+ * @param branchXid The Xid for the transaction branch that is to
+ * be used for associating with this resource.
+ * @param idxSameRM The index in our XA resource tables of the first
+ * XA resource having the same resource manager as
+ * <code>xaRes</code>, or <code>-1</code> if <code>xaRes</code>
+ * is the first resource seen with this resource manager.
+ *
+ * @return The index of the new resource in our internal tables.
*/
- private void addResource(XAResource xaRes)
+ private int addResource(XAResource xaRes, Xid branchXid, int idxSameRM)
{
if (resourceCount == resourceAllocSize) {
// expand tables
@@ -981,10 +1025,21 @@
int[] stat = new int[resourceAllocSize];
System.arraycopy(resourceState, 0, stat, 0, resourceCount);
resourceState = stat;
+
+ Xid[] xids = new Xid[resourceAllocSize];
+ System.arraycopy(resourceXids, 0, xid, 0, resourceCount);
+ resourceXids = xids;
+
+ int[] sameRM = new int[resourceAllocSize];
+ System.arraycopy(resourceSameRM, 0, sameRM, 0, resourceCount);
+ resourceSameRM = sameRM;
}
resources[resourceCount] = xaRes;
- resourceState[resourceCount] = RS_ENLISTED;
- ++resourceCount;
+ resourceState[resourceCount] = RS_NEW;
+ resourceXids[resourceCount] = branchXid;
+ resourceSameRM[resourceCount] = idxSameRM;
+
+ return resourceCount++;
}
/**
@@ -1004,51 +1059,108 @@
}
/**
- * Call <code>start()</code> on the XAResource.
+ * Call <code>start()</code> on a XAResource and update
+ * internal state information.
* This will release the lock while calling out.
+ *
+ * @param idx The index of the resource in our internal tables.
*/
- private void startResource(XAResource xaRes, int flags, Xid currentBranchXid)
+ private void startResource(int idx)
throws XAException
{
- if(trace)
- Logger.debug("TxCapsule.startResource(" +
XidImpl.toString(currentBranchXid) +
- ") entered: " + xaRes.toString() +
- " flags=" + flags);
+ int flags = XAResource.TMJOIN;
+
+ if (resourceSameRM[idx] == -1) {
+ switch (resourceState[idx]) {
+ case RS_NEW:
+ flags = XAResource.TMNOFLAGS;
+ break;
+ case RS_SUSPENDED:
+ flags = XAResource.TMRESUME;
+ break;
+ }
+ }
+
+ if (trace)
+ Logger.debug("TxCapsule.startResource(" +
+ XidImpl.toString(resourceXids[idx]) +
+ ") entered: " + resources[idx].toString() +
+ " flags=" + flags);
+
unlock();
// OSH FIXME: resourceState could be incorrect during this callout.
try {
- xaRes.start(currentBranchXid, flags);
- branchXids.put(xaRes, currentBranchXid);
+ try {
+ resources[idx].start(resourceXids[idx], flags);
+ } catch(XAException e) {
+ throw e;
+ } catch (Throwable t) {
+ if (trace)
+ Logger.exception(t);
+ status = Status.STATUS_MARKED_ROLLBACK;
+ return;
+ }
+
+ // Now the XA resource is associated with a transaction.
+ resourceState[idx] = RS_ENLISTED;
} finally {
lock();
- if(trace)
- Logger.debug("TxCapsule.startResource(" +
XidImpl.toString(currentBranchXid) +
- ") leaving: " + xaRes.toString() +
+ if (trace)
+ Logger.debug("TxCapsule.startResource(" +
+ XidImpl.toString(resourceXids[idx]) +
+ ") leaving: " + resources[idx].toString() +
" flags=" + flags);
}
}
/**
- * Call <code>end()</code> on the XAResource.
+ * Call <code>end()</code> on the XAResource and update
+ * internal state information.
* This will release the lock while calling out.
+ *
+ * @param idx The index of the resource in our internal tables.
+ * @param flag The flag argument for the end() call.
*/
- private void endResource(XAResource xaRes, int flag)
+ private void endResource(int idx, int flag)
throws XAException
{
- Xid useXid = (Xid)branchXids.get(xaRes);
- if(trace)
- Logger.debug("TxCapsule.endResource(" + XidImpl.toString(useXid) +
- ") entered: " + xaRes.toString() +
+ if (trace)
+ Logger.debug("TxCapsule.endResource(" +
+ XidImpl.toString(resourceXids[idx]) +
+ ") entered: " + resources[idx].toString() +
" flag=" + flag);
unlock();
// OSH FIXME: resourceState could be incorrect during this callout.
try {
- xaRes.end(useXid, flag);
+ try {
+ resources[idx].end(resourceXids[idx], flag);
+ } catch(XAException e) {
+ throw e;
+ } catch (Throwable t) {
+ if (trace)
+ Logger.exception(t);
+ status = Status.STATUS_MARKED_ROLLBACK;
+ // Resource may or may not be ended after illegal exception.
+ // We just assume it ended.
+ resourceState[idx] = RS_ENDED;
+ return;
+ }
+
+
+ // Update our internal state information
+ if (flag == XAResource.TMSUSPEND)
+ resourceState[idx] = RS_SUSPENDED;
+ else {
+ if (flag == XAResource.TMFAIL)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ resourceState[idx] = RS_ENDED;
+ }
} finally {
lock();
- if(trace)
- Logger.debug("TxCapsule.endResource(" + XidImpl.toString(useXid) +
- ") leaving: " + xaRes.toString() +
+ if (trace)
+ Logger.debug("TxCapsule.endResource(" +
+ XidImpl.toString(resourceXids[idx]) +
+ ") leaving: " + resources[idx].toString() +
" flag=" + flag);
}
}
@@ -1058,9 +1170,9 @@
*/
private void endResources()
{
- for (int i = 0; i < resourceCount; i++) {
+ for (int idx = 0; idx < resourceCount; idx++) {
try {
- if (resourceState[i] == RS_SUSPENDED) {
+ if (resourceState[idx] == RS_SUSPENDED) {
// This is mad, but JTA 1.0.1 spec says on page 41:
// "If TMSUSPEND is specified in flags, the transaction
// branch is temporarily suspended in incomplete state.
@@ -1073,13 +1185,13 @@
// a suspended state.
// But the Minerva XA pool does not like that we call end()
// two times in a row, so we resume before ending.
- startResource(resources[i], XAResource.TMRESUME,
(Xid)branchXids.get(resources[i]));
- resourceState[i] = RS_ENLISTED;
+ startResource(idx);
}
- if (resourceState[i] == RS_ENLISTED) {
- Logger.debug("endresources("+i+"): state="+resourceState[i]);
- endResource(resources[i], XAResource.TMSUCCESS);
- resourceState[i] = RS_ENDED;
+ if (resourceState[idx] == RS_ENLISTED) {
+ if (trace)
+ Logger.debug("endresources(" + idx + "): state=" +
+ resourceState[idx]);
+ endResource(idx, XAResource.TMSUCCESS);
}
} catch(XAException e) {
Logger.warning("XAException: tx=" + toString() + " errorCode=" +
@@ -1100,8 +1212,16 @@
{
unlock();
try {
- for (int i = 0; i < syncCount; i++)
- sync[i].beforeCompletion();
+ for (int i = 0; i < syncCount; i++) {
+ try {
+ sync[i].beforeCompletion();
+ } catch (Throwable t) {
+ if (trace)
+ Logger.exception(t);
+ status = Status.STATUS_MARKED_ROLLBACK;
+ break;
+ }
+ }
} finally {
lock();
}
@@ -1116,8 +1236,14 @@
// Assert: Status indicates: Too late to add new synchronizations.
unlock();
try {
- for (int i = 0; i < syncCount; i++)
- sync[i].afterCompletion(status);
+ for (int i = 0; i < syncCount; i++) {
+ try {
+ sync[i].afterCompletion(status);
+ } catch (Throwable t) {
+ if (trace)
+ Logger.exception(t);
+ }
+ }
} finally {
lock();
}
@@ -1125,11 +1251,18 @@
/**
* We got another heuristic.
+ *
* Promote <code>heuristicCode</code> if needed and tell
* the resource to forget the heuristic.
* This will release the lock while calling out.
+ *
+ * @param resIdx The index of the XA resource that got a
+ * heurictic in our internal tables, or <code>-1</code>
+ * if the heuristic came from here.
+ * @param code The heuristic code, one of
+ * <code>XAException.XA_HEURxxx</code>.
*/
- private void gotHeuristic(XAResource resource, int code)
+ private void gotHeuristic(int resIdx, int code)
{
switch (code) {
case XAException.XA_HEURMIX:
@@ -1160,10 +1293,10 @@
throw new IllegalArgumentException();
}
- if (resource != null) {
+ if (resIdx != -1) {
try {
unlock();
- resource.forget((Xid)branchXids.get(resource));
+ resources[resIdx].forget(resourceXids[resIdx]);
} catch (XAException e) {
Logger.warning("XAException at forget(): errorCode=" +
getStringXAErrorCode(e.errorCode));
@@ -1230,12 +1363,26 @@
}
// Clear content of collections.
+ for (int i = 0; i < syncCount; ++i)
+ sync[i] = null; // release for GC
syncCount = 0;
+
+ for (int i = 0; i < transactionCount; ++i)
+ transactions[i] = null; // release for GC
transactionCount = 0;
+
+ for (int i = 0; i < resourceCount; ++i) {
+ resources[i] = null; // release for GC
+ resourceXids[i] = null; // release for GC
+ }
resourceCount = 0;
+ // If using a special class, second constructor argument is now useless.
+ if (xidConstructor != null)
+ xidConstructorArgs[1] = null; // This now needs initializing
+
// This instance is now ready for reuse (when we release the lock).
- tm.releaseTxCapsule(this);
+ tm.releaseTxCapsule(this, xid);
}
/**
@@ -1260,6 +1407,9 @@
if (status != Status.STATUS_PREPARING)
return false;
+ if (resourceSameRM[i] != -1)
+ continue; // This RM already prepared.
+
XAResource resource = resources[i];
try {
@@ -1267,7 +1417,7 @@
unlock();
try {
- vote = resources[i].prepare((Xid)branchXids.get(resources[i]));
+ vote = resources[i].prepare(resourceXids[i]);
} finally {
lock();
}
@@ -1289,12 +1439,12 @@
case XAException.XA_HEURCOM:
// Heuristic commit is not that bad when preparing.
// But it means trouble if we have to rollback.
- gotHeuristic(resources[i], e.errorCode);
+ gotHeuristic(i, e.errorCode);
break;
case XAException.XA_HEURRB:
case XAException.XA_HEURMIX:
case XAException.XA_HEURHAZ:
- gotHeuristic(resources[i], e.errorCode);
+ gotHeuristic(i, e.errorCode);
if (status == Status.STATUS_PREPARING)
status = Status.STATUS_MARKED_ROLLBACK;
break;
@@ -1306,6 +1456,11 @@
status = Status.STATUS_MARKED_ROLLBACK;
break;
}
+ } catch (Throwable t) {
+ if (trace)
+ Logger.exception(t);
+ if (status == Status.STATUS_PREPARING)
+ status = Status.STATUS_MARKED_ROLLBACK;
}
}
@@ -1324,11 +1479,15 @@
status = Status.STATUS_COMMITTING;
for (int i = 0; i < resourceCount; i++) {
- if(trace) {
- Logger.debug("TxCapsule.commitResources():
resourceStates["+i+"]="+resourceState[i]);
- }
+ if (trace)
+ Logger.debug("TxCapsule.commitResources(): " +
+ "resourceStates["+i+"]="+resourceState[i]);
+
if (!onePhase && resourceState[i] != RS_VOTE_OK)
- continue;
+ continue; // Voted read-only at prepare phase.
+
+ if (resourceSameRM[i] != -1)
+ continue; // This RM already committed.
// Abort commit on state change.
if (status != Status.STATUS_COMMITTING)
@@ -1337,7 +1496,7 @@
try {
unlock();
try {
- resources[i].commit((Xid)branchXids.get(resources[i]), onePhase);
+ resources[i].commit(resourceXids[i], onePhase);
} finally {
lock();
}
@@ -1347,7 +1506,7 @@
case XAException.XA_HEURCOM:
case XAException.XA_HEURMIX:
case XAException.XA_HEURHAZ:
- gotHeuristic(resources[i], e.errorCode);
+ gotHeuristic(i, e.errorCode);
break;
default:
Logger.warning("XAException: tx=" + toString() + " errorCode=" +
@@ -1356,10 +1515,15 @@
break;
}
try {
- resources[i].forget((Xid)branchXids.get(resources[i]));
- } catch(XAException forgetEx) {}
- } finally {
- branchXids.remove(resources[i]);
+ // OSH: Why this?
+ // Heuristics only exist if we go one of the heuristic
+ // error codes, and for these forget() is called from
+ // gotHeuristic().
+ resources[i].forget(resourceXids[i]);
+ } catch (XAException forgetEx) {}
+ } catch (Throwable t) {
+ if (trace)
+ Logger.exception(t);
}
}
@@ -1382,7 +1546,7 @@
try {
unlock();
try {
- resources[i].rollback((Xid)branchXids.get(resources[i]));
+ resources[i].rollback(resourceXids[i]);
} finally {
lock();
}
@@ -1390,12 +1554,12 @@
switch (e.errorCode) {
case XAException.XA_HEURRB:
// Heuristic rollback is not that bad when rolling back.
- gotHeuristic(resources[i], e.errorCode);
+ gotHeuristic(i, e.errorCode);
break;
case XAException.XA_HEURCOM:
case XAException.XA_HEURMIX:
case XAException.XA_HEURHAZ:
- gotHeuristic(resources[i], e.errorCode);
+ gotHeuristic(i, e.errorCode);
break;
default:
Logger.warning("XAException: tx=" + toString() + " errorCode=" +
@@ -1404,61 +1568,54 @@
break;
}
try {
- resources[i].forget((Xid)branchXids.get(resources[i]));
- } catch(XAException forgetEx) {}
- } finally {
- branchXids.remove(resources[i]);
+ // OSH: Why this?
+ // Heuristics only exist if we go one of the heuristic
+ // error codes, and for these forget() is called from
+ // gotHeuristic().
+ resources[i].forget(resourceXids[i]);
+ } catch (XAException forgetEx) {}
+ } catch (Throwable t) {
+ if (trace)
+ Logger.exception(t);
}
}
status = Status.STATUS_ROLLEDBACK;
}
- private Xid createXid() {
- if (xidConstructor == null) {
- String name = System.getProperty("jboss.xa.xidclass",
"org.jboss.tm.XidImpl");
-
- try {
- Class cls = Class.forName(name);
- xidConstructor = cls.getConstructor(new Class[]{Integer.TYPE,
byte[].class, byte[].class});
- } catch (Exception e) {
- System.out.println("Unable to load Xid class '"+name+"'");
- }
- }
-
- try {
- Object xid = xidConstructor.newInstance(new Object[]{new
Integer(XidImpl.getJbossFormatId()),
-
XidImpl.getGlobalIdString(XidImpl.getNextId()),
-
XidImpl.noBranchQualifier});
- return (Xid)xid;
- } catch (Exception e) {
- System.out.println("Unable to create an Xid (reverting to default impl):
"+e);
- return new XidImpl(XidImpl.getJbossFormatId(),
XidImpl.getGlobalIdString(XidImpl.getNextId()), XidImpl.noBranchQualifier);
- }
- }
+ /**
+ * Create an Xid representing a new branch of this transaction.
+ */
+ private Xid createXidBranch() {
+ int branchId = ++lastBranchId;
- private Xid createXid(Xid lastBranch) {
- if (xidConstructor == null) {
- String name = System.getProperty("jboss.xa.xidclass",
"org.jboss.tm.XidImpl");
-
- try {
- Class cls = Class.forName(name);
- xidConstructor = cls.getConstructor(new Class[]{Integer.TYPE,
byte[].class, byte[].class});
- } catch (Exception e) {
- System.out.println("Unable to load Xid class '"+name+"'");
- }
- }
-
- try {
- Object xid = xidConstructor.newInstance(new Object[]{new
Integer(XidImpl.getJbossFormatId()),
-
XidImpl.getGlobalIdString(XidImpl.getNextId()),
-
XidImpl.getNextBranchQualifier(lastBranch.getBranchQualifier())});
- return (Xid)xid;
- } catch (Exception e) {
- System.out.println("Unable to create an Xid (reverting to default impl):
"+e);
- return new XidImpl(XidImpl.getJbossFormatId(),
XidImpl.getGlobalIdString(XidImpl.getNextId()), XidImpl.noBranchQualifier);
- }
- }
+ if (xidConstructor == null)
+ return new XidImpl(xid, branchId);
+ else {
+ try {
+ if (xidConstructorArgs[1] == null) {
+ // First branching of this global transaction id.
+ // Oracle XA driver needs the full length GID.
+ byte[] gidShort = xid.getGlobalTransactionId();
+ byte[] gid = new byte[Xid.MAXGTRIDSIZE];
+ System.arraycopy(gidShort, 0, gid, 0, gidShort.length);
+ xidConstructorArgs[1] = gid;
+ }
+
+ // Oracle XA driver needs the full length BQID.
+ byte[] bqidShort = Integer.toString(branchId).getBytes();
+ byte[] bqid = new byte[Xid.MAXBQUALSIZE];
+ System.arraycopy(bqidShort, 0, bqid, 0, bqidShort.length);
+ xidConstructorArgs[2] = bqid;
+
+ return (Xid)xidConstructor.newInstance(xidConstructorArgs);
+ } catch (Exception e) {
+ System.out.println("Unable to create an Xid " +
+ "(reverting to default impl): " + e);
+ return new XidImpl(xid, branchId);
+ }
+ }
+ }
// Inner classes -------------------------------------------------
}
1.26 +94 -28 jboss/src/main/org/jboss/tm/TxManager.java
Index: TxManager.java
===================================================================
RCS file: /products/cvs/ejboss/jboss/src/main/org/jboss/tm/TxManager.java,v
retrieving revision 1.25
retrieving revision 1.26
diff -u -r1.25 -r1.26
--- TxManager.java 2000/12/07 15:45:17 1.25
+++ TxManager.java 2001/01/24 05:59:56 1.26
@@ -38,7 +38,7 @@
* @author Rickard �berg ([EMAIL PROTECTED])
* @author <a href="mailto:[EMAIL PROTECTED]">Marc Fleury</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Ole Husgaard</a>
- * @version $Revision: 1.25 $
+ * @version $Revision: 1.26 $
*/
public class TxManager
implements TransactionManager
@@ -54,9 +54,30 @@
long timeOut = 5*60*1000;
// Static --------------------------------------------------------
-
+
+ /**
+ * The singleton instance.
+ */
+ private static TxManager singleton = new TxManager();
+
+ /**
+ * Get a reference to the singleton instance.
+ */
+ static TxManager getInstance()
+ {
+ return singleton;
+ }
+
// Constructors --------------------------------------------------
-
+
+ /**
+ * Private constructor for singleton. Use getInstance() to obtain
+ * a reference to the singleton.
+ */
+ private TxManager()
+ {
+ }
+
// Public --------------------------------------------------------
public void begin()
@@ -225,10 +246,54 @@
public void associateThread(Transaction transaction)
{
+ //
// If the transaction has travelled, we have to import it.
+ //
+ // This implicit import will go away at some point in the
+ // future and be replaced by an explicit import by calling
+ // importTPC().
+ // That will make it possible to only propagate XidImpl over the
+ // wire so that we no longer have to handle multible Transaction
+ // frontends for each transaction.
+ //
if (transaction != null && transaction instanceof TransactionImpl) {
TransactionImpl tx = (TransactionImpl)transaction;
+ if (tx.importNeeded())
+ transaction = importTPC(transaction);
+ }
+
+ // Associate with the thread
+ threadTx.set(transaction);
+ }
+
+ /**
+ * Import a transaction propagation context into this TM.
+ * The TPC is loosely typed, as we may (at a later time) want to
+ * import TPCs that come from other transaction monitors without
+ * offloading the conversion to the client.
+ *
+ * @param tpc The transaction propagation context that we want to
+ * import into this TM. Currently this is an instance
+ * of TransactionImpl. Later this will be changed to an
+ * instance of XidImpl. And at some later time this may
+ * even be an instance of a transaction propagation context
+ * from another kind of transaction monitor like
+ * org.omg.CosTransactions.PropagationContext.
+ *
+ * @return A transaction representing this transaction propagation
+ * context, or null if this TPC cannot be imported.
+ */
+ public Transaction importTPC(Object tpc)
+ {
+ if (tpc instanceof TransactionImpl) {
+ // TODO: Change to just return the transaction without importing.
+ // A raw TransactionImpl will only be used for optimized local calls,
+ // where the import will not be needed.
+ // But that will have to wait until remote calls use XidImpl instead,
+ // otherwise we cannot distinguish cases.
+ TransactionImpl tx = (TransactionImpl)tpc;
+
if (tx.importNeeded()) {
synchronized(tx) {
// Recheck with synchronization.
@@ -242,38 +307,23 @@
}
}
}
- }
-
- // Associate with the thread
- threadTx.set(transaction);
+ return tx;
+ } else if (tpc instanceof XidImpl)
+ Logger.warning("XidImpl import not yet implemented.");
+ else
+ Logger.warning("Cannot import transaction propagation context: " +
+ tpc.toString());
+ return null;
}
-
-
+
// Package protected ---------------------------------------------
- // There has got to be something better :)
- static TxManager getTransactionManager()
- {
- try {
- javax.naming.InitialContext context = new javax.naming.InitialContext();
-
- //One tx in naming
- Logger.log("Calling get manager from JNDI");
- TxManager manager = (TxManager) context.lookup("java:/TransactionManager");
- Logger.log("Returning TM " + manager.hashCode());
-
- return manager;
- } catch (Exception e ) {
- return null;
- }
- }
-
/**
* Release the given txCapsule for reuse.
*/
- void releaseTxCapsule(TxCapsule txCapsule)
+ void releaseTxCapsule(TxCapsule txCapsule, XidImpl xid)
{
- activeCapsules.remove(txCapsule.getXid());
+ activeCapsules.remove(xid);
SoftReference ref = new SoftReference(txCapsule);
synchronized (inactiveCapsules) {
@@ -281,6 +331,22 @@
}
}
+ /**
+ * Return an Xid that identifies the transaction associated
+ * with the invoking thread, or <code>null</code> if the invoking
+ * thread is not associated with a transaction.
+ * This is used for JRMP transaction context propagation.
+ */
+ Xid getXid()
+ {
+ Transaction current = (Transaction)threadTx.get();
+
+ // If no transaction or unknown transaction class, return null.
+ if (current == null || !(current instanceof TransactionImpl))
+ return null;
+
+ return ((TransactionImpl)current).xid;
+ }
// Protected -----------------------------------------------------
1.9 +75 -95 jboss/src/main/org/jboss/tm/XidImpl.java
Index: XidImpl.java
===================================================================
RCS file: /products/cvs/ejboss/jboss/src/main/org/jboss/tm/XidImpl.java,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -r1.8 -r1.9
--- XidImpl.java 2001/01/13 18:24:15 1.8
+++ XidImpl.java 2001/01/24 05:59:56 1.9
@@ -14,61 +14,51 @@
/**
* This object encapsulates the ID of a transaction.
+ * This implementation is immutable and always serializable at runtime.
*
* @see TransactionImpl
* @author Rickard �berg ([EMAIL PROTECTED])
* @author <a href="mailto:[EMAIL PROTECTED]">Ole Husgaard</a>
- * @version $Revision: 1.8 $
+ * @version $Revision: 1.9 $
*/
class XidImpl
implements Xid, java.io.Serializable
{
// Constants -----------------------------------------------------
+ public static final int JBOSS_FORMAT_ID = 0x0101;
+
// Attributes ----------------------------------------------------
/**
* Hash code of this instance. This is really a sequence number.
*/
- int hash;
+ private int hash;
/**
* Global transaction id of this instance.
+ * The coding of this class depends on the fact that this variable is
+ * initialized in the constructor and never modified. References to
+ * this array are never given away, instead a clone is delivered.
*/
- byte[] globalId;
+ private byte[] globalId;
/**
* Branch qualifier of this instance.
* This identifies the branch of a transaction.
*/
- byte[] branchId;
+ private byte[] branchId;
// Static --------------------------------------------------------
- /**
- * A cache for the <code>getHostName()</code> function.
- */
- private static String hostName;
-
/**
- * Return the host name of this host, followed by a slash.
+ * The host name of this host, followed by a slash.
*
* This is used for building globally unique transaction identifiers.
* It would be safer to use the IP address, but a host name is better
* for humans to read and will do for now.
*/
- static private String getHostName()
- {
- if (hostName == null) {
- try {
- hostName = InetAddress.getLocalHost().getHostName() + "/";
- } catch (UnknownHostException e) {
- hostName = "localhost/";
- }
- }
-
- return hostName;
- }
+ private static String hostName;
/**
* The next transaction id to use on this host.
@@ -78,40 +68,71 @@
/**
* Return a new unique transaction id to use on this host.
*/
- static synchronized int getNextId()
+ static private synchronized int getNextId()
{
return nextId++;
}
/**
* Singleton for no branch qualifier.
+ */
+ static private byte[] noBranchQualifier = new byte[0];
+
+ /**
+ * Initialize the <code>hostName</code> class variable.
+ */
+ static {
+ try {
+ hostName = InetAddress.getLocalHost().getHostName() + "/";
+ // Ensure room for 14 digits of serial no.
+ if (hostName.length() > MAXGTRIDSIZE - 15)
+ hostName = hostName.substring(0, MAXGTRIDSIZE - 15);
+ hostName = hostName + "/";
+ } catch (UnknownHostException e) {
+ hostName = "localhost/";
+ }
+ }
+
+ /**
+ * Return a string that describes any Xid instance.
*/
- static byte[] noBranchQualifier = new byte[MAXBQUALSIZE];
- static {
- for(int i=0; i<noBranchQualifier.length; i++)
- noBranchQualifier[i] = 0;
- }
+ static String toString(Xid id) {
+ if (id == null)
+ return "[NULL Xid]";
+
+ String s = id.getClass().getName();
+ s = s.substring(s.lastIndexOf('.') + 1);
+ s = s + " [FormatId=" + id.getFormatId() +
+ ", GlobalId=" + new String(id.getGlobalTransactionId()).trim() +
+ ", BranchQual=" + new String(id.getBranchQualifier()).trim()+"]";
+ return s;
+ }
+
// Constructors --------------------------------------------------
/**
- * Create a new unique branch qualifier.
+ * Create a new instance.
*/
- public XidImpl(int formatId, byte[] globalId, byte[] branchId)
+ public XidImpl()
{
- this.hash = getNextId();
- this.globalId = globalId;
- this.branchId = branchId;
+ hash = getNextId();
+ globalId = (hostName + Integer.toString(hash)).getBytes();
+ branchId = noBranchQualifier;
}
/**
- * Create a new branch of an existing global transaction id.
+ * Create a new branch of an existing global transaction ID.
+ *
+ * @param xid The transaction ID to create a new branch of.
+ * @param branchId The ID of the new branch.
+ *
*/
- public XidImpl(XidImpl xid, byte[] branchId)
+ public XidImpl(XidImpl xid, int branchId)
{
- hash = xid.hash;
- this.globalId = xid.globalId;
- this.branchId = branchId;
+ this.hash = xid.hash;
+ this.globalId = xid.globalId; // reuse array instance, we never modify.
+ this.branchId = Integer.toString(branchId).getBytes();
}
// Public --------------------------------------------------------
@@ -144,11 +165,6 @@
* how the global id and branch qualifier should be interpreted.
*/
public int getFormatId() {
- return getJbossFormatId();
- }
-
- public static int getJbossFormatId()
- {
// The id we return here should be different from all other transaction
// implementations.
// Known IDs are:
@@ -156,31 +172,38 @@
// 0: OSI TP (javadoc states OSI CCR, but that is a bit misleading
// as OSI CCR doesn't even have ACID properties. But OSI CCR and
// OSI TP do have the same id format.)
- // 0xBB14: Used by JONAS
- // 0xBB20: Used by JONAS
- return 0x0101;
+ // 1: Was used by early betas of jBoss.
+ // 0x0101: The JBOSS_FORMAT_ID we use here.
+ // 0xBB14: Used by JONAS.
+ // 0xBB20: Used by JONAS.
+
+ return JBOSS_FORMAT_ID;
}
/**
* Compare for equality.
*
- * This checks the format id and the global transaction ID, but
- * ignores the branch qualifier.
+ * Instances are considered equal if they are both instances of XidImpl,
+ * and if they have the same global transaction id and transaction
+ * branch qualifier.
*/
public boolean equals(Object obj)
{
- // OSH: Should we also compare the branch ID ?
-
- if (obj != null && obj instanceof XidImpl) {
+ if (obj instanceof XidImpl) {
XidImpl other = (XidImpl)obj;
- if (globalId.length != other.globalId.length)
+ if (globalId.length != other.globalId.length ||
+ branchId.length != other.branchId.length)
return false;
for (int i = 0; i < globalId.length; ++i)
if (globalId[i] != other.globalId[i])
return false;
+ for (int i = 0; i < branchId.length; ++i)
+ if (branchId[i] != other.branchId[i])
+ return false;
+
return true;
}
return false;
@@ -201,49 +224,6 @@
// Protected -----------------------------------------------------
// Private -------------------------------------------------------
-
- static byte[] getGlobalIdString(int hash)
- {
- String value;
- String s = getHostName();
- String h = Integer.toString(hash);
- if(s.length() + h.length() > 64) {
- value = s.substring(0, 64-h.length())+h;
- } else value = s+h;
- byte b[] = new byte[MAXGTRIDSIZE];
- System.arraycopy(value.getBytes(), 0, b, 0, value.length());
- return b;
- }
-
- static byte[] getNextBranchQualifier() {
- int id = getNextId();
- String ids = Integer.toString(id);
- byte[] source = ids.getBytes();
- byte[] result = new byte[MAXBQUALSIZE];
- System.arraycopy(source, 0, result, 0, source.length);
- return result;
- }
-
- static byte[] getNextBranchQualifier(byte[] previous) {
- String ids = new String(previous).trim();
- if(ids.length() == 0)
- ids = "1";
- else
- ids = Integer.toString(Integer.parseInt(ids)+1);
- byte[] source = ids.getBytes();
- byte[] result = new byte[MAXBQUALSIZE];
- System.arraycopy(source, 0, result, 0, source.length);
- return result;
- }
-
- static String toString(Xid id) {
- if(id == null)
- return "[NULL XID!!]";
- String s = id.getClass().getName();
- s = s.substring(s.lastIndexOf('.')+1);
- s = s+" [ID="+id.getFormatId()+", Global="+new
String(id.getGlobalTransactionId()).trim()+", Branch="+new
String(id.getBranchQualifier()).trim()+"]";
- return s;
- }
// Inner classes -------------------------------------------------
}