User: starksm
Date: 01/07/17 23:11:38
Modified: src/main/org/jboss/ejb/plugins Tag: Branch_2_4
EntityInstanceInterceptor.java
Log:
Fix the busy wait on tx contention by adding a Thread.sleep(1)
Reformat the code to clean up the indent mismatch
Revision Changes Path
No revision
No revision
1.30.2.1 +368 -362
jboss/src/main/org/jboss/ejb/plugins/EntityInstanceInterceptor.java
Index: EntityInstanceInterceptor.java
===================================================================
RCS file:
/cvsroot/jboss/jboss/src/main/org/jboss/ejb/plugins/EntityInstanceInterceptor.java,v
retrieving revision 1.30
retrieving revision 1.30.2.1
diff -u -r1.30 -r1.30.2.1
--- EntityInstanceInterceptor.java 2001/06/15 23:37:05 1.30
+++ EntityInstanceInterceptor.java 2001/07/18 06:11:38 1.30.2.1
@@ -1,9 +1,9 @@
/*
-* JBoss, the OpenSource EJB server
-*
-* Distributable under LGPL license.
-* See terms of license at gnu.org.
-*/
+ * JBoss, the OpenSource EJB server
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
package org.jboss.ejb.plugins;
import java.lang.reflect.Method;
@@ -39,367 +39,373 @@
import org.jboss.util.Sync;
/**
-* This container acquires the given instance.
-*
-* @see <related>
-* @author Rickard Öberg ([EMAIL PROTECTED])
-* @author <a href="mailto:[EMAIL PROTECTED]">Marc Fleury</a>
-* @author <a href="mailto:[EMAIL PROTECTED]">Sebastien Alborini</a>
-* @version $Revision: 1.30 $
-*/
+ * This container acquires the given instance.
+ *
+ * @see <related>
+ * @author Rickard Öberg ([EMAIL PROTECTED])
+ * @author <a href="mailto:[EMAIL PROTECTED]">Marc Fleury</a>
+ * @author <a href="mailto:[EMAIL PROTECTED]">Sebastien Alborini</a>
+ * @version $Revision: 1.30.2.1 $
+ */
public class EntityInstanceInterceptor
extends AbstractInterceptor
{
- // Constants -----------------------------------------------------
-
- // Attributes ----------------------------------------------------
- protected EntityContainer container;
-
- // Static --------------------------------------------------------
-
- // Constructors --------------------------------------------------
-
- // Public --------------------------------------------------------
- public void setContainer(Container container)
- {
- this.container = (EntityContainer)container;
- }
-
- public Container getContainer()
- {
- return container;
- }
-
- // Interceptor implementation --------------------------------------
- public Object invokeHome(MethodInvocation mi)
- throws Exception
- {
- // Get context
- EnterpriseContext ctx =
((EntityContainer)getContainer()).getInstancePool().get();
- mi.setEnterpriseContext(ctx);
-
- // It is a new context for sure so we can lock it
- ctx.lock();
-
- try
- {
- // Invoke through interceptors
- return getNext().invokeHome(mi);
- } finally
- {
- // Always unlock, no matter what
- ctx.unlock();
-
- // Still free? Not free if create() was called successfully
- if (ctx.getId() == null)
+ // Constants -----------------------------------------------------
+
+ // Attributes ----------------------------------------------------
+ protected EntityContainer container;
+
+ // Static --------------------------------------------------------
+
+ // Constructors --------------------------------------------------
+
+ // Public --------------------------------------------------------
+ public void setContainer(Container container)
+ {
+ this.container = (EntityContainer)container;
+ }
+
+ public Container getContainer()
+ {
+ return container;
+ }
+
+ // Interceptor implementation --------------------------------------
+ public Object invokeHome(MethodInvocation mi)
+ throws Exception
+ {
+ // Get context
+ EnterpriseContext ctx =
((EntityContainer)getContainer()).getInstancePool().get();
+ mi.setEnterpriseContext(ctx);
+
+ // It is a new context for sure so we can lock it
+ ctx.lock();
+
+ try
+ {
+ // Invoke through interceptors
+ return getNext().invokeHome(mi);
+ } finally
+ {
+ // Always unlock, no matter what
+ ctx.unlock();
+
+ // Still free? Not free if create() was called successfully
+ if (ctx.getId() == null)
+ {
+ container.getInstancePool().free(ctx);
+ }
+ }
+ }
+
+ public Object invoke(MethodInvocation mi)
+ throws Exception
+ {
+ // The id store is a CacheKey in the case of Entity
+ CacheKey key = (CacheKey)mi.getId();
+
+ // Get cache
+ AbstractInstanceCache cache =
(AbstractInstanceCache)container.getInstanceCache();
+ BeanSemaphore mutex = (BeanSemaphore)cache.getLock(key);
+
+ EnterpriseContext ctx = null;
+ boolean exceptionThrown = false;
+
+ try
+ {
+ boolean waitingOnTransaction = false; // So we don't output
LOCKING-WAITING all the time
+ boolean waitingOnContext = false; // So we don't output LOCKING-WAITING
all the time
+ do
+ {
+ if (mi.getTransaction() != null && mi.getTransaction().getStatus() ==
Status.STATUS_MARKED_ROLLBACK)
+ throw new RuntimeException("Transaction marked for rollback,
possibly a timeout");
+
+ try
{
- container.getInstancePool().free(ctx);
+ // Don't let contending thread spin on the cpu
+ if( waitingOnTransaction == true )
+ Thread.sleep(1);
+
+ mutex.acquire();
+
+ // This loop guarantees a mutex lock for the specified object id
+ // When a cache.remove happens, it disassociates the mutex from the
bean's id,
+ // Thus, the mutex in this code could be invalid. We avoid this
problem with the following loop.
+ //
+ // Also we should have a while loop so we don't mess up the finally
clause that does
+ // mutex.release()
+ //
+ while (!mutex.isValid())
+ {
+ BeanSemaphore newmutex = (BeanSemaphore)cache.getLock(key);
+ mutex.release();
+ mutex = newmutex;
+
+ // Avoid infinite loop.
+ if (mi.getTransaction() != null &&
mi.getTransaction().getStatus() == Status.STATUS_MARKED_ROLLBACK)
+ throw new RuntimeException("Transaction marked for rollback,
possibly a timeout");
+
+ mutex.acquire();
+ }
+ // Get context
+ ctx = cache.get(key);
+
+ // Do we have a running transaction with the context
+ Transaction tx = ctx.getTransaction();
+ if (tx != null &&
+ // And are we trying to enter with another transaction
+ !tx.equals(mi.getTransaction()))
+ {
+ // Let's put the thread to sleep a lock release will wake the
thread
+ // Possible deadlock
+ if (!waitingOnTransaction)
+ {
+ Logger.debug("LOCKING-WAITING (TRANSACTION) in Thread "
+ + Thread.currentThread().getName()
+ + " for id "+ctx.getId()+" ctx.hash "+ctx.hashCode()
+ +" tx:"+((tx == null) ? "null" : tx.toString()));
+ waitingOnTransaction = true;
+ }
+
+ // Try your luck again
+ ctx = null;
+ continue;
+ }
+ if (waitingOnTransaction)
+ {
+ Logger.debug("FINISHED-LOCKING-WAITING (TRANSACTION) in Thread "
+ + Thread.currentThread().getName()
+ + " for id "+ctx.getId()
+ +" ctx.hash "+ctx.hashCode()
+ +" tx:"+((tx == null) ? "null" : tx.toString()));
+ waitingOnTransaction = false;
+ }
+ // OK so we now know that for this PrimaryKey, no other
+ // thread has a transactional lock on the bean.
+ // So, let's setTransaction of the ctx here instead of in later code.
+ // I really don't understand why it wasn't done here anyways before.
+ // Later on, in the finally clause, I'll check to see if the ctx was
+ // invoked upon and if not, dissassociate the transaction with the
ctx.
+ // If there is no transactional assocation here, there is a race
condition
+ // for re-entrant entity beans, so don't remove the code below.
+ //
+ // If this "waitingOnTransaction" loop is ever removed in favor of
+ // something like using db locks instead, this transactional
assocation
+ // must be removed.
+ if (mi.getTransaction() != null && tx != null &&
!tx.equals(mi.getTransaction()))
+ {
+ // Do transactional "lock" on ctx right now!
+ ctx.setTransaction(mi.getTransaction());
+ }
+ // If we get here it's the right tx, or no tx
+ if (!ctx.isLocked())
+ {
+ //take it!
+ ctx.lock();
+ }
+ else
+ {
+ if (!isCallAllowed(mi))
+ {
+ // Go to sleep and wait for the lock to be released
+ // This is not one of the "home calls" so we need to wait for
the lock
+
+ // Possible deadlock
+ if (!waitingOnContext)
+ {
+ Logger.debug("LOCKING-WAITING (CTX) in Thread "
+ + Thread.currentThread().getName()
+ + " for id "+ctx.getId()
+ +" ctx.hash "+ctx.hashCode());
+ waitingOnContext = true;
+ }
+
+ // Try your luck again
+ ctx = null;
+ Thread.yield(); // Help out the OS.
+ continue;
+ }
+ else
+ {
+ //We are in a home call so take the lock, take it!
+ ctx.lock();
+ }
+ }
+ if (waitingOnContext)
+ {
+ Logger.debug("FINISHED-LOCKING-WAITING (CTX) in Thread "
+ + Thread.currentThread().getName()
+ + " for id "
+ + ctx.getId()
+ + " ctx.hash " + ctx.hashCode());
+ waitingOnContext = false;
+ }
}
- }
- }
-
- public Object invoke(MethodInvocation mi)
- throws Exception
- {
- // The id store is a CacheKey in the case of Entity
- CacheKey key = (CacheKey)mi.getId();
-
- // Get cache
- AbstractInstanceCache cache =
(AbstractInstanceCache)container.getInstanceCache();
- BeanSemaphore mutex = (BeanSemaphore)cache.getLock(key);
-
- EnterpriseContext ctx = null;
- boolean exceptionThrown = false;
-
- try
- {
- boolean waitingOnTransaction = false; // So we don't output
LOCKING-WAITING all the time
- boolean waitingOnContext = false; // So we don't output LOCKING-WAITING
all the time
- do
+ catch (InterruptedException ignored)
+ {}
+ finally
{
- if (mi.getTransaction() != null && mi.getTransaction().getStatus()
== Status.STATUS_MARKED_ROLLBACK)
- throw new RuntimeException("Transaction marked for rollback,
possibly a timeout");
-
- try
- {
-
- mutex.acquire();
-
- // This loop guarantees a mutex lock for the specified object id
- // When a cache.remove happens, it disassociates the mutex from
the bean's id,
- // Thus, the mutex in this code could be invalid. We avoid this
problem with the following loop.
- //
- // Also we should have a while loop so we don't mess up the
finally clause that does
- // mutex.release()
- //
- while (!mutex.isValid())
- {
- BeanSemaphore newmutex = (BeanSemaphore)cache.getLock(key);
- mutex.release();
- mutex = newmutex;
-
- // Avoid infinite loop.
- if (mi.getTransaction() != null &&
mi.getTransaction().getStatus() == Status.STATUS_MARKED_ROLLBACK)
- throw new RuntimeException("Transaction marked for
rollback, possibly a timeout");
-
- mutex.acquire();
- }
- // Get context
- ctx = cache.get(key);
-
- // Do we have a running transaction with the context
- Transaction tx = ctx.getTransaction();
- if (tx != null &&
- // And are we trying to enter with another transaction
- !tx.equals(mi.getTransaction()))
- {
- // Let's put the thread to sleep a lock release will wake
the thread
- // Possible deadlock
- if (!waitingOnTransaction)
- {
- Logger.debug("LOCKING-WAITING (TRANSACTION) in Thread "
- + Thread.currentThread().getName()
- + " for id "+ctx.getId()+" ctx.hash
"+ctx.hashCode()
- +" tx:"+((tx == null) ? "null" :
tx.toString()));
- waitingOnTransaction = true;
- }
-
- // Try your luck again
- ctx = null;
- Thread.yield(); // Give the OS some help.
- continue;
- }
- if (waitingOnTransaction)
- {
- Logger.debug("FINISHED-LOCKING-WAITING (TRANSACTION) in Thread
"
- + Thread.currentThread().getName()
- + " for id "+ctx.getId()
- +" ctx.hash "+ctx.hashCode()
- +" tx:"+((tx == null) ? "null" : tx.toString()));
- waitingOnTransaction = false;
- }
- // OK so we now know that for this PrimaryKey, no other
- // thread has a transactional lock on the bean.
- // So, let's setTransaction of the ctx here instead of in later
code.
- // I really don't understand why it wasn't done here anyways
before.
- // Later on, in the finally clause, I'll check to see if the ctx
was
- // invoked upon and if not, dissassociate the transaction with the
ctx.
- // If there is no transactional assocation here, there is a race
condition
- // for re-entrant entity beans, so don't remove the code below.
- //
- // If this "waitingOnTransaction" loop is ever removed in favor of
- // something like using db locks instead, this transactional
assocation
- // must be removed.
- if (mi.getTransaction() != null && tx != null &&
!tx.equals(mi.getTransaction()))
- {
- // Do transactional "lock" on ctx right now!
- ctx.setTransaction(mi.getTransaction());
- }
- // If we get here it's the right tx, or no tx
- if (!ctx.isLocked())
- {
- //take it!
- ctx.lock();
- }
- else
- {
- if (!isCallAllowed(mi))
- {
- // Go to sleep and wait for the lock to be released
- // This is not one of the "home calls" so we need to
wait for the lock
-
- // Possible deadlock
- if (!waitingOnContext)
- {
- Logger.debug("LOCKING-WAITING (CTX) in Thread "
- + Thread.currentThread().getName()
- + " for id "+ctx.getId()
- +" ctx.hash "+ctx.hashCode());
- waitingOnContext = true;
- }
-
- // Try your luck again
- ctx = null;
- Thread.yield(); // Help out the OS.
- continue;
- }
- else
- {
- //We are in a home call so take the lock, take it!
- ctx.lock();
- }
- }
- if (waitingOnContext)
- {
- Logger.debug("FINISHED-LOCKING-WAITING (CTX) in Thread "
- + Thread.currentThread().getName()
- + " for id "
- + ctx.getId()
- + " ctx.hash " + ctx.hashCode());
- waitingOnContext = false;
- }
- }
- catch (InterruptedException ignored) {}
- finally
- {
- mutex.release();
- }
- } while (ctx == null);
-
- // Set context on the method invocation
- mi.setEnterpriseContext(ctx);
-
- // Go on, you won
- return getNext().invoke(mi);
-
- }
- catch (RemoteException e)
- {
- exceptionThrown = true;
- throw e;
- } catch (RuntimeException e)
- {
- exceptionThrown = true;
- throw e;
- } catch (Error e)
- {
- exceptionThrown = true;
- throw e;
- }
- finally
- {
- try
- {
- mutex.acquire();
- // Logger.debug("Release instance for "+id);
- // ctx can be null if cache.get throws an Exception, for
- // example when activating a bean.
- if (ctx != null)
- {
- // unlock the context
- ctx.unlock();
-
- Transaction tx = ctx.getTransaction();
- if (tx != null
- && mi.getTransaction() != null
- && tx.equals(mi.getTransaction())
- && !((EntityEnterpriseContext)ctx).isInvoked())
- {
- // The context has been associated with this method's
transaction
- // but the entity has not been invoked upon yet, so let's
- // disassociate the transaction from the ctx.
- // I'm doing this because I'm assuming that the bean hasn't
been registered with
- // the TxManager.
- ctx.setTransaction(null);
- }
-
- // If an exception has been thrown, DO NOT remove the ctx
- // if the ctx has been registered in an InstanceSynchronization.
- // InstanceSynchronization will remove the key for us
- if (exceptionThrown &&
- (tx == null || (mi.getTransaction() != null &&
!((EntityEnterpriseContext)ctx).isInvoked())))
- {
- // Discard instance
- // EJB 1.1 spec 12.3.1
- //
- cache.remove(key);
- }
- else if (ctx.getId() == null)
- {
- // Work only if no transaction was encapsulating this remove()
- if (ctx.getTransaction() == null)
- {
- // Here we arrive if the bean has been removed and no
- // transaction was associated with the remove, or if
- // the bean has been passivated
-
- // Remove from cache
- cache.remove(key);
-
- // It has been removed -> send to the pool
- // REVISIT: FIXME:
- // We really should only let passivation free an instance
because it is
- // quite possible that another thread is working with
- // the same context, but let's do it anyways.
- //
- container.getInstancePool().free(ctx);
- }
- else
- {
- // We want to remove the bean, but it has a Tx associated
with
- // the remove() method. We remove it from the cache, to
avoid
- // that a successive insertion with same pk will break the
- // cache. Anyway we don't free the context, since the tx
must
- // finish. The EnterpriseContext instance will be GC and
not
- // recycled.
- cache.remove(key);
- }
- }
- }
- else
- {
- /*
- * This used to happen in the old code.
- * Why? If the ctx is null, why should we remove it? Another
thread could
- * be screwed up by this.
- if (exceptionThrown)
- {
- // Discard instance
- // EJB 1.1 spec 12.3.1
- cache.remove(key);
- }
- */
- }
- }
- finally
- {
- mutex.release();
- }
- }
- }
-
- // 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)
- {
- boolean reentrant =
((EntityMetaData)container.getBeanMetaData()).isReentrant();
-
- 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;
- }
+ mutex.release();
+ }
+ } while (ctx == null);
+
+ // Set context on the method invocation
+ mi.setEnterpriseContext(ctx);
+
+ // Go on, you won
+ return getNext().invoke(mi);
+
+ }
+ catch (RemoteException e)
+ {
+ exceptionThrown = true;
+ throw e;
+ } catch (RuntimeException e)
+ {
+ exceptionThrown = true;
+ throw e;
+ } catch (Error e)
+ {
+ exceptionThrown = true;
+ throw e;
+ }
+ finally
+ {
+ try
+ {
+ mutex.acquire();
+ // Logger.debug("Release instance for "+id);
+ // ctx can be null if cache.get throws an Exception, for
+ // example when activating a bean.
+ if (ctx != null)
+ {
+ // unlock the context
+ ctx.unlock();
+
+ Transaction tx = ctx.getTransaction();
+ if (tx != null
+ && mi.getTransaction() != null
+ && tx.equals(mi.getTransaction())
+ && !((EntityEnterpriseContext)ctx).isInvoked())
+ {
+ // The context has been associated with this method's transaction
+ // but the entity has not been invoked upon yet, so let's
+ // disassociate the transaction from the ctx.
+ // I'm doing this because I'm assuming that the bean hasn't been
registered with
+ // the TxManager.
+ ctx.setTransaction(null);
+ }
+
+ // If an exception has been thrown, DO NOT remove the ctx
+ // if the ctx has been registered in an InstanceSynchronization.
+ // InstanceSynchronization will remove the key for us
+ if (exceptionThrown &&
+ (tx == null || (mi.getTransaction() != null &&
!((EntityEnterpriseContext)ctx).isInvoked())))
+ {
+ // Discard instance
+ // EJB 1.1 spec 12.3.1
+ //
+ cache.remove(key);
+ }
+ else if (ctx.getId() == null)
+ {
+ // Work only if no transaction was encapsulating this remove()
+ if (ctx.getTransaction() == null)
+ {
+ // Here we arrive if the bean has been removed and no
+ // transaction was associated with the remove, or if
+ // the bean has been passivated
+
+ // Remove from cache
+ cache.remove(key);
+
+ // It has been removed -> send to the pool
+ // REVISIT: FIXME:
+ // We really should only let passivation free an instance
because it is
+ // quite possible that another thread is working with
+ // the same context, but let's do it anyways.
+ //
+ container.getInstancePool().free(ctx);
+ }
+ else
+ {
+ // We want to remove the bean, but it has a Tx associated with
+ // the remove() method. We remove it from the cache, to avoid
+ // that a successive insertion with same pk will break the
+ // cache. Anyway we don't free the context, since the tx must
+ // finish. The EnterpriseContext instance will be GC and not
+ // recycled.
+ cache.remove(key);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * This used to happen in the old code.
+ * Why? If the ctx is null, why should we remove it? Another thread could
+ * be screwed up by this.
+ if (exceptionThrown)
+ {
+ // Discard instance
+ // EJB 1.1 spec 12.3.1
+ cache.remove(key);
+ }
+ */
+ }
+ }
+ finally
+ {
+ mutex.release();
+ }
+ }
+ }
+
+ // 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)
+ {
+ boolean reentrant =
((EntityMetaData)container.getBeanMetaData()).isReentrant();
+
+ 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;
+ }
}
_______________________________________________
Jboss-development mailing list
[EMAIL PROTECTED]
http://lists.sourceforge.net/lists/listinfo/jboss-development