User: mnf999
Date: 01/08/01 12:48:23
Added: src/main/org/jboss/ejb BeanLock.java BeanLockManager.java
Log:
The beanlock and bean lock manager from Bill,
They are now on steroids to implement the scheduling for threads coming in the
container
This particular BeanLock implements "pessimistic" locking on the tx with a full
notify() structure
We do this with a N-Lock list, one per transaction, and this enables real FIFO
behaviour on the
transaction scheduling
Revision Changes Path
1.1 jboss/src/main/org/jboss/ejb/BeanLock.java
Index: BeanLock.java
===================================================================
/*
* JBoss, the OpenSource EJB server
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.ejb;
import java.util.LinkedList;
import java.util.HashMap;
import java.util.Collections;
import java.lang.reflect.Method;
import javax.transaction.Transaction;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.TransactionManager;
import javax.transaction.RollbackException;
import javax.ejb.EJBObject;
import org.jboss.ejb.MethodInvocation;
import org.jboss.logging.log4j.JBossCategory;
/**
* Holds all locks for entity beans, not used for stateful. <p>
*
* All BeanLocks have a reference count.
* When the reference count goes to 0, the lock is released from the
* id -> lock mapping.
*
* @author <a href="[EMAIL PROTECTED]">Bill Burke</a>
* @author <a href="[EMAIL PROTECTED]">Marc Fleury</a>
*
* @version $Revision: 1.1 $
*
* <p><b>Revisions:</b><br>
* <p><b>2001/07/29: billb</b>
* <ol>
* <li>Initial revision
* </ol>
* <p><b>2001/08/01: marcf</b>
* <ol>
* <li>Added the schedule method
* <li>The bean lock is now responsible for implementing the locking policies, it
was before in the
* interceptor code it is now factored out allowing for pluggable lock policies
(optimistic for ex)
* <li>Implemented pessimistic locking and straight spec requirement in the
schedule method
* would need to factor this in an abstract class for the BeanLock that extending
policies can use
* <li> N-lock design for the actual lock implementation (one per tx) which enables
a full FIFO
* implementation on the transactions themselves.
* </ol>
*/
public class BeanLock
{
/** The actual lock objects **/
// private LinkedList txList = (LinkedList) Collections.synchronizedList(new
LinkedList()) ;
private LinkedList txList = new LinkedList();
private HashMap txLocks = new HashMap();
private Object currentLock = new Object();
/** number of threads invoking methods on this bean (1 normally >1 if reentrant)
**/
private int numMethodLocks = 0;
/** number of threads that retrieved this lock from the manager (0 means
removing) **/
private int refs = 0;
/**The Cachekey corresponding to this Bean */
private Object id = null;
/**Are reentrant calls allowed? */
private boolean reentrant;
/** Use a JBoss custom log4j category for trace level logging */
static JBossCategory log = (JBossCategory)
JBossCategory.getInstance(BeanLock.class);
private Transaction tx = null;
private boolean synched = false;
private int txTimeout;
public BeanLock(Object id, boolean reentrant, int txTimeout)
{
this.id = id;
this.reentrant = reentrant;
this.txTimeout = txTimeout;
}
public Object getId()
{
return id;
}
public void sync()
{
synchronized(this)
{
while(synched)
{
try
{
this.wait();
}
catch (InterruptedException ex) { /* ignore */ }
}
synched = true;
}
}
public void releaseSync()
{
synchronized(this)
{
synched = false;
this.notify();
}
}
/**
* Schedule(MethodInvocation)
*
* Schedule implements a particular policy for scheduling the threads coming in.
* There is always the spec required "serialization" but we can add custom
scheduling in here
*
* Synchronizing on lock: a failure to get scheduled must result in a wait() call
and a
* release of the lock. Schedulation must return with lock.
*
* @return boolean returns true if the thread is scheduled to go through the rest
of the
* interceptors. Returns false if the interceptor must try the scheduling
again.
*/
public boolean schedule(MethodInvocation mi)
throws Exception
{
this.sync();
boolean syncAlreadyReleased = false;
try
{
Transaction miTx = mi.getTransaction();
boolean trace = true;//log.isTraceEnabled();
if( trace ) log.trace("Begin schedule, key="+mi.getId());
// Maybe my transaction already expired?
if (miTx != null && miTx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
{
log.error("Saw rolled back tx="+miTx);
throw new RuntimeException("Transaction marked for rollback, possibly a
timeout");
}
//Next test is independent of whether the context is locked or not, it is
purely transactional
// Is the instance involved with another transaction? if so we implement
pessimistic locking
// Do we have a running transaction with the context?
if (tx != null &&
// And are we trying to enter with another transaction?
!tx.equals(miTx))
{
// That's no good, only one transaction per context
// Let's put the thread to sleep the transaction demarcation will wake
them up
if( trace ) log.trace("Transactional contention on context"+id);
try
{
if( trace ) log.trace("Begin wait on Tx="+tx);
// Insert the transaction in the list of waiting tx, and create a lock
if (!txList.contains(miTx))
{
txList.addLast(miTx);
txLocks.put(miTx, new Object());
}
// And lock the threads on the lock corresponding to the Tx in MI
synchronized(txLocks.get(miTx))
{
// We are going to wait on one of the implementation locks
therefore
// we need to release the sync on the BeanLock object itself
syncAlreadyReleased = true;
releaseSync();
//FIXME: use this line when 2.5 is released otherwise
// transaction timeouts will never happen.
// txLocks.get(miTx).wait(txTimeout);
txLocks.get(miTx).wait();
}
if( trace ) log.trace("End wait on TxLock="+tx);
}
// We need to try again
finally {return false;}
}
// The following code should really be done in a superclass of the lock
// it implements the default serialization from the specification
// The next test is the pure serialization from the EJB specification.
// If we are here we either did not have a tx(tx == null) or this is a
// recursive call and the current ctx.tx == mi.tx
// The transaction is good and current.
//Is the context used already (with or without a transaction), is it locked?
if (isMethodLocked())
{
// It is locked but re-entrant calls permitted (reentrant home ones are
ok as well)
if (!isCallAllowed(mi))
{
// This instance is in use and you are not permitted to reenter
// Go to sleep and wait for the lock to be released
if( trace ) log.trace("Thread contention on lock"+id);
// Threads finishing invocation will notify() on the lock
try
{
if( trace ) log.trace("Begin lock.wait(), id="+mi.getId());
synchronized(currentLock)
{
// we're about to wait on a different object, so release synch
on beanlock
// Also, this notify should always be within the synch(lock)
block
syncAlreadyReleased = true;
releaseSync();
//FIXME: use this line when 2.5 is released otherwise
// transaction timeouts will never happen.
// currentLock.wait(txTimeout);
currentLock.wait();
}
}
catch (InterruptedException ignored) {}
// We need to try again
finally
{
if( trace ) log.trace("End lock.wait(), id="+mi.getId()+",
isLocked="+isMethodLocked());
return false;
}
}
else
{
//We are in a valid reentrant call so add a method lock
addMethodLock();
}
}
// No one is using that instance
else
{
// We are now using the instance
addMethodLock();
}
// keep track of the transaction in the lock
tx = miTx;
}
finally
{
if (!syncAlreadyReleased) this.releaseSync();
}
//If we reach here we are properly scheduled to go through so return true
return true;
}
/**
* setTransaction(Transaction tx)
*
* The setTransaction associates a transaction with the lock. The current
transaction is associated
* by the schedule call.
*/
public void setTransaction(Transaction tx){this.tx = tx;}
/*
* nextTransaction()
*
* nextTransaction will
* - set the current tx to null
* - wake up all the threads still waiting on the current lock
* - schedule the next transaction by notifying at least a thread
* - setting the thread with the new transaction so there is no race with
incoming calls
*/
public void nextTransaction()
{
// Make sure nobody is stupid enough to call this method when it isn't
synchronized.
if (!synched) throw new IllegalStateException("call of method nextTransaction
should always be synched!");
tx = null;
// wake everyone on the current lock
synchronized(currentLock) {currentLock.notifyAll();}
// is there a waiting list?
if (!txList.isEmpty())
{
// The new transaction is the next one, important to set it up to avoid
race with
// new incoming calls
this.tx = (Transaction) txList.removeFirst();
//Promote the first transaction FIFO
currentLock = txLocks.remove(this.tx);
//Time to harvest the currentLock has threads waiting
synchronized(currentLock) { currentLock.notify();}
}
// If not just keep the currentLock it is doing fine
}
public Transaction getTransaction()
{
return tx;
}
public boolean isMethodLocked() { return numMethodLocks > 0;}
public int getNumMethodLocks() { return numMethodLocks;}
public void addMethodLock() { numMethodLocks++; }
/**
* releaseMethodLock
*
* if we reach the count of zero it means the instance is free from threads (and
reentrency)
* we wake up the next thread in the currentLock
*/
public void releaseMethodLock()
{
numMethodLocks--;
if (numMethodLocks == 0)
{
//Wake up a thread to do work on the instance within the current transaction
synchronized(currentLock)
{
currentLock.notify();
}
}
}
public void addRef() { refs++;}
public void removeRef() { refs--;}
public int getRefs() { return refs;}
// Private --------------------------------------------------------
private static Method getEJBHome;
private static Method getHandle;
private static Method getPrimaryKey;
private static Method isIdentical;
private static Method remove;
static
{
try
{
Class[] noArg = new Class[0];
getEJBHome = EJBObject.class.getMethod("getEJBHome", noArg);
getHandle = EJBObject.class.getMethod("getHandle", noArg);
getPrimaryKey = EJBObject.class.getMethod("getPrimaryKey", noArg);
isIdentical = EJBObject.class.getMethod("isIdentical", new Class[]
{EJBObject.class});
remove = EJBObject.class.getMethod("remove", noArg);
}
catch (Exception x) {x.printStackTrace();}
}
private boolean isCallAllowed(MethodInvocation mi)
{
if (reentrant)
{
return true;
}
else
{
Method m = mi.getMethod();
if (m.equals(getEJBHome) ||
m.equals(getHandle) ||
m.equals(getPrimaryKey) ||
m.equals(isIdentical) ||
m.equals(remove))
{
return true;
}
}
return false;
}
}
1.1 jboss/src/main/org/jboss/ejb/BeanLockManager.java
Index: BeanLockManager.java
===================================================================
/*
* JBoss, the OpenSource EJB server
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.ejb;
import java.util.HashMap;
import org.jboss.metadata.EntityMetaData;
import org.jboss.tm.TxManager;
/**
* Manages BeanLocks. All BeanLocks have a reference count.
* When the reference count goes to 0, the lock is released from the
* id -> lock mapping.
*
* @author <a href="[EMAIL PROTECTED]">Bill Burke</a>
* @author <a href="[EMAIL PROTECTED]">Marc Fleury</a>
*
* @version $Revision: 1.1 $
*
*/
public class BeanLockManager
{
private HashMap map = new HashMap();
// The container this manager reports to
private Container container;
// Reentrancy of calls
private boolean reentrant = false;
private int txTimeout = 5000;
public void BeanLockManager(Container container)
{
this.container = container;
if (container.getBeanMetaData().isEntity())
reentrant = ((EntityMetaData)container.getBeanMetaData()).isReentrant();
if (container.getTransactionManager() != null)
{
if (container.getTransactionManager() instanceof TxManager)
{
TxManager mgr = (TxManager)container.getTransactionManager();
txTimeout = (mgr.getDefaultTransactionTimeout() * 1000) + 50;
}
}
}
/**
* getLock()
*
* getLock returns the lock associated with the key passed. If there is no lock
one is created
* this call also increments the number of references interested in Lock
* WARNING: All access to this method MUST have an equivalent removeLockRef
cleanup call,
* or this will create a leak in the map,
*
*/
public synchronized BeanLock getLock(Object id)
{
BeanLock lock = (BeanLock)map.get(id);
if (lock == null)
{
lock = new BeanLock(id, reentrant, txTimeout);
map.put(id, lock);
}
lock.addRef();
return lock;
}
public synchronized void removeLockRef(Object id)
{
BeanLock lock = (BeanLock)map.get(id);
if (lock != null)
{
lock.removeRef();
if (lock.getRefs() <= 0)
{
map.remove(lock.getId());
}
}
}
}
_______________________________________________
Jboss-development mailing list
[EMAIL PROTECTED]
http://lists.sourceforge.net/lists/listinfo/jboss-development