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]