KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > ejb > plugins > lock > QueuedPessimisticEJBLock


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.ejb.plugins.lock;
23
24 import java.util.LinkedList JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.ArrayList JavaDoc;
27
28 import javax.transaction.Transaction JavaDoc;
29 import javax.transaction.Status JavaDoc;
30
31 import org.jboss.invocation.Invocation;
32 import org.jboss.ejb.Container;
33 import org.jboss.ejb.EntityEnterpriseContext;
34 import org.jboss.monitor.LockMonitor;
35 import org.jboss.util.deadlock.DeadlockDetector;
36
37 /**
38  * This class is holds threads awaiting the transactional lock to be free
39  * in a fair FIFO transactional queue. Non-transactional threads
40  * are also put in this wait queue as well. Unlike SimplePessimisticEJBLock which notifies all
41  * threads on transaction completion, this class pops the next waiting transaction from the queue
42  * and notifies only those threads waiting associated with that transaction. This
43  * class should perform better than Simple on high contention loads.
44  *
45  * Holds all locks for entity beans, not used for stateful. <p>
46  *
47  * All BeanLocks have a reference count.
48  * When the reference count goes to 0, the lock is released from the
49  * id -> lock mapping.
50  *
51  * As of 04/10/2002, you can now specify in jboss.xml method attributes that define
52  * methods as read-only. read-only methods(and read-only beans) will release transactional
53  * locks at the end of the invocation. This decreases likelyhood of deadlock and increases
54  * performance.
55  *
56  * FIXME marcf: we should get solid numbers on this locking, bench in multi-thread environments
57  * We need someone with serious SUN hardware to run this lock into the ground
58  *
59  * @author <a HREF="marc.fleury@jboss.org">Marc Fleury</a>
60  * @author <a HREF="bill@burkecentral.com">Bill Burke</a>
61  * @author <a HREF="pete@subx.com">Peter Murray</a>
62  *
63  * @version $Revision: 37459 $
64  */

