001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.tm;
023:
024: import java.util.Collections;
025: import java.util.HashMap;
026: import java.util.Map;
027:
028: import javax.resource.spi.work.Work;
029: import javax.resource.spi.work.WorkCompletedException;
030: import javax.resource.spi.work.WorkException;
031: import javax.transaction.HeuristicMixedException;
032: import javax.transaction.HeuristicRollbackException;
033: import javax.transaction.InvalidTransactionException;
034: import javax.transaction.NotSupportedException;
035: import javax.transaction.RollbackException;
036: import javax.transaction.Status;
037: import javax.transaction.SystemException;
038: import javax.transaction.Transaction;
039: import javax.transaction.TransactionManager;
040: import javax.transaction.xa.XAException;
041: import javax.transaction.xa.Xid;
042:
043: import org.jboss.logging.Logger;
044: import org.jboss.tm.integrity.TransactionIntegrity;
045: import org.jboss.util.UnexpectedThrowable;
046: import org.jboss.util.UnreachableStatementException;
047:
048: /**
049: * Our TransactionManager implementation.
050: *
051: * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Öberg</a>
052: * @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a>
053: * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
054: * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
055: * @author <a href="reverbel@ime.usp.br">Francisco Reverbel</a>
056: * @author <a href="adrian@jboss.com">Adrian Brock</a>
057: * @author <a href="dimitris@jboss.org">Dimitris Andreadis</a>
058: * @version $Revision: 57208 $
059: * @deprecated Do not reference directly, use org.jboss.tm.TransactionManagerLocator
060: */
061: public class TxManager implements TransactionManager,
062: TransactionPropagationContextImporter,
063: TransactionPropagationContextFactory, TransactionLocalDelegate,
064: TransactionTimeoutConfiguration, JBossXATerminator {
065: // Constants -----------------------------------------------------
066:
067: // Attributes ----------------------------------------------------
068:
069: /** True if the TxManager should keep a map from GlobalIds to transactions. */
070: private boolean globalIdsEnabled = false;
071:
072: /** Whether to interrupt threads at transaction timeout */
073: private boolean interruptThreads = false;
074:
075: /** Instance logger. */
076: private Logger log = Logger.getLogger(this .getClass());
077:
078: /** True if trace messages should be logged. */
079: private boolean trace = log.isTraceEnabled();
080:
081: /**
082: * Default timeout in milliseconds.
083: * Must be >= 1000!
084: */
085: private long timeOut = 5 * 60 * 1000;
086:
087: // The following two fields are ints (not longs) because
088: // volatile 64Bit types are broken (i.e. access is not atomic) in most VMs, and we
089: // don't want to lock just for a statistic. Additionaly,
090: // it will take several years on a highly loaded system to
091: // exceed the int range. Note that we might loose an
092: // increment every now and then, since the ++ operation is
093: // not atomic on volatile data types.
094: /** A count of the transactions that have been committed */
095: private volatile int commitCount;
096: /** A count of the transactions that have been rolled back */
097: private volatile int rollbackCount;
098:
099: /** The transaction integrity policy */
100: private TransactionIntegrity integrity;
101:
102: // Static --------------------------------------------------------
103:
104: /**
105: * The singleton instance.
106: */
107: private static TxManager singleton = new TxManager();
108:
109: /**
110: * Get a reference to the singleton instance.
111: */
112: public static TxManager getInstance() {
113: return singleton;
114: }
115:
116: // Constructors --------------------------------------------------
117:
118: /**
119: * Private constructor for singleton. Use getInstance() to obtain
120: * a reference to the singleton.
121: */
122: private TxManager() {
123: //make sure TxCapsule can be used
124: TransactionImpl.defaultXidFactory();
125: }
126:
127: // Public --------------------------------------------------------
128:
129: /**
130: * Setter for attribute <code>globalIdsEnabled</code>.
131: */
132: public void setGlobalIdsEnabled(boolean newValue) {
133: XidImpl.setTrulyGlobalIdsEnabled(newValue);
134: globalIdsEnabled = newValue;
135: }
136:
137: /**
138: * Getter for attribute <code>globalIdsEnabled</code>.
139: */
140: public boolean getGlobalIdsEnabled() {
141: return globalIdsEnabled;
142: }
143:
144: /**
145: * Enable/disable thread interruption at transaction timeout.
146: *
147: * @param interruptThreads pass true to interrupt threads, false otherwise
148: */
149: public void setInterruptThreads(boolean interruptThreads) {
150: this .interruptThreads = interruptThreads;
151: }
152:
153: /**
154: * Is thread interruption enabled at transaction timeout
155: *
156: * @return true for interrupt threads, false otherwise
157: */
158: public boolean isInterruptThreads() {
159: return interruptThreads;
160: }
161:
162: /**
163: * Set the transaction integrity policy
164: *
165: * @param integrity the transaction integrity policy
166: */
167: public void setTransactionIntegrity(TransactionIntegrity integrity) {
168: this .integrity = integrity;
169: }
170:
171: /**
172: * Get the transaction integrity policy
173: *
174: * @return the transaction integrity policy
175: */
176: public TransactionIntegrity getTransactionIntegrity() {
177: return integrity;
178: }
179:
180: /**
181: * Begin a new transaction.
182: * The new transaction will be associated with the calling thread.
183: */
184: public void begin() throws NotSupportedException, SystemException {
185: trace = log.isTraceEnabled();
186:
187: ThreadInfo ti = getThreadInfo();
188: TransactionImpl current = ti.tx;
189:
190: if (current != null) {
191: if (current.isDone())
192: disassociateThread(ti);
193: else
194: throw new NotSupportedException(
195: "Transaction already active, cannot nest transactions.");
196: }
197:
198: long timeout = (ti.timeout == 0) ? timeOut : ti.timeout;
199: TransactionImpl tx = new TransactionImpl(timeout);
200: associateThread(ti, tx);
201: localIdTx.put(tx.getLocalId(), tx);
202: if (globalIdsEnabled)
203: globalIdTx.put(tx.getGlobalId(), tx);
204:
205: if (trace)
206: log.trace("began tx: " + tx);
207: }
208:
209: /**
210: * Commit the transaction associated with the currently running thread.
211: */
212: public void commit() throws RollbackException,
213: HeuristicMixedException, HeuristicRollbackException,
214: SecurityException, IllegalStateException, SystemException {
215: ThreadInfo ti = getThreadInfo();
216: TransactionImpl current = ti.tx;
217:
218: if (current != null) {
219: current.commit();
220: disassociateThread(ti);
221: if (trace)
222: log.trace("commited tx: " + current);
223: } else
224: throw new IllegalStateException("No transaction.");
225: }
226:
227: /**
228: * Return the status of the transaction associated with the currently
229: * running thread, or <code>Status.STATUS_NO_TRANSACTION</code> if no
230: * active transaction is currently associated.
231: */
232: public int getStatus() throws SystemException {
233: ThreadInfo ti = getThreadInfo();
234: TransactionImpl current = ti.tx;
235:
236: if (current != null) {
237: if (current.isDone())
238: disassociateThread(ti);
239: else
240: return current.getStatus();
241: }
242: return Status.STATUS_NO_TRANSACTION;
243: }
244:
245: /**
246: * Return the transaction currently associated with the invoking thread,
247: * or <code>null</code> if no active transaction is currently associated.
248: */
249: public Transaction getTransaction() throws SystemException {
250: ThreadInfo ti = getThreadInfo();
251: TransactionImpl current = ti.tx;
252:
253: if (current != null && current.isDone()) {
254: current = null;
255: disassociateThread(ti);
256: }
257:
258: return current;
259: }
260:
261: /**
262: * Resume a transaction.
263: *
264: * Note: This will not enlist any resources involved in this
265: * transaction. According to JTA1.0.1 specification section 3.2.3,
266: * that is the responsibility of the application server.
267: */
268: public void resume(Transaction transaction)
269: throws InvalidTransactionException, IllegalStateException,
270: SystemException {
271: if (transaction != null
272: && !(transaction instanceof TransactionImpl))
273: throw new RuntimeException("Not a TransactionImpl, but a "
274: + transaction.getClass().getName());
275:
276: ThreadInfo ti = getThreadInfo();
277: TransactionImpl current = ti.tx;
278:
279: if (current != null) {
280: if (current.isDone())
281: current = ti.tx = null;
282: else
283: throw new IllegalStateException(
284: "Already associated with a tx");
285: }
286:
287: if (current != transaction) {
288: associateThread(ti, (TransactionImpl) transaction);
289: }
290:
291: if (trace)
292: log.trace("resumed tx: " + ti.tx);
293: }
294:
295: /**
296: * Suspend the transaction currently associated with the current
297: * thread, and return it.
298: *
299: * Note: This will not delist any resources involved in this
300: * transaction. According to JTA1.0.1 specification section 3.2.3,
301: * that is the responsibility of the application server.
302: */
303: public Transaction suspend() throws SystemException {
304: ThreadInfo ti = getThreadInfo();
305: TransactionImpl current = ti.tx;
306:
307: if (current != null) {
308: current.disassociateCurrentThread();
309: ti.tx = null;
310:
311: if (trace)
312: log.trace("suspended tx: " + current);
313:
314: if (current.isDone())
315: current = null;
316: }
317:
318: return current;
319: }
320:
321: /**
322: * Roll back the transaction associated with the currently running thread.
323: */
324: public void rollback() throws IllegalStateException,
325: SecurityException, SystemException {
326: ThreadInfo ti = getThreadInfo();
327: TransactionImpl current = ti.tx;
328:
329: if (current != null) {
330: if (!current.isDone()) {
331: current.rollback();
332:
333: if (trace)
334: log.trace("rolled back tx: " + current);
335: return;
336: }
337: disassociateThread(ti);
338: }
339: throw new IllegalStateException("No transaction.");
340: }
341:
342: /**
343: * Mark the transaction associated with the currently running thread
344: * so that the only possible outcome is a rollback.
345: */
346: public void setRollbackOnly() throws IllegalStateException,
347: SystemException {
348: ThreadInfo ti = getThreadInfo();
349: TransactionImpl current = ti.tx;
350:
351: if (current != null) {
352: if (!current.isDone()) {
353: current.setRollbackOnly();
354:
355: if (trace)
356: log
357: .trace("tx marked for rollback only: "
358: + current);
359: return;
360: }
361: ti.tx = null;
362: }
363: throw new IllegalStateException("No transaction.");
364: }
365:
366: public int getTransactionTimeout() {
367: return (int) (getThreadInfo().timeout / 1000);
368: }
369:
370: /**
371: * Set the transaction timeout for new transactions started by the
372: * calling thread.
373: */
374: public void setTransactionTimeout(int seconds)
375: throws SystemException {
376: getThreadInfo().timeout = 1000 * seconds;
377:
378: if (trace)
379: log.trace("tx timeout is now: " + seconds + "s");
380: }
381:
382: /**
383: * Set the default transaction timeout for new transactions.
384: * This default value is used if <code>setTransactionTimeout()</code>
385: * was never called, or if it was called with a value of <code>0</code>.
386: */
387: public void setDefaultTransactionTimeout(int seconds) {
388: timeOut = 1000L * seconds;
389:
390: if (trace)
391: log.trace("default tx timeout is now: " + seconds + "s");
392: }
393:
394: /**
395: * Get the default transaction timeout.
396: *
397: * @return Default transaction timeout in seconds.
398: */
399: public int getDefaultTransactionTimeout() {
400: return (int) (timeOut / 1000);
401: }
402:
403: public long getTimeLeftBeforeTransactionTimeout(
404: boolean errorRollback) throws RollbackException {
405: try {
406: ThreadInfo ti = getThreadInfo();
407: TransactionImpl current = ti.tx;
408: if (current != null && current.isDone()) {
409: disassociateThread(ti);
410: return -1;
411: }
412: return current.getTimeLeftBeforeTimeout(errorRollback);
413: } catch (RollbackException e) {
414: throw e;
415: } catch (Exception ignored) {
416: return -1;
417: }
418: }
419:
420: /**
421: * The following 2 methods are here to provide association and
422: * disassociation of the thread.
423: */
424: public Transaction disassociateThread() {
425: return disassociateThread(getThreadInfo());
426: }
427:
428: private Transaction disassociateThread(ThreadInfo ti) {
429: TransactionImpl current = ti.tx;
430: ti.tx = null;
431: current.disassociateCurrentThread();
432: return current;
433: }
434:
435: public void associateThread(Transaction transaction) {
436: if (transaction != null
437: && !(transaction instanceof TransactionImpl))
438: throw new RuntimeException("Not a TransactionImpl, but a "
439: + transaction.getClass().getName());
440:
441: // Associate with the thread
442: TransactionImpl transactionImpl = (TransactionImpl) transaction;
443: ThreadInfo ti = getThreadInfo();
444: ti.tx = transactionImpl;
445: transactionImpl.associateCurrentThread();
446: }
447:
448: private void associateThread(ThreadInfo ti,
449: TransactionImpl transaction) {
450: // Associate with the thread
451: ti.tx = transaction;
452: transaction.associateCurrentThread();
453: }
454:
455: /**
456: * Return the number of active transactions
457: */
458: public int getTransactionCount() {
459: return localIdTx.size();
460: }
461:
462: /** A count of the transactions that have been committed */
463: public long getCommitCount() {
464: return commitCount;
465: }
466:
467: /** A count of the transactions that have been rolled back */
468: public long getRollbackCount() {
469: return rollbackCount;
470: }
471:
472: // Implements TransactionPropagationContextImporter ---------------
473:
474: /**
475: * Import a transaction propagation context into this TM.
476: * The TPC is loosely typed, as we may (at a later time) want to
477: * import TPCs that come from other transaction domains without
478: * offloading the conversion to the client.
479: *
480: * @param tpc The transaction propagation context that we want to
481: * import into this TM. Currently this is an instance
482: * of LocalId. At some later time this may be an instance
483: * of a transaction propagation context from another
484: * transaction domain like
485: * org.omg.CosTransactions.PropagationContext.
486: *
487: * @return A transaction representing this transaction propagation
488: * context, or null if this TPC cannot be imported.
489: */
490: public Transaction importTransactionPropagationContext(Object tpc) {
491: if (tpc instanceof LocalId) {
492: LocalId id = (LocalId) tpc;
493: return (Transaction) localIdTx.get(id);
494: } else if (globalIdsEnabled && tpc instanceof GlobalId) {
495: GlobalId id = (GlobalId) tpc;
496: Transaction tx = (Transaction) globalIdTx.get(id);
497: if (trace) {
498: if (tx != null)
499: log
500: .trace("Successfully imported transaction context "
501: + tpc);
502: else
503: log.trace("Could not import transaction context "
504: + tpc);
505: }
506: return tx;
507: }
508:
509: log.warn("Cannot import transaction propagation context: "
510: + tpc);
511: return null;
512: }
513:
514: // Implements TransactionPropagationContextFactory ---------------
515:
516: /**
517: * Return a TPC for the current transaction.
518: */
519: public Object getTransactionPropagationContext() {
520: return getTransactionPropagationContext(getThreadInfo().tx);
521: }
522:
523: /**
524: * Return a TPC for the argument transaction.
525: */
526: public Object getTransactionPropagationContext(Transaction tx) {
527: // If no transaction or unknown transaction class, return null.
528: if (tx == null)
529: return null;
530: if (!(tx instanceof TransactionImpl)) {
531: log.warn("Cannot export transaction propagation context: "
532: + tx);
533: return null;
534: }
535:
536: return ((TransactionImpl) tx).getLocalId();
537: }
538:
539: // Implements XATerminator ----------------------------------
540:
541: public void registerWork(Work work, Xid xid, long timeout)
542: throws WorkCompletedException {
543: if (trace)
544: log.trace("registering work=" + work + " xid=" + xid
545: + " timeout=" + timeout);
546: try {
547: TransactionImpl tx = importExternalTransaction(xid, timeout);
548: tx.setWork(work);
549: } catch (WorkCompletedException e) {
550: throw e;
551: } catch (Throwable t) {
552: WorkCompletedException e = new WorkCompletedException(
553: "Error registering work", t);
554: e.setErrorCode(WorkException.TX_RECREATE_FAILED);
555: throw e;
556: }
557: if (trace)
558: log.trace("registered work= " + work + " xid=" + xid
559: + " timeout=" + timeout);
560: }
561:
562: public void startWork(Work work, Xid xid)
563: throws WorkCompletedException {
564: if (trace)
565: log.trace("starting work=" + work + " xid=" + xid);
566: TransactionImpl tx = getExternalTransaction(xid);
567: associateThread(tx);
568: if (trace)
569: log.trace("started work= " + work + " xid=" + xid);
570: }
571:
572: public void endWork(Work work, Xid xid) {
573: if (trace)
574: log.trace("ending work=" + work + " xid=" + xid);
575: try {
576: TransactionImpl tx = getExternalTransaction(xid);
577: tx.setWork(null);
578: disassociateThread();
579: } catch (WorkCompletedException e) {
580: log.error("Unexpected error from endWork ", e);
581: throw new UnexpectedThrowable(e.toString());
582: }
583: if (trace)
584: log.trace("ended work=" + work + " xid=" + xid);
585: }
586:
587: public void cancelWork(Work work, Xid xid) {
588: if (trace)
589: log.trace("cancling work=" + work + " xid=" + xid);
590: try {
591: TransactionImpl tx = getExternalTransaction(xid);
592: tx.setWork(null);
593: } catch (WorkCompletedException e) {
594: log.error("Unexpected error from cancelWork ", e);
595: throw new UnexpectedThrowable(e.toString());
596: }
597: if (trace)
598: log.trace("cancled work=" + work + " xid=" + xid);
599: }
600:
601: public int prepare(Xid xid) throws XAException {
602: if (trace)
603: log.trace("preparing xid=" + xid);
604: try {
605: TransactionImpl tx = getExternalTransaction(xid);
606: int result = tx.prepare();
607: if (trace)
608: log.trace("prepared xid=" + xid + " result=" + result);
609: return result;
610: } catch (Throwable t) {
611: JBossXAException.rethrowAsXAException(
612: "Error during prepare", t);
613: throw new UnreachableStatementException();
614: }
615: }
616:
617: public void rollback(Xid xid) throws XAException {
618: if (trace)
619: log.trace("rolling back xid=" + xid);
620: try {
621: TransactionImpl tx = getExternalTransaction(xid);
622: tx.rollback();
623: } catch (Throwable t) {
624: JBossXAException.rethrowAsXAException(
625: "Error during rollback", t);
626: }
627: if (trace)
628: log.trace("rolled back xid=" + xid);
629: }
630:
631: public void commit(Xid xid, boolean onePhase) throws XAException {
632: if (trace)
633: log
634: .trace("committing xid=" + xid + " onePhase="
635: + onePhase);
636: try {
637: TransactionImpl tx = getExternalTransaction(xid);
638: tx.commit(onePhase);
639: } catch (Throwable t) {
640: JBossXAException.rethrowAsXAException(
641: "Error during commit", t);
642: }
643: if (trace)
644: log.trace("committed xid=" + xid);
645: }
646:
647: public void forget(Xid xid) throws XAException {
648: if (trace)
649: log.trace("forgetting xid=" + xid);
650: try {
651: TransactionImpl tx = getExternalTransaction(xid);
652: tx.rollback();
653: } catch (Throwable t) {
654: JBossXAException.rethrowAsXAException(
655: "Error during forget", t);
656: }
657: if (trace)
658: log.trace("forgot xid=" + xid);
659: }
660:
661: public Xid[] recover(int flag) throws XAException {
662: // TODO recover
663: return new Xid[0];
664: }
665:
666: TransactionImpl importExternalTransaction(Xid xid, long timeOut) {
667: GlobalId gid = new GlobalId(xid);
668: TransactionImpl tx = (TransactionImpl) globalIdTx.get(gid);
669: if (tx != null) {
670: if (trace)
671: log.trace("imported existing transaction xid: " + xid
672: + " tx=" + tx);
673: } else {
674: ThreadInfo ti = getThreadInfo();
675: long timeout = (ti.timeout == 0) ? timeOut : ti.timeout;
676: tx = new TransactionImpl(gid, timeout);
677: localIdTx.put(tx.getLocalId(), tx);
678: if (globalIdsEnabled)
679: globalIdTx.put(gid, tx);
680:
681: if (trace)
682: log.trace("imported new transaction xid: " + xid
683: + " tx=" + tx + " timeout=" + timeout);
684: }
685: return tx;
686: }
687:
688: TransactionImpl getExternalTransaction(Xid xid)
689: throws WorkCompletedException {
690: GlobalId gid = new GlobalId(xid);
691: TransactionImpl tx = (TransactionImpl) globalIdTx.get(gid);
692: if (tx == null)
693: throw new WorkCompletedException("Xid not found " + xid,
694: WorkException.TX_RECREATE_FAILED);
695: return tx;
696: }
697:
698: // Implements TransactionLocalDelegate ----------------------
699:
700: public void lock(TransactionLocal local, Transaction tx)
701: throws InterruptedException {
702: TransactionImpl tximpl = (TransactionImpl) tx;
703: tximpl.lock();
704: }
705:
706: public void unlock(TransactionLocal local, Transaction tx) {
707: TransactionImpl tximpl = (TransactionImpl) tx;
708: tximpl.unlock();
709: }
710:
711: public Object getValue(TransactionLocal local, Transaction tx) {
712: TransactionImpl tximpl = (TransactionImpl) tx;
713: return tximpl.getTransactionLocalValue(local);
714: }
715:
716: public void storeValue(TransactionLocal local, Transaction tx,
717: Object value) {
718: TransactionImpl tximpl = (TransactionImpl) tx;
719: tximpl.putTransactionLocalValue(local, value);
720: }
721:
722: public boolean containsValue(TransactionLocal local, Transaction tx) {
723: TransactionImpl tximpl = (TransactionImpl) tx;
724: return tximpl.containsTransactionLocal(local);
725: }
726:
727: // Package protected ---------------------------------------------
728:
729: /**
730: * Release the given TransactionImpl.
731: */
732: void releaseTransactionImpl(TransactionImpl tx) {
733: localIdTx.remove(tx.getLocalId());
734: if (globalIdsEnabled)
735: globalIdTx.remove(tx.getGlobalId());
736: }
737:
738: /**
739: * Increment the commit count
740: */
741: void incCommitCount() {
742: ++commitCount;
743: }
744:
745: /**
746: * Increment the rollback count
747: */
748: void incRollbackCount() {
749: ++rollbackCount;
750: }
751:
752: // Protected -----------------------------------------------------
753:
754: // Private -------------------------------------------------------
755:
756: /**
757: * This keeps track of the thread association with transactions
758: * and timeout values.
759: * In some cases terminated transactions may not be cleared here.
760: */
761: private ThreadLocal threadTx = new ThreadLocal();
762:
763: /**
764: * This map contains the active transactions as values.
765: * The keys are the <code>LocalId</code>s of the transactions.
766: */
767: private Map localIdTx = Collections.synchronizedMap(new HashMap());
768:
769: /**
770: * If <code>globalIdsEnabled</code> is true, this map associates
771: * <code>GlobalId</code>s to active transactions.
772: */
773: private Map globalIdTx = Collections.synchronizedMap(new HashMap());
774:
775: /**
776: * Return the ThreadInfo for the calling thread, and create if not
777: * found.
778: */
779: private ThreadInfo getThreadInfo() {
780: ThreadInfo ret = (ThreadInfo) threadTx.get();
781:
782: if (ret == null) {
783: ret = new ThreadInfo();
784: ret.timeout = timeOut;
785: threadTx.set(ret);
786: }
787:
788: return ret;
789: }
790:
791: // Inner classes -------------------------------------------------
792:
793: /**
794: * A simple aggregate of a thread-associated timeout value
795: * and a thread-associated transaction.
796: */
797: static class ThreadInfo {
798: long timeout;
799: TransactionImpl tx;
800: }
801: }
|