ozeigermann    2004/12/18 19:07:04

  Modified:    transaction/src/java/org/apache/commons/transaction/locking
                        GenericLockManager.java
  Log:
  Added global timeouts and means for deferred (more performing)
  deadlock checking
  
  Revision  Changes    Path
  1.6       +170 -40   
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.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- GenericLockManager.java   17 Dec 2004 16:36:21 -0000      1.5
  +++ GenericLockManager.java   19 Dec 2004 03:07:04 -0000      1.6
  @@ -41,6 +41,7 @@
   public class GenericLockManager implements LockManager {
   
       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());
  @@ -51,30 +52,47 @@
       /** Maps resourceId to lock. */
       protected Map globalLocks = new HashMap();
       
  +    /** Maps onwerId to global time outs. */
  +    protected Map globalTimeouts = Collections.synchronizedMap(new 
HashMap());
  +
  +    protected Set timedOutOwners = Collections.synchronizedSet(new 
HashSet());
  +    
       protected int maxLockLevel = -1;
       protected LoggerFacade logger;
       protected long globalTimeoutMSecs;
  +    protected long checkThreshhold;
       
       /**
        * Creates a new generic lock manager.
        * 
        * @param maxLockLevel
  -     *            highest allowed lock level as described in [EMAIL 
PROTECTED] GenericLock}'s class intro
  +     *            highest allowed lock level as described in [EMAIL 
PROTECTED] GenericLock}
  +     *            's class intro
        * @param logger
        *            generic logger used for all kind of debug logging
        * @param timeoutMSecs
        *            specifies the maximum time to wait for a lock in 
milliseconds
  +     * @param checkThreshholdMSecs
  +     *            specifies a special wait threshhold before deadlock and
  +     *            timeout detection come into play or <code>-1</code> switch
  +     *            it off and check for directly
        * @throws IllegalArgumentException
        *             if maxLockLevel is less than 1
        */
  -    public GenericLockManager(int maxLockLevel, LoggerFacade logger, long 
timeoutMSecs)
  -            throws IllegalArgumentException {
  +    public GenericLockManager(int maxLockLevel, LoggerFacade logger, long 
timeoutMSecs,
  +            long checkThreshholdMSecs) throws IllegalArgumentException {
           if (maxLockLevel < 1)
               throw new IllegalArgumentException("The maximum lock level must 
be at least 1 ("
                       + maxLockLevel + " was specified)");
           this.maxLockLevel = maxLockLevel;
           this.logger = logger.createLogger("Locking");
           this.globalTimeoutMSecs = timeoutMSecs;
  +        this.checkThreshhold = checkThreshholdMSecs;
  +    }
  +
  +    public GenericLockManager(int maxLockLevel, LoggerFacade logger, long 
timeoutMSecs)
  +            throws IllegalArgumentException {
  +        this(maxLockLevel, logger, timeoutMSecs, DEFAULT_CHECK_THRESHHOLD);
       }
   
       public GenericLockManager(int maxLockLevel, LoggerFacade logger)
  @@ -82,10 +100,24 @@
           this(maxLockLevel, logger, DEFAULT_TIMEOUT);
       }
   
  +
  +    public void setGlobalTimeout(Object ownerId, long timeoutMSecs) {
  +        long now = System.currentTimeMillis();
  +        long timeout = now + timeoutMSecs;
  +        globalTimeouts.put(ownerId, new Long(timeout));
  +    }
  +    
  +    public long getGlobalTimeoutTime(Object ownerId) {
  +        Long timeout = (Long) globalTimeouts.get(ownerId);
  +        return timeout.longValue();
  +    }
  +    
       /**
        * @see LockManager#tryLock(Object, Object, int, boolean)
        */
       public boolean tryLock(Object ownerId, Object resourceId, int 
targetLockLevel, boolean reentrant) {
  +        timeoutCheck(ownerId);
  +
           GenericLock lock = (GenericLock) atomicGetOrCreateLock(resourceId);
           boolean acquired = lock.tryLock(ownerId, targetLockLevel,
                   reentrant ? GenericLock.COMPATIBILITY_REENTRANT : 
GenericLock.COMPATIBILITY_NONE,
  @@ -111,6 +143,11 @@
       public void lock(Object ownerId, Object resourceId, int targetLockLevel, 
boolean reentrant,
               long timeoutMSecs) throws LockException {
   
  +        timeoutCheck(ownerId);
  +        
  +        long now = System.currentTimeMillis();
  +        long waitEnd = now + timeoutMSecs;
  +
           GenericLock lock = (GenericLock) atomicGetOrCreateLock(resourceId);
   
           // we need to be careful that we the detected deadlock status is 
still valid when actually
  @@ -126,23 +163,60 @@
           addWaiter(lock, ownerId);
   
           try {
  -            boolean acquired;
  -            // (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) {
  -                
  -                // TODO: detection is rather expensive, would be an idea to 
wait for a 
  -                // short time (<5 seconds) to see if we get the lock, after 
that we can still check
  -                // for the deadlock and if not try with the remaining 
timeout time
  -                boolean deadlock = wouldDeadlock(ownerId, lock, 
targetLockLevel,
  -                        reentrant ? GenericLock.COMPATIBILITY_REENTRANT : 
GenericLock.COMPATIBILITY_NONE);
  -                if (deadlock) {
  -                    throw new LockException("Lock would cause deadlock",
  -                            LockException.CODE_DEADLOCK_VICTIM, resourceId);
  -                }
  -        
  +            boolean acquired = false;
  +            
  +            // detection for deadlocks and time outs is rather expensive, 
  +            // so we wait for the lock for a  
  +            // short time (<5 seconds) to see if we get it without checking;
  +            // if not we still can check what the reason for this is
  +            if (checkThreshhold != -1 && timeoutMSecs > checkThreshhold) {
                   acquired = lock
  -                        .acquire(ownerId, targetLockLevel, true, reentrant, 
timeoutMSecs);
  +                        .acquire(ownerId, targetLockLevel, true, reentrant, 
checkThreshhold);
  +                if (acquired) {
  +                    addOwner(ownerId, lock);
  +                    return;
  +                } else {
  +                    timeoutMSecs -= checkThreshhold;
  +                }
  +            }
  +            
  +            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,
  +                            reentrant ? GenericLock.COMPATIBILITY_REENTRANT
  +                                    : GenericLock.COMPATIBILITY_NONE);
  +
  +                    boolean deadlock = wouldDeadlock(ownerId, lock, 
targetLockLevel,
  +                            reentrant ? GenericLock.COMPATIBILITY_REENTRANT
  +                                    : GenericLock.COMPATIBILITY_NONE, 
conflicts);
  +                    if (deadlock) {
  +                        throw new LockException("Lock would cause deadlock",
  +                                LockException.CODE_DEADLOCK_VICTIM, 
resourceId);
  +                    }
  +
  +                    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, 
reentrant, timeoutMSecs);
  +                    
  +                }
               }
               if (!acquired) {
                   throw new LockException("Lock wait timed out", 
LockException.CODE_TIMED_OUT,
  @@ -161,6 +235,7 @@
        * @see LockManager#getLevel(Object, Object)
        */
       public int getLevel(Object ownerId, Object resourceId) {
  +        timeoutCheck(ownerId);
           GenericLock lock = (GenericLock) getLock(resourceId);
           if (lock != null) {
               return lock.getLockLevel(ownerId);
  @@ -173,6 +248,7 @@
        * @see LockManager#release(Object, Object)
        */
       public void release(Object ownerId, Object resourceId) {
  +        timeoutCheck(ownerId);
           GenericLock lock = (GenericLock) atomicGetOrCreateLock(resourceId);
           lock.release(ownerId);
           removeOwner(ownerId, lock);
  @@ -182,6 +258,11 @@
        * @see LockManager#releaseAll(Object)
        */
       public void releaseAll(Object ownerId) {
  +        // reset time out status for this owner
  +        if (timedOutOwners.remove(ownerId)) {
  +            // short cut if we were timed out there are no more locks
  +            return;
  +        }
           Set locks = (Set) globalOwners.get(ownerId);
           if (locks != null) {
               for (Iterator it = locks.iterator(); it.hasNext();) {
  @@ -221,12 +302,14 @@
       }
   
       protected void addWaiter(GenericLock lock, Object ownerId) {
  -        Set waiters = (Set) waitsForLock.get(lock);
  -        if (waiters == null) {
  -            waiters = new HashSet();
  -            waitsForLock.put(lock, waiters);
  +        synchronized (waitsForLock) {
  +            Set waiters = (Set) waitsForLock.get(lock);
  +            if (waiters == null) {
  +                waiters = Collections.synchronizedSet(new HashSet());
  +                waitsForLock.put(lock, waiters);
  +            }
  +            waiters.add(ownerId);
           }
  -        waiters.add(ownerId);
       }
   
       protected void removeWaiter(GenericLock lock, Object ownerId) {
  @@ -237,11 +320,7 @@
       }
   
       protected boolean wouldDeadlock(Object ownerId, GenericLock lock, int 
targetLockLevel,
  -            int compatibility) {
  -        // 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);
  +            int compatibility, Set conflicts) {
           if (conflicts != null) {
               // these are our locks
               Set locks = (Set) globalOwners.get(ownerId);
  @@ -251,13 +330,15 @@
                       // these are the ones waiting for one of our locks
                       Set waiters = (Set) waitsForLock.get(mylock);
                       if (waiters != null) {
  -                        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;
  -
  +                        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;
  +        
  +                            }
                           }
                       }
                   }
  @@ -266,6 +347,44 @@
           return false;
       }
   
  +    protected boolean releaseTimedOutOwners() {
  +        boolean released = false;
  +        synchronized (globalTimeouts) {
  +            for (Iterator it = globalTimeouts.entrySet().iterator(); 
it.hasNext();) {
  +                Map.Entry entry = (Map.Entry) it.next();
  +                Object ownerId = entry.getKey();
  +                long timeout = ((Long)entry.getValue()).longValue();
  +                long now = System.currentTimeMillis();
  +                if (timeout < now) {
  +                    releaseAll(ownerId);
  +                    timedOutOwners.add(ownerId);
  +                    it.remove();
  +                    released = true;
  +                }
  +            }
  +        }
  +        return released;
  +    }
  +    
  +    protected long getNextGlobalConflictTimeout(Set conflicts) {
  +        long minTimeout = -1;
  +        long now = System.currentTimeMillis();
  +        if (conflicts != null) {
  +            synchronized (globalTimeouts) {
  +                for (Iterator it = globalTimeouts.entrySet().iterator(); 
it.hasNext();) {
  +                    Map.Entry entry = (Map.Entry) it.next();
  +                    Object ownerId = entry.getKey();
  +                    if (conflicts.contains(ownerId)) {
  +                        long timeout = ((Long) entry.getValue()).longValue();
  +                        if (minTimeout == -1 || timeout < minTimeout) {
  +                            minTimeout = timeout;
  +                        }
  +                    }
  +                }
  +            }
  +        }
  +        return minTimeout;
  +    }
       
       public MultiLevelLock getLock(Object resourceId) {
           synchronized (globalLocks) {
  @@ -305,6 +424,17 @@
               GenericLock lock = new GenericLock(resourceId, maxLockLevel, 
logger);
               globalLocks.put(resourceId, lock);
               return lock;
  +        }
  +    }
  +    
  +    protected void timeoutCheck(Object ownerId) throws LockException {
  +        if (timedOutOwners.contains(ownerId)) {
  +            throw new LockException(
  +                    "All locks of owner "
  +                            + ownerId
  +                            + " have globally timed out."
  +                            + " You will not be able to to continue with 
this owner until you call releaseAll.",
  +                    LockException.CODE_TIMED_OUT, null);
           }
       }
   }
  
  
  

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

Reply via email to