KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > aspects > txlock > QueuedTxLock


1 /*
2   * JBoss, Home of Professional Open Source
3   * Copyright 2005, JBoss Inc., and individual contributors as indicated
4   * by the @authors tag. See the copyright.txt in the distribution for a
5   * full listing of individual contributors.
6   *
7   * This is free software; you can redistribute it and/or modify it
8   * under the terms of the GNU Lesser General Public License as
9   * published by the Free Software Foundation; either version 2.1 of
10   * the License, or (at your option) any later version.
11   *
12   * This software is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   * Lesser General Public License for more details.
16   *
17   * You should have received a copy of the GNU Lesser General Public
18   * License along with this software; if not, write to the Free
19   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21   */

22 package org.jboss.aspects.txlock;
23
24 import org.jboss.logging.Logger;
25 import org.jboss.util.deadlock.DeadlockDetector;
26 import org.jboss.util.deadlock.Resource;
27
28 import javax.transaction.Status JavaDoc;
29 import javax.transaction.Synchronization JavaDoc;
30 import javax.transaction.Transaction JavaDoc;
31 import java.util.HashMap JavaDoc;
32 import java.util.LinkedList JavaDoc;
33
34 /**
35  * This class is holds threads awaiting the transactional lock to be free
36  * in a fair FIFO transactional queue. Non-transactional threads
37  * are also put in this wait queue as well. This class pops the next waiting transaction from the queue
38  * and notifies only those threads waiting associated with that transaction.
39  *
40  * As of 04/10/2002, you can now specify in jboss.xml method attributes that define
41  * methods as read-only. read-only methods(and read-only beans) will release transactional
42  * locks at the end of the invocation. This decreases likelyhood of deadlock and increases
43  * performance.
44  *
45  * FIXME marcf: we should get solid numbers on this locking, bench in multi-thread environments
46  * We need someone with serious SUN hardware to run this lock into the ground
47  *
48  * @author <a HREF="marc.fleury@jboss.org">Marc Fleury</a>
49  * @author <a HREF="bill@burkecentral.com">Bill Burke</a>
50  *
51  * @version $Revision: 37406 $
52  */

