ozeigermann    2005/01/08 10:55:09

  Modified:    transaction/src/java/org/apache/commons/transaction/locking
                        GenericLockManager.java
  Log:
  - Moved management for waiters into GenericLock
  - Fixed deadlock detection to find indirect deadlocks as well by traversing 
the whole dependency graph for cycles
  
  Revision  Changes    Path
  1.15      +106 -119  
jakarta-commons/transaction/src/java/org/apache/commons/transaction/locking/GenericLockManager.java
  
  Index: GenericLockManager.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-commons/transaction/src/java/org/apache/commons/transaction/locking/GenericLockManager.java,v
  retrieving revision 1.14
  retrieving revision 1.15
  diff -u -r1.14 -r1.15
  --- GenericLockManager.java   7 Jan 2005 23:24:03 -0000       1.14
  +++ GenericLockManager.java   8 Jan 2005 18:55:09 -0000       1.15
  @@ -23,6 +23,7 @@
   
   package org.apache.commons.transaction.locking;
   
  +import java.util.ArrayList;
   import java.util.Collection;
   import java.util.Collections;
   import java.util.HashMap;
  @@ -38,7 +39,7 @@
    * <ul>
    * <li>deadlock detection, which is configurable to come into effect after 
an initial short waiting
    * lock request; this is useful as it is somewhat expensive
  - * <li>global transaction timeouts that actively revok granted rights from 
transactions
  + * <li>global transaction timeouts that actively revoke granted rights from 