65 public class QueuedPessimisticEJBLock extends BeanLockSupport
66 {
67    private HashMap JavaDoc txLocks = new HashMap JavaDoc();
68    private LinkedList JavaDoc txWaitQueue = new LinkedList JavaDoc();
69
70    private int txIdGen = 0;
71    protected LockMonitor lockMonitor = null;
72    /** A flag that disables the deadlock detection check */
73    protected boolean deadlockDetection = true;
74
75    public void setContainer(Container container)
76    {
77       this.container = container;
78       lockMonitor = container.getLockManager().getLockMonitor();
79    }
80
81    public boolean getDeadlockDetection()
82    {
83       return deadlockDetection;
84    }
85    public void setDeadlockDetection(boolean flag)
86    {
87       this.deadlockDetection = flag;
88    }
89
90    private class TxLock
91    {
92
93       public Transaction JavaDoc waitingTx = null;
94       public int id = 0;
95       public String JavaDoc threadName;
96       public boolean isQueued;
97
98       /**
99        * deadlocker is used by the DeadlockDetector
100        * It is the thread if the tx is null.
101        */

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

208    protected boolean doSchedule(Invocation mi)
209            throws Exception JavaDoc
210    {
211       boolean wasThreadScheduled = false;
212       Transaction JavaDoc miTx = mi.getTransaction();
213       boolean trace = log.isTraceEnabled();
214       this.sync();
215       try
216       {
217          if (trace) log.trace("Begin schedule, key=" + mi.getId());
218
219          if (isTxExpired(miTx))
220          {
221             log.error("Saw rolled back tx=" + miTx);
222             throw new RuntimeException JavaDoc("Transaction marked for rollback, possibly a timeout");
223          }
224
225          //Next test is independent of whether the context is locked or not, it is purely transactional
226
// Is the instance involved with another transaction? if so we implement pessimistic locking
227
long startWait = System.currentTimeMillis();
228          try
229          {
230             wasThreadScheduled = waitForTx(miTx, trace);
231             if (wasThreadScheduled && lockMonitor != null)
232             {
233                long endWait = System.currentTimeMillis() - startWait;
234                lockMonitor.finishedContending(endWait);
235             }
236          }
237          catch (Exception JavaDoc throwable)
238          {
239             if (lockMonitor != null && isTxExpired(miTx))
240             {
241                lockMonitor.increaseTimeouts();
242             }
243             if (lockMonitor != null)
244             {
245                long endWait = System.currentTimeMillis() - startWait;
246                lockMonitor.finishedContending(endWait);
247             }
248             throw throwable;
249          }
250       }
251       finally
252       {
253          if (miTx == null // non-transactional
254
&& wasThreadScheduled)
255          {
256             // if this non-transctional thread was
257
// scheduled in txWaitQueue, we need to call nextTransaction
258
// Otherwise, threads in txWaitQueue will never wake up.
259
nextTransaction();
260          }
261          this.releaseSync();
262       }
263
264       //If we reach here we are properly scheduled to go through so return true
265
return true;
266    }
267
268    /**
269     * Wait until no other transaction is running with this lock.
270     *
271     * @return Returns true if this thread was scheduled in txWaitQueue
272     */

273    protected boolean waitForTx(Transaction JavaDoc miTx, boolean trace) throws Exception JavaDoc
274    {
275       boolean wasScheduled = false;
276       // Do we have a running transaction with the context?
277
// We loop here until either until success or until transaction timeout
278
// If we get out of the loop successfully, we can successfully
279
// set the transaction on this puppy.
280
TxLock txLock = null;
281       Object JavaDoc deadlocker = miTx;
282       if (deadlocker == null) deadlocker = Thread.currentThread();
283
284       while (getTransaction() != null &&
285               // And are we trying to enter with another transaction?
286
!getTransaction().equals(miTx))
287       {
288          // Check for a deadlock on every cycle
289
try
290          {
291             if( deadlockDetection == true )
292                DeadlockDetector.singleton.deadlockDetection(deadlocker, this);
293          }
294          catch (Exception JavaDoc e)
295          {
296             // We were queued, not any more
297
if (txLock != null && txLock.isQueued)
298             {
299                txLocks.remove(txLock);
300                txWaitQueue.remove(txLock);
301             }
302             throw e;
303          }
304
305          wasScheduled = true;
306          if (lockMonitor != null) lockMonitor.contending();
307          // That's no good, only one transaction per context
308
// Let's put the thread to sleep the transaction demarcation will wake them up
309
if (trace) log.trace("Transactional contention on context" + id);
310
311          // Only queue the lock on the first iteration
312
if (txLock == null)
313             txLock = getTxLock(miTx);
314
315          if (trace) log.trace("Begin wait on Tx=" + getTransaction());
316
317          // And lock the threads on the lock corresponding to the Tx in MI
318
synchronized (txLock)
319          {
320             releaseSync();
321             try
322             {
323                txLock.wait(txTimeout);
324             }
325             catch (InterruptedException JavaDoc ignored)
326             {
327             }
328          } // end synchronized(txLock)
329

330          this.sync();
331
332          if (trace) log.trace("End wait on TxLock=" + getTransaction());
333          if (isTxExpired(miTx))
334          {
335             log.error(Thread.currentThread() + "Saw rolled back tx=" + miTx + " waiting for txLock"
336                     // +" On method: " + mi.getMethod().getName()
337
// +" txWaitQueue size: " + txWaitQueue.size()
338
);
339             if (txLock.isQueued)
340             {
341                // Remove the TxLock from the queue because this thread is exiting.
342
// Don't worry about notifying other threads that share the same transaction.
343
// They will timeout and throw the below RuntimeException
344
txLocks.remove(txLock);
345                txWaitQueue.remove(txLock);
346             }
347             else if (getTransaction() != null && getTransaction().equals(miTx))
348             {
349                // We're not qu
350
nextTransaction();
351             }
352             if (miTx != null)
353             {
354                if( deadlockDetection == true )
355                   DeadlockDetector.singleton.removeWaiting(deadlocker);
356             }
357             throw new RuntimeException JavaDoc("Transaction marked for rollback, possibly a timeout");
358          }
359       } // end while(tx!=miTx)
360

361       // If we get here, this means that we have the txlock
362
if (!wasScheduled)
363       {
364          setTransaction(miTx);
365       }
366       return wasScheduled;
367    }
368
369    /*
370     * nextTransaction()
371     *
372     * nextTransaction will
373     * - set the current tx to null
374     * - schedule the next transaction by notifying all threads waiting on the transaction
375     * - setting the thread with the new transaction so there is no race with incoming calls
376     */

377    protected void nextTransaction()
378    {
379       if (synched == null)
380       {
381          throw new IllegalStateException JavaDoc("do not call nextTransaction while not synched!");
382       }
383
384       setTransaction(null);
385       // is there a waiting list?
386
if (!txWaitQueue.isEmpty())
387       {
388          TxLock thelock = (TxLock) txWaitQueue.removeFirst();
389          txLocks.remove(thelock);
390          thelock.isQueued = false;
391          // The new transaction is the next one, important to set it up to avoid race with
392
// new incoming calls
393
setTransaction(thelock.waitingTx);
394          // log.debug(Thread.currentThread()+" handing off to "+lock.threadName);
395
if( deadlockDetection == true )
396             DeadlockDetector.singleton.removeWaiting(thelock.deadlocker);
397
398          synchronized (thelock)
399          {
400             // notify All threads waiting on this transaction.
401
// They will enter the methodLock wait loop.
402
thelock.notifyAll();
403          }
404       }
405       else
406       {
407          // log.debug(Thread.currentThread()+" handing off to empty queue");
408
}
409    }
410
411    public void endTransaction(Transaction JavaDoc transaction)
412    {
413       nextTransaction();
414    }
415
416    public void wontSynchronize(Transaction JavaDoc trasaction)
417    {
418       nextTransaction();
419    }
420
421    /**
422     * releaseMethodLock
423     *
424     * if we reach the count of zero it means the instance is free from threads (and reentrency)
425     * we wake up the next thread in the currentLock
426     */

427    public void endInvocation(Invocation mi)
428    {
429       // Do we own the lock?
430
Transaction JavaDoc tx = mi.getTransaction();
431       if (tx != null && tx.equals(getTransaction()))
432       {
433          // If there is no context or synchronization, release the lock
434
EntityEnterpriseContext ctx = (EntityEnterpriseContext) mi.getEnterpriseContext();
435          if (ctx == null || ctx.hasTxSynchronization() == false)
436             endTransaction(tx);
437       }
438    }
439
440    public void removeRef()
441    {
442       refs--;
443       if (refs == 0 && txWaitQueue.size() > 0)
444       {
445          log.error("removing bean lock and it has tx's in QUEUE! " + toString());
446          throw new IllegalStateException JavaDoc("removing bean lock and it has tx's in QUEUE!");
447       }
448       else if (refs == 0 && getTransaction() != null)
449       {
450          log.error("removing bean lock and it has tx set! " + toString());
451          throw new IllegalStateException JavaDoc("removing bean lock and it has tx set!");
452       }
453       else if (refs < 0)
454       {
455          log.error("negative lock reference count should never happen !");
456          throw new IllegalStateException JavaDoc("negative lock reference count !");
457       }
458    }
459
460    public String JavaDoc toString()
461    {
462       StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(100);
463       buffer.append(super.toString());
464       buffer.append(", bean=").append(container.getBeanMetaData().getEjbName());
465       buffer.append(", id=").append(id);
466       buffer.append(", refs=").append(refs);
467       buffer.append(", tx=").append(getTransaction());
468       buffer.append(", synched=").append(synched);
469       buffer.append(", timeout=").append(txTimeout);
470       buffer.append(", queue=").append(new ArrayList JavaDoc(txWaitQueue));
471       return buffer.toString();
472    }
473 }
474
Popular Tags