53 public class QueuedTxLock implements Resource
54 {
55    public static final String JavaDoc TXLOCK = "TxLock";
56    public static final String JavaDoc TIMEOUT = "timeout";
57
58    private HashMap JavaDoc txLocks = new HashMap JavaDoc();
59    private LinkedList JavaDoc txWaitQueue = new LinkedList JavaDoc();
60
61    /** Transaction holding lock on bean */
62    private Transaction JavaDoc tx = null;
63    private boolean synched = false;
64    private boolean isSynchronized = false;
65    private Logger log = Logger.getLogger(this.getClass());
66
67    public Object JavaDoc getResourceHolder()
68    {
69       return tx;
70    }
71
72    public void sync() throws InterruptedException JavaDoc
73    {
74       synchronized (this)
75       {
76          while (synched)
77          {
78             this.wait();
79          }
80          synched = true;
81       }
82    }
83
84    public void releaseSync()
85    {
86       synchronized (this)
87       {
88          synched = false;
89          this.notify();
90       }
91    }
92
93    /**
94     * The setTransaction associates a transaction with the lock.
95     * The current transaction is associated by the schedule call.
96     */

97    public void setTransaction(Transaction JavaDoc tx)
98    {
99       this.tx = tx;
100    }
101
102    public Transaction JavaDoc getTransaction()
103    {
104       return tx;
105    }
106
107
108    private class TxLock
109    {
110
111       public Transaction JavaDoc waitingTx;
112       public String JavaDoc threadName;
113       public boolean isQueued;
114
115       public TxLock(Transaction JavaDoc trans)
116       {
117          this.threadName = Thread.currentThread().toString();
118          this.waitingTx = trans;
119          this.isQueued = true;
120       }
121
122       public boolean equals(Object JavaDoc obj)
123       {
124          if (obj == this) return true;
125
126          TxLock lock = (TxLock) obj;
127
128          return lock.waitingTx.equals(this.waitingTx);
129       }
130
131       public int hashCode()
132       {
133          return waitingTx.hashCode();
134       }
135
136       public String JavaDoc toString()
137       {
138          StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(100);
139          buffer.append("TXLOCK waitingTx=").append(waitingTx);
140          buffer.append(" thread=").append(threadName);
141          buffer.append(" queued=").append(isQueued);
142          return buffer.toString();
143       }
144    }
145
146    protected TxLock getTxLock(Transaction JavaDoc miTx)
147    {
148       TxLock lock = null;
149       TxLock key = new TxLock(miTx);
150       lock = (TxLock) txLocks.get(key);
151       if (lock == null)
152       {
153          txLocks.put(key, key);
154          txWaitQueue.addLast(key);
155          lock = key;
156       }
157       return lock;
158    }
159
160    protected boolean isTxExpired(Transaction JavaDoc miTx) throws Exception JavaDoc
161    {
162       if (miTx != null && miTx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
163       {
164          return true;
165       }
166       return false;
167    }
168
169
170    public boolean lockNoWait(Transaction JavaDoc transaction) throws Exception JavaDoc
171    {
172       this.sync();
173       if (log.isTraceEnabled())
174          log.trace("lockNoWait tx=" + transaction + " " + toString());
175       try
176       {
177          // And are we trying to enter with another transaction?
178
if (getTransaction() != null && !getTransaction().equals(transaction))
179          {
180             return false;
181          }
182          setTransaction(transaction);
183          return true;
184       }
185       finally
186       {
187          this.releaseSync();
188       }
189    }
190
191    /**
192     * doSchedule(Invocation)
193     *
194     * doSchedule implements a particular policy for scheduling the threads coming in.
195     * There is always the spec required "serialization" but we can add custom scheduling in here
196     *
197     * Synchronizing on lock: a failure to get scheduled must result in a wait() call and a
198     * release of the lock. Schedulation must return with lock.
199     *
200     */

201    public void schedule(Transaction JavaDoc miTx, org.jboss.aop.joinpoint.Invocation mi)
202            throws Exception JavaDoc
203    {
204       boolean trace = log.isTraceEnabled();
205       this.sync();
206       try
207       {
208          if (trace) log.trace("Begin schedule");
209
210          if (isTxExpired(miTx))
211          {
212             log.error("Saw rolled back tx=" + miTx);
213             throw new RuntimeException JavaDoc("Transaction marked for rollback, possibly a timeout");
214          }
215
216          //Next test is independent of whether the context is locked or not, it is purely transactional
217
// Is the instance involved with another transaction? if so we implement pessimistic locking
218
waitForTx(mi, miTx, trace);
219          if (!isSynchronized)
220          {
221             isSynchronized = true;
222             miTx.registerSynchronization(new TxLockSynchronization());
223          }
224       }
225       finally
226       {
227          this.releaseSync();
228       }
229
230    }
231
232    /**
233     * Wait until no other transaction is running with this lock.
234     *
235     * @return Returns true if this thread was scheduled in txWaitQueue
236     */

237    protected void waitForTx(org.jboss.aop.joinpoint.Invocation mi, Transaction JavaDoc miTx, boolean trace) throws Exception JavaDoc
238    {
239       boolean wasScheduled = false;
240       // Do we have a running transaction with the context?
241
// We loop here until either until success or until transaction timeout
242
// If we get out of the loop successfully, we can successfully
243
// set the transaction on this puppy.
244
TxLock txLock = null;
245       while (getTransaction() != null &&
246               // And are we trying to enter with another transaction?
247
!getTransaction().equals(miTx))
248       {
249          // Check for a deadlock on every cycle
250
try
251          {
252             DeadlockDetector.singleton.deadlockDetection(miTx, this);
253          }
254          catch (Exception JavaDoc e)
255          {
256             // We were queued, not any more
257
if (txLock != null && txLock.isQueued)
258             {
259                txLocks.remove(txLock);
260                txWaitQueue.remove(txLock);
261             }
262             throw e;
263          }
264
265          wasScheduled = true;
266          // That's no good, only one transaction per context
267
// Let's put the thread to sleep the transaction demarcation will wake them up
268
if (trace) log.trace("Transactional contention on context miTx=" + miTx + " " + toString());
269
270          if (txLock == null)
271             txLock = getTxLock(miTx);
272
273          if (trace) log.trace("Begin wait on " + txLock + " " + toString());
274
275          // And lock the threads on the lock corresponding to the Tx in MI
276
synchronized (txLock)
277          {
278             releaseSync();
279             try
280             {
281                int txTimeout = 0;
282                Integer JavaDoc timeout = (Integer JavaDoc) mi.getMetaData(TXLOCK, TIMEOUT);
283                if (timeout != null) txTimeout = timeout.intValue();
284                txLock.wait(txTimeout);
285             }
286             catch (InterruptedException JavaDoc ignored)
287             {
288             }
289          } // end synchronized(txLock)
290

291          this.sync();
292
293          if (trace) log.trace("End wait on " + txLock + " " + toString());
294          if (isTxExpired(miTx))
295          {
296             log.error(Thread.currentThread() + "Saw rolled back tx=" + miTx + " waiting for txLock");
297             if (txLock.isQueued)
298             {
299                // Remove the TxLock from the queue because this thread is exiting.
300
// Don't worry about notifying other threads that share the same transaction.
301
// They will timeout and throw the below RuntimeException
302
txLocks.remove(txLock);
303                txWaitQueue.remove(txLock);
304             }
305             else if (getTransaction() != null && getTransaction().equals(miTx))
306             {
307                // We're not qu
308
nextTransaction(trace);
309             }
310             if (miTx != null)
311             {
312                DeadlockDetector.singleton.removeWaiting(miTx);
313             }
314             throw new RuntimeException JavaDoc("Transaction marked for rollback, possibly a timeout");
315          }
316       } // end while(tx!=miTx)
317

318       // If we get here, this means that we have the txlock
319
if (!wasScheduled) setTransaction(miTx);
320       return;
321    }
322
323    /*
324     * nextTransaction()
325     *
326     * nextTransaction will
327     * - set the current tx to null
328     * - schedule the next transaction by notifying all threads waiting on the transaction
329     * - setting the thread with the new transaction so there is no race with incoming calls
330     */

331    protected void nextTransaction(boolean trace)
332    {
333       if (!synched)
334       {
335          throw new IllegalStateException JavaDoc("do not call nextTransaction while not synched!");
336       }
337
338       setTransaction(null);
339       // is there a waiting list?
340
TxLock thelock = null;
341       if (!txWaitQueue.isEmpty())
342       {
343          thelock = (TxLock) txWaitQueue.removeFirst();
344          txLocks.remove(thelock);
345          thelock.isQueued = false;
346          // The new transaction is the next one, important to set it up to avoid race with
347
// new incoming calls
348
if (thelock.waitingTx != null)
349             DeadlockDetector.singleton.removeWaiting(thelock.waitingTx);
350          setTransaction(thelock.waitingTx);
351          synchronized (thelock)
352          {
353             // notify All threads waiting on this transaction.
354
// They will enter the methodLock wait loop.
355
thelock.notifyAll();
356          }
357       }
358       if (trace)
359          log.trace("nextTransaction: " + thelock + " " + toString());
360    }
361
362    public void endTransaction()
363    {
364       boolean trace = log.isTraceEnabled();
365       if (trace)
366          log.trace("endTransaction: " + toString());
367       nextTransaction(trace);
368    }
369
370    public void endInvocation(Transaction JavaDoc thetx)
371    {
372       if (log.isTraceEnabled())
373          log.trace("endInvocation: miTx=" + thetx + " " + toString());
374    }
375
376    public String JavaDoc toString()
377    {
378       StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(100);
379       buffer.append(" hash=").append(hashCode());
380       buffer.append(" tx=").append(getTransaction());
381       buffer.append(" synched=").append(synched);
382       buffer.append(" queue=").append(txWaitQueue);
383       return buffer.toString();
384    }
385
386    private final class TxLockSynchronization implements Synchronization JavaDoc
387    {
388       public void beforeCompletion()
389       {
390       }
391
392       public void afterCompletion(int status)
393       {
394          try
395          {
396             sync();
397          }
398          catch (InterruptedException JavaDoc ignored)
399          {
400          }
401          isSynchronized = false;
402          endTransaction();
403          releaseSync();
404       }
405    }
406 }
407
408
Popular Tags