transactions
    * </ul>
    * 
    * @version $Revision$
  @@ -48,9 +49,6 @@
       public static final long DEFAULT_TIMEOUT = 30000;
       public static final long DEFAULT_CHECK_THRESHHOLD = 500;
       
  -    /** Maps lock to ownerIds waiting for it. */
  -    protected Map waitsForLock = Collections.synchronizedMap(new HashMap());
  -
       /** Maps onwerId to locks it (partially) owns. */
       protected Map globalOwners = Collections.synchronizedMap(new HashMap());
   
  @@ -60,9 +58,6 @@
       /** Maps onwerId to global effective time outs (i.e. the time the lock 
will time out). */
       protected Map effectiveGlobalTimeouts = Collections.synchronizedMap(new 
HashMap());
   
  -    /** Maps onwerId to global time outs (i.e. the miliseconds before 
timeout). */
  -    protected Map globalTimeouts = Collections.synchronizedMap(new 
HashMap());
  -
       protected Set timedOutOwners = Collections.synchronizedSet(new 
HashSet());
       
       protected int maxLockLevel = -1;
  @@ -109,22 +104,12 @@
       }
   
       /**
  -     * Sets a global timeout for an owner. This is especially usefull, when 
the owner is a 
  -     * transaction. After a global timeout occurs all of the owner's lock 
will be released and 
  -     * the owner will not be allowed to access any
  -     * locks before before calling [EMAIL PROTECTED] #releaseAll(Object)}.
  -     * 
  -     * @param ownerId
  -     *            a unique id identifying the entity that wants to acquire 
this
  -     *            lock
  -     * @param timeoutMSecs
  -     *            specifies the global timeout in milliseconds
  +     * @see LockManager2#startGlobalTimeout(Object, long)
        */
  -    public void setGlobalTimeout(Object ownerId, long timeoutMSecs) {
  +    public void startGlobalTimeout(Object ownerId, long timeoutMSecs) {
           long now = System.currentTimeMillis();
           long timeout = now + timeoutMSecs;
           effectiveGlobalTimeouts.put(ownerId, new Long(timeout));
  -        globalTimeouts.put(ownerId, new Long(timeoutMSecs));
       }
       
       /**
  @@ -182,12 +167,9 @@
           //    we are applying for this special lock before we check for 
deadlocks ourselves; this
           //    is important as the other thread might be the one to discover 
the deadlock
           
  -        // (b) register us as a waiter before actually trying, so other 
threads take us into account
  -        // XXX: This may however mean both deadlocking parts detect the 
deadlock simultaneously,
  -        // and both will be rolled back. The (worse) alternative, however, 
is that we add us
  -        // as a waiter after deadlock check which may mean we do not detect 
the deadlock at all
  -        addWaiter(lock, ownerId);
  -
  +        GenericLock.LockOwner lockWaiter = new 
GenericLock.LockOwner(ownerId, targetLockLevel,
  +                compatibility, preferred);
  +        
           try {
               boolean acquired = false;
               
  @@ -199,51 +181,46 @@
                   acquired = lock
                           .acquire(ownerId, targetLockLevel, true, 
compatibility,
                                   preferred, checkThreshhold);
  -                if (acquired) {
  -                    addOwner(ownerId, lock);
  -                    return;
  -                } else {
  -                    timeoutMSecs -= checkThreshhold;
  -                }
  +                timeoutMSecs -= checkThreshhold;
  +            } else {
  +                acquired = lock
  +                .acquire(ownerId, targetLockLevel, false, compatibility,
  +                        preferred, checkThreshhold);
  +                
  +            }
  +            if (acquired) {
  +                addOwner(ownerId, lock);
  +                return;
               }
               
  +            lock.registerWaiter(lockWaiter);
  +            
               while (!acquired && waitEnd > now) {
               
                   // first be sure all locks are stolen from owners that have 
already timed out
                   releaseTimedOutOwners();
   
  -                // (a) while we are checking if we can have this lock, no 
one else must apply for it
  -                // and possibly change the data
  -                synchronized (lock) {
  -                    
  -                    // let's see if any of the conflicting owners waits for 
us, if so we
  -                    // have a deadlock
  -    
  -                    Set conflicts = lock.getConflictingOwners(ownerId, 
targetLockLevel,
  -                            compatibility);
  -
  -                    boolean deadlock = wouldDeadlock(ownerId, lock, 
targetLockLevel,
  -                            compatibility, conflicts);
  -                    if (deadlock) {
  -                        throw new LockException("Lock would cause deadlock",
  -                                LockException.CODE_DEADLOCK_VICTIM, 
resourceId);
  -                    }
  +                boolean deadlock = wouldDeadlock(ownerId, new HashSet());
  +                if (deadlock) {
  +                    throw new LockException("Lock would cause deadlock",
  +                            LockException.CODE_DEADLOCK_VICTIM, resourceId);
  +                }
   
  -                    // if there are owners we conflict with lets see if one 
of them globally times
  -                    // out earlier than this lock, if so we will wake up 
then to check again
  -                    long nextConflictTimeout = 
getNextGlobalConflictTimeout(conflicts);
  -                    if (nextConflictTimeout != -1 && nextConflictTimeout < 
waitEnd) {
  -                        timeoutMSecs = nextConflictTimeout - now;
  -                        // XXX add 10% to ensure the lock really is timed out
  -                        timeoutMSecs += timeoutMSecs / 10;
  -                    } else {
  -                        timeoutMSecs = waitEnd - now;
  -                    }
  -            
  -                    acquired = lock.acquire(ownerId, targetLockLevel, true, 
compatibility,
  -                            preferred, timeoutMSecs);
  -                    
  +                // if there are owners we conflict with lets see if one of 
them globally times
  +                // out earlier than this lock, if so we will wake up then to 
check again
  +                Set conflicts = lock.getConflictingOwners(ownerId, 
targetLockLevel, compatibility);
  +                long nextConflictTimeout = 
getNextGlobalConflictTimeout(conflicts);
  +                if (nextConflictTimeout != -1 && nextConflictTimeout < 
waitEnd) {
  +                    timeoutMSecs = nextConflictTimeout - now;
  +                    // XXX add 10% to ensure the lock really is timed out
  +                    timeoutMSecs += timeoutMSecs / 10;
  +                } else {
  +                    timeoutMSecs = waitEnd - now;
                   }
  +        
  +                acquired = lock.acquire(ownerId, targetLockLevel, true, 
compatibility,
  +                        preferred, timeoutMSecs);
  +                
               }
               if (!acquired) {
                   throw new LockException("Lock wait timed out", 
LockException.CODE_TIMED_OUT,
  @@ -254,7 +231,7 @@
           } catch (InterruptedException e) {
               throw new LockException("Interrupted", 
LockException.CODE_INTERRUPTED, resourceId);
           } finally {
  -            removeWaiter(lock, ownerId);
  +            lock.unregisterWaiter(lockWaiter);
           }
       }
   
  @@ -285,9 +262,13 @@
        * @see LockManager2#releaseAll(Object)
        */
       public void releaseAll(Object ownerId) {
  -        // XXX even if we are timed out we can still have
  -        // locks acquired because we might have been waiting for one
  -        // while we were set to timed out
  +        releaseAllNoTimeOutReset(ownerId);
  +        // reset time out status for this owner
  +        timedOutOwners.remove(ownerId);
  +        effectiveGlobalTimeouts.remove(ownerId);
  +    }
  +
  +    protected void releaseAllNoTimeOutReset(Object ownerId) {
           Set locks = (Set) globalOwners.get(ownerId);
           if (locks != null) {
               synchronized (locks) {
  @@ -298,15 +279,8 @@
                   }
               }
           }
  -        // reset time out status for this owner
  -        timedOutOwners.remove(ownerId);
  -        // and start a new time out cycle
  -        Long timeOut = (Long) globalTimeouts.get(ownerId);
  -        if (timeOut != null) {
  -            setGlobalTimeout(ownerId, timeOut.longValue());
  -        }
       }
  -
  +    
       /**
        * @see LockManager2#getAll(Object)
        */
  @@ -337,52 +311,55 @@
           }
       }
   
  -    protected void addWaiter(GenericLock lock, Object ownerId) {
  -        synchronized (waitsForLock) {
  -            Set waiters = (Set) waitsForLock.get(lock);
  -            if (waiters == null) {
  -                waiters = Collections.synchronizedSet(new HashSet());
  -                waitsForLock.put(lock, waiters);
  +    /**
  +     * Checks if an owner is deadlocked. <br>
  +     * <br>
  +     * We traverse the tree recursively formed by owners, locks held by them 
and
  +     * then again owners waiting for the locks. If there is a cycle in one of
  +     * the paths from the root to a leaf we have a deadlock. <br>
  +     * <br>
  +     * A more detailed discussion on deadlocks and definitions and how to 
detect
  +     * them can be found in <a
  +     * 
href="http://www.onjava.com/pub/a/onjava/2004/10/20/threads2.html?page=1";>this
  +     * nice article </a>. <br>
  +     * <em>Caution:</em> This computation can be very expensive with many
  +     * owners and locks. Worst (unlikely) case is exponential.
  +     * 
  +     * @param ownerId
  +     *            the owner to check for being deadlocked
  +     * @param path
  +     *            initially should be called with an empty set
  +     * @return <code>true</code> if the owner is deadlocked,
  +     *         <code>false</code> otherwise
  +     */
  +    protected boolean wouldDeadlock(Object ownerId, Set path) {
  +        path.add(ownerId);
  +        // these are our locks
  +        Set locks = (Set) globalOwners.get(ownerId);
  +        if (locks != null) {
  +            Collection locksCopy;
  +            // need to copy in order not to interfere with releaseAll 
possibly called by
  +            // other threads
  +            synchronized (locks) {
  +                locksCopy = new ArrayList(locks);
               }
  -            waiters.add(ownerId);
  -        }
  -    }
  -
  -    protected void removeWaiter(GenericLock lock, Object ownerId) {
  -        Set waiters = (Set) waitsForLock.get(lock);
  -        if (waiters != null) {
  -            waiters.remove(ownerId);
  -        }
  -    }
  -
  -    // TODO this does not detect indirect deadlocks where we would be 
deadlocked by 
  -    // an owner we wait for that waits for a lock that is owner by a third 
owner that
  -    // waits for us
  -    protected boolean wouldDeadlock(Object ownerId, GenericLock lock, int 
targetLockLevel,
  -            int compatibility, Set conflicts) {
  -        if (conflicts != null) {
  -            // these are our locks
  -            Set locks = (Set) globalOwners.get(ownerId);
  -            if (locks != null) {
  -                for (Iterator i = locks.iterator(); i.hasNext();) {
  -                    GenericLock mylock = (GenericLock) i.next();
  -                    // these are the ones waiting for one of our locks
  -                    Set waiters = (Set) waitsForLock.get(mylock);
  -                    if (waiters != null) {
  -                        synchronized (waiters) {
  -                            for (Iterator j = waiters.iterator(); 
j.hasNext();) {
  -                                Object waitingOwnerId = j.next();
  -                                // if someone waiting for one of our locks 
would make us wait
  -                                // this is a deadlock
  -                                if (conflicts.contains(waitingOwnerId))
  -                                    return true;
  -        
  -                            }
  +            for (Iterator i = locksCopy.iterator(); i.hasNext();) {
  +                GenericLock mylock = (GenericLock) i.next();
  +                // these are the ones waiting for one of our locks
  +                Collection conflicts = mylock.getConflictingWaiters(ownerId);
  +                if (conflicts != null) {
  +                    for (Iterator j = conflicts.iterator(); j.hasNext();) {
  +                        Object waitingOwnerId = j.next();
  +                        if (path.contains(waitingOwnerId)) {
  +                            return true;
  +                        } else if (wouldDeadlock(waitingOwnerId, path)) {
  +                            return true;
                           }
                       }
                   }
               }
           }
  +        path.remove(ownerId);
           return false;
       }
   
  @@ -392,10 +369,10 @@
               for (Iterator it = 
effectiveGlobalTimeouts.entrySet().iterator(); it.hasNext();) {
                   Map.Entry entry = (Map.Entry) it.next();
                   Object ownerId = entry.getKey();
  -                long timeout = ((Long)entry.getValue()).longValue();
  +                long timeout = ((Long) entry.getValue()).longValue();
                   long now = System.currentTimeMillis();
                   if (timeout < now) {
  -                    releaseAll(ownerId);
  +                    releaseAllNoTimeOutReset(ownerId);
                       timedOutOwners.add(ownerId);
                       released = true;
                   }
  @@ -469,6 +446,15 @@
           }
       }
   
  +    public synchronized String toString() {
  +        StringBuffer buf = new StringBuffer(1000);
  +        for (Iterator it = globalLocks.values().iterator(); it.hasNext();) {
  +            GenericLock lock = (GenericLock) it.next();
  +            buf.append(lock.toString()).append('\n');
  +        }
  +        return buf.toString();
  +    }
  +
       protected GenericLock createLock(Object resourceId) {
           synchronized (globalLocks) {
               GenericLock lock = new GenericLock(resourceId, maxLockLevel, 
logger);
  @@ -489,4 +475,5 @@
           }
       }
   
  +    
   }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to