From: Kevin D. <ke...@tr...> - 2006-03-31 15:08:23
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML><HEAD> <STYLE type=text/css> P, UL, OL, DL, DIR, MENU, PRE { margin: 0 auto;}</STYLE> <META content="MSHTML 6.00.2900.2802" name=GENERATOR></HEAD> <BODY leftMargin=1 topMargin=1 rightMargin=1><FONT face=Tahoma> <DIV><FONT face=Arial size=2>Bryan-</FONT></DIV> <DIV><FONT face=Arial size=2></FONT> </DIV> <DIV><FONT face=Arial size=2>Ahh - ok - if you do indeed have a lock context, then you may want to consider changing all references to the Tx class in the changes I proposed to be generic Objects. The thread local variable will become threadLocalLockContect, and you will have calls for setting and retrieving the lock context. This nicely encapsulates the locking sub-system. Consumers of that sub-system (the jdbm transaction manager, for example) would choose their lock object types as they see fit (in the transaction manager, we would probaly just choose the transaction object - but that's optional).</FONT></DIV> <DIV><FONT face=Arial size=2></FONT> </DIV> <DIV><FONT face=Arial size=2>Technically, we will need to store the transaction for the current thread in the transaction manager anyway, but I think that having a separation between the concept of a lock context and a transaction may be a good design decision.</FONT></DIV> <DIV><FONT face=Arial size=2></FONT> </DIV> <DIV><FONT face=Arial size=2>As a quasi-side point: If you implement with generic lock contexts, then a user could obtain the exact behavior as your original design by using the deginerate case of assiging the current thread as the lock context object.</FONT></DIV> <DIV><FONT face=Arial size=2></FONT> </DIV> <DIV><FONT face=Arial size=2>- K</FONT></DIV> <DIV><FONT face=Arial size=2></FONT> </DIV> <DIV><FONT face=Arial size=2> </FONT> <TABLE> <TBODY> <TR> <TD width=1 bgColor=blue><FONT face=Arial size=2></FONT></TD> <TD><FONT face=Arial size=2><FONT color=red>> <BR>Kevin, <BR> <BR>Thanks for looking at the code. I can certainly make the naming changes that you suggest. Queue.lock() does accept a lock context object it was in the comments where it was invoked and so does Queue.unlock(). I just had not propagated that change through the code and documentation yet. <BR> <BR>-bryan <BR> <BR><BR><BR>From: <A href="mailto:jdb...@li..."><FONT color=#0000ff>jdb...@li...</FONT></A> <A href="mailto:jdb...@li..."><FONT color=#0000ff>[mailto:jdb...@li...]</FONT></A> On Behalf Of Kevin Day <BR>Sent: Thursday, March 30, 2006 8:02 PM<BR>To: JDBM Developer listserv<BR>Subject: [Jdbm-developer] re: DefaultTransactionManager - draft of prototype. <BR> <BR><BR>Bryan- <BR><BR> <BR><BR>I've done a cursory glance through this code (this will be a heck of a lot easier when we have svn access!)... <BR><BR> <BR><BR>The only thing that stands out that I feel needs comments is the currentThread variable... I think that you want this to be threadLocalTx. <BR><BR> <BR><BR>So, the call to getCurrentThread and setCurrentThread will actually morph into getCurrentTx and setCurrentTx, etc..., as in: <BR><BR> <BR><BR> <BR><BR> synchronized public static Tx getCurrentTx() { <BR><BR> return (Tx) threadLocalTx.get();<BR> } <BR><BR> <BR><BR> synchronized public static void setCurrentTx(Tx contextTx) {<BR> if (threadLocalTx.get() != null) {<BR> throw new IllegalStateException("Tx already assigned for this thread");<BR> }<BR> threadLocalTx.set(contextTx);<BR> } <BR><BR> /**<BR> * Throw exception if the specified lock context is not bound to the current thread. <BR><BR> */<BR> synchronized public static void assertCurrentTx() {<BR> if (threadLocalLockContext.get() != this) {<BR> throw new IllegalStateException("Tx context not bound to this thread");<BR> }<BR> } <BR><BR> <BR><BR> <BR><BR>I didn't see an implementation for the Queue class itself, but I suspect that the Queue.lock() method will wind up accepting a lock context object as a parameter. In jdbm, this lock context will be the transaction object returned by getCurrentTx(). <BR><BR> <BR><BR>Or am I maybe missing something important about how the Queue class operates? It seems to me that the items on the queue must somehow be associated with the transaction that holds the lock (or is waiting for the lock)... <BR><BR> <BR><BR>- K <BR><BR> <BR><BR> <BR><BR> <BR><BR> <BR>> All,<BR><BR>Here is some code based on our discussions that attempts to integrate<BR>the 2PL support with a transaction factory and support the binding of<BR>threads to transactions. Please take a look. I have made some notes<BR>where the code is incomplete, requires features not available, etc.<BR><BR>I am not sure where this is going right now. I could see how this<BR>could be re-factored into an extensible mechanism for creating<BR>transactions and making use of the 2PL facilities, but I am not sure<BR>that this much of the control structure belongs in a generic purpose<BR>2PL locking package rather than in some jdbm specific transaction<BR>management classes.<BR><BR>-bryan<BR><BR>package org.CognitiveWeb.concurrent.locking;<BR><BR>import java.util.HashMap;<BR>import java.util.HashSet;<BR>import java.util.Iterator;<BR>import java.util.Map;<BR>import java.util.Set;<BR><BR>/**<BR>* A factory for transaction objects supporting assignment of threads to<BR>* transactions. A thread may assign a transaction to itself iff the<BR>transaction<BR>* is not currently bound to a thread. If a lock request would result in a<BR>* deadlock, then a {@link DeadlockException} is thrown. If a lock request<BR>* blocks, then a transaction will remain bound to that thread until the<BR>lock<BR>* request is granted, a timeout occurs, or the transaction is aborted. When<BR>a<BR>* thread is running, it may release the bound transaction. A transaction<BR>that<BR>* is not associated with any thread is not running. If other transactions<BR>are<BR>* waiting on a transaction that is not running, then those transactions<BR>will<BR>* block until the transaction is rebound to another thread and it releases<BR>its<BR>* locks. When a transaction completes (either aborts or commits), all locks<BR>* associated with that transaction are released.<BR>* <BR>* @author <a <A href="mailto:Bryan">href="mailto:tho...@us...">Bryan"><FONT color=#0000ff>Bryan">href="mailto:tho...@us...">Bryan</FONT></A><BR>Thompson</a><BR>* @version $Id: DefaultTransactionManager.java,v 1.1 2006/03/29 14:41:46<BR>thompsonbry Exp $<BR>*/<BR><BR>public class DefaultTransactionManager<BR>{<BR> <BR> /**<BR> * The maximum multi-programming level.<BR> */<BR> private final int capacity;<BR> <BR> /**<BR> * The directed graph of the WAITS_FOR relation which is used to<BR>detect<BR> * deadlocks.<BR> */<BR> protected final TxDag waitsFor;<BR><BR> /**<BR> * The active transaction objects.<BR> */<BR> private final Set transactions;<BR><BR> /**<BR> * The queue for each in-use resource.<BR> * <BR> * FIXME This must be a weak value hash map so that queues may be<BR>garbage<BR> * collected once they are no longer in use.<BR> */<BR> private final Map queues = new HashMap();<BR> <BR> /**<BR> * The maximum multi-programming level (from the constructor).<BR> */<BR> public int capacity() {<BR> return capacity;<BR> }<BR><BR> /**<BR> * The current multi-programming level.<BR> */<BR> public int size() {<BR> return transactions.size();<BR> }<BR> <BR> /**<BR> * Create a transaction manager.<BR> * <BR> * @param capacity<BR> * The maximum multiprogramming level (aka the maximum<BR>#of<BR> * concurrent transactions).<BR> */<BR> <BR> public DefaultTransactionManager( int capacity )<BR> {<BR> <BR> this.capacity = capacity;<BR> <BR> waitsFor = new TxDag( capacity );<BR><BR> transactions = new HashSet( capacity );<BR> <BR> }<BR><BR> /**<BR> * Create a new transaction. By default the transaction will be<BR>bound to the<BR> * thread in which this request was made. If the maximum<BR>multi-programming<BR> * level would be exceeded, then this request will block until the<BR> * concurrency level has decreased.<BR> * <BR> * @exception IllegalStateException<BR> * If the thread is already bound to another<BR>transaction.<BR> * <BR> * @return The transaction.<BR> */<BR> <BR> public Tx createTx()<BR> {<BR> <BR> return createTx( 0L, 0 );<BR><BR> }<BR> <BR> /**<BR> * Create a new transaction. By default the transaction will be<BR>bound to the<BR> * thread in which this request was made. If the maximum<BR>multi-programming<BR> * level would be exceeded, then this request will block until the<BR> * concurrency level has decreased.<BR> * <BR> * @param millis<BR> * <BR> * @param nanos<BR> * <BR> * @exception IllegalStateException<BR> * If the thread is already bound to another<BR>transaction.<BR> * @exception TimeoutException<BR> * If the transaction could not be created in the<BR>specified<BR> * time.<BR> * <BR> * @return The transaction.<BR> */<BR> <BR> synchronized public Tx createTx( long millis, int nanos )<BR> {<BR> <BR> // @todo enforce multi-programming limit. e.g., using<BR>java.util.concurrent.Queue.<BR> <BR> Tx tx = new Tx( this );<BR> <BR> transactions.add( tx );<BR> <BR> return tx;<BR> <BR> }<BR><BR> /**<BR> * Return the {@link Queue} for the identified resource.<BR> * <p><BR> * Note: The {@link Queue}s are maintained in a weak value hash map<BR>whose<BR> * keys are the resource identified. Each transaction must hold a<BR>hard<BR> * reference to each queue for which it requests a lock and must<BR>clear that<BR> * hard reference once it releases its lock. Once there are no more<BR> * references to a given {@link Queue}, the garbage collector will<BR>reclaim<BR> * the queue. This is important since the number of queue objects is<BR>bounded<BR> * by the number of distinct resources in the database and could<BR>otherwise<BR> * easily exceed the available memory.<BR> * <BR> * @param resource<BR> * The resource identified.<BR> * <BR> * @param insert<BR> * When true a new {@link Queue} will be created iff none<BR>exists<BR> * for that resource.<BR> * <BR> * @return The queue for that resource or <code>null</code> iff<BR> * <code> insert == false </code> and there is no queue for<BR>that<BR> * resource.<BR> */<BR> <BR> synchronized protected Queue getQueue( Object resource, boolean<BR>insert )<BR> {<BR> <BR> if( resource == null ) {<BR> throw new IllegalArgumentException();<BR> }<BR> <BR> Queue queue = (Queue) queues.get(resource);<BR> <BR> if( queue == null && insert ) {<BR> <BR> queue = new Queue(waitsFor,resource);<BR> <BR> queues.put( resource, queue );<BR> <BR> }<BR> <BR> return queue;<BR> <BR> }<BR> <BR> /**<BR> * A transaction object. A transaction object may only be used by<BR>the thread<BR> * to which it is currently bound. If the transaction is not bound<BR>to any<BR> * thread, then it must be bound to a thread before any other<BR>operations may<BR> * be taken. A transaction which is not bound to a thread is not<BR>running and<BR> * any transactions waiting on that transaction can not run the<BR>transaction<BR> * has been bound to a thread and its locks have been released.<BR> * <BR> * @author <a <A href="mailto:Bryan">href="mailto:tho...@us...">Bryan"><FONT color=#0000ff>Bryan">href="mailto:tho...@us...">Bryan</FONT></A><BR>Thompson</a><BR> * @version $Id: DefaultTransactionManager.java,v 1.1 2006/03/29<BR>14:41:46 thompsonbry Exp $<BR> * <BR> * @todo Define a transaction identifier, expose it, and use it as<BR>the key<BR> * in {@link DefaultTransactionManager#transactions}?<BR> */<BR><BR> public static class Tx {<BR><BR> /**<BR> * The transaction manager.<BR> */<BR> private final DefaultTransactionManager txMgr;<BR><BR> /**<BR> * The {@link Queue} for each lock requested by this<BR>transaction. The<BR> * queue is added to this collection before the lock request<BR>is made and<BR> * is cleared from the collection once the lock has either<BR>been released<BR> * or when a deadlock or timeout exception was thrown at the<BR>time that<BR> * the lock was requested.<BR> */<BR> private final Set locks = new HashSet();<BR> <BR> /**<BR> * The transaction manager.<BR> */<BR><BR> public DefaultTransactionManager getTranasctionManager() {<BR> return txMgr;<BR> }<BR><BR> /**<BR> * Create a new transaction object.<BR> */<BR> protected Tx( DefaultTransactionManager txMgr ) {<BR> if( txMgr == null ) {<BR> throw new IllegalArgumentException();<BR> }<BR> this.txMgr = txMgr;<BR> setCurrentThread();<BR> }<BR> <BR> /**<BR> * A thread local variable whose value is either the thread<BR>to which the<BR> * transaction is currently bound or <code>null</code> iff<BR>the<BR> * transaction is not currently bound to any thread.<BR> * <BR> * @todo Review synchronization requirements for methods<BR>that access and<BR> * set this thread local variable.<BR> */<BR> private static ThreadLocal currentThread = new ThreadLocal()<BR>{<BR> protected synchronized Object initialValue() {<BR> return null;<BR> }<BR> };<BR><BR> /**<BR> * Return the thread to which this transaction is currently<BR>bound or<BR> * <code>null</code> iff the transaction is not bound to any<BR> * thread.<BR> */<BR> synchronized public static Thread getCurrentThread() {<BR> return (Thread) currentThread.get();<BR> }<BR><BR> /**<BR> * Release the transaction from the thread of the current<BR>context.<BR> * <BR> * @exception IllegalStateException<BR> * if the transaction is not bound to the<BR>thread of the<BR> * current context.<BR> */<BR> synchronized public static void releaseCurrentThread() {<BR> assertCurrentThread();<BR> currentThread.set(null);<BR> }<BR><BR> /**<BR> * Bind the transaction to the thread of the current<BR>context.<BR> * <BR> * @exception IllegalStateException<BR> * if the transaction is already bound to a<BR>thread.<BR> */<BR> synchronized public static void setCurrentThread() {<BR> if (currentThread.get() != null) {<BR> throw new IllegalStateException("thread<BR>already assigned");<BR> }<BR> currentThread.set(Thread.currentThread());<BR> }<BR><BR> /**<BR> * Throw exception if the {@link Tx} is not bound to the<BR>thread of<BR> * the current context.<BR> */<BR> synchronized public static void assertCurrentThread() {<BR> if (currentThread.get() != Thread.currentThread()) {<BR> throw new IllegalStateException(<BR> "transaction not bound to<BR>this thread");<BR> }<BR> }<BR><BR> /**<BR> * Request a lock on the identified resource in the<BR>specified mode. This<BR> * method will block until the lock can be granted.<BR> * <BR> * @param resource<BR> * The resource identifier.<BR> * <BR> * @param mode<BR> * The mode - see {@link LockMode).<BR> * <BR> * @exception DeadlockException<BR> * if the lock request would cause a<BR>deadlock.<BR> * <BR> * @todo add timeout variant of this method.<BR> * @todo add non-blocking variant of this method.<BR> */<BR> <BR> public void lock( final Object resource, final short mode )<BR> {<BR> <BR> if( resource == null ) {<BR> throw new IllegalArgumentException();<BR> }<BR><BR> assertCurrentThread();<BR><BR> final Queue queue = txMgr.getQueue( resource, true<BR>);<BR> <BR> /*<BR> * Note: If locks are reentrant, then we need to be<BR>careful to only<BR> * clear the Queue reference from [locks] once the<BR>transaction has<BR> * reduced the lock counter to zero for that queue.<BR> */ <BR> final boolean exists = locks.add( queue );<BR> <BR> try {<BR> queue.lock( mode ); // @todo lock( this,<BR>mode );<BR> }<BR> catch( DeadlockException t ) {<BR> if( ! exists ) locks.remove( queue );<BR> throw t;<BR> }<BR> catch( TimeoutException t ) {<BR> if( ! exists ) locks.remove( queue );<BR> throw t;<BR> }<BR> catch( Throwable t ) {<BR> if( ! exists ) locks.remove( queue );<BR> throw new RuntimeException( t );<BR> }<BR> <BR> }<BR><BR> /**<BR> * Release the lock on the identified resource.<BR> * <BR> * @param resource<BR> * <BR> * @exception IllegalStateException<BR> * if the transaction does not have a lock on<BR>that<BR> * resource.<BR> */<BR> <BR> public void unlock( Object resource )<BR> {<BR> <BR> if( resource == null ) {<BR> throw new IllegalArgumentException();<BR> }<BR> <BR> assertCurrentThread();<BR> <BR> Queue queue = txMgr.getQueue( resource, true );<BR> <BR> if( queue == null ) {<BR> <BR> throw new IllegalStateException(<BR> "no queue for resource:<BR>resource=" + resource);<BR> <BR> }<BR><BR> queue.unlock(); // @todo unlock( this );<BR><BR><BR> if( ! locks.remove( queue ) ) {<BR> <BR> throw new AssertionError();<BR> <BR> }<BR> <BR> }<BR> <BR> /**<BR> * This method MUST be invoked to signal that the<BR>transaction has<BR> * completed its processing and is responsible for releasing<BR>all locks<BR> * and other resources associated with the transaction. This<BR>method MUST<BR> * be used whether the transaction was successful or<BR>aborted. This method<BR> * also releases the bond between the transaction and the<BR>thread which is<BR> * executing that transaction.<BR> */<BR> <BR> public void complete()<BR> {<BR><BR> /*<BR> * @todo Is it always possible to call this method,<BR>e.g., on a tx<BR> * abort, from the thread associated with the tx?<BR>Perhaps we need to<BR> * allow this method to be called by any thread and<BR>always have it<BR> * release the tx - thread bond.<BR> */<BR> assertCurrentThread();<BR> <BR> /*<BR> * @todo In order to optimize lock release behavior<BR>both this method<BR> * and Queue should probably synchronize on<BR>waitsFor. This would<BR> * allow us to use a unlock() variant that did not<BR>update the<BR> * waitsFor graph to release each of the locks and<BR>then use the<BR> * optimized methods to clear the row and column for<BR>this<BR> * transaction from the waitsFor graph.<BR> * <BR> * The code below just steps through all of the<BR>locks that are still<BR> * held by this transaction releasing them one at a<BR>time and<BR> * updating the waitsFor graph each time a lock is<BR>released.<BR> * <BR> * @todo If we have re-entrant locks then this code<BR>will not work<BR> * since the application will have to release the<BR>locks the right<BR> * #of times. I really do not see the point of<BR>re-entrant locks,<BR> * certainly not if they are associated with a lock<BR>count as opposed<BR> * to simply returning immedately if the lock is<BR>already held by the<BR> * transaction.<BR> */<BR> <BR> Iterator itr = locks.iterator();<BR> <BR> while( itr.hasNext() ) {<BR> <BR> Queue queue = (Queue) itr.next();<BR> <BR> queue.unlock(); // @todo unlock( this );<BR><BR><BR> itr.remove();<BR> <BR> }<BR><BR> releaseCurrentThread();<BR> <BR> }<BR> <BR> } // class Tx.<BR> <BR>}<BR><BR><<BR><<BR></FONT></FONT></TD></TR></TBODY></TABLE></DIV></FONT></BODY></HTML> |