@Override public void acquireShared(Locks.ResourceType resourceType, long... resourceIds) throws AcquireLockTimeoutException { ConcurrentMap<Long, ForsetiLockManager.Lock> lockMap = lockMaps[resourceType.typeId()]; Map<Long, Integer> heldShareLocks = sharedLockCounts[resourceType.typeId()]; Map<Long, Integer> heldExclusiveLocks = exclusiveLockCounts[resourceType.typeId()]; for (long resourceId : resourceIds) { Integer heldCount = heldShareLocks.get(resourceId); if (heldCount != null) { // We already have a lock on this, just increment our local reference counter. heldShareLocks.put(resourceId, heldCount + 1); continue; } if (heldExclusiveLocks.containsKey(resourceId)) { // We already have an exclusive lock, so just leave that in place. When the exclusive lock // is released, // it will be automatically downgraded to a shared lock, since we bumped the share lock // reference count. heldShareLocks.put(resourceId, 1); continue; } int tries = 0; SharedLock mySharedLock = null; while (true) { ForsetiLockManager.Lock existingLock = lockMap.get(resourceId); if (existingLock == null) { // Try to create a new shared lock if (mySharedLock == null) { mySharedLock = new SharedLock(this); } if (lockMap.putIfAbsent(resourceId, mySharedLock) == null) { // Success! break; } else { continue; } } else if (existingLock instanceof SharedLock) { if (((SharedLock) existingLock).acquire(this)) { // Success! break; } } else if (existingLock instanceof ExclusiveLock) { // We need to wait, just let the loop run. } else { throw new UnsupportedOperationException("Unknown lock type: " + existingLock); } waitStrategies[resourceType.typeId()].apply(tries++); markAsWaitingFor(existingLock, resourceType, resourceId); } clearWaitList(); heldShareLocks.put(resourceId, 1); } }
@Override public void releaseShared(Locks.ResourceType resourceType, long... resourceIds) { for (long resourceId : resourceIds) { if (releaseLocalLock(resourceType, resourceId, sharedLockCounts[resourceType.typeId()])) { continue; } // Only release if we were not holding an exclusive lock as well if (!exclusiveLockCounts[resourceType.typeId()].containsKey(resourceId)) { releaseGlobalLock(lockMaps[resourceType.typeId()], resourceId); } } }
@Override public void releaseExclusive(Locks.ResourceType resourceType, long... resourceIds) { for (long resourceId : resourceIds) { if (releaseLocalLock(resourceType, resourceId, exclusiveLockCounts[resourceType.typeId()])) { continue; } if (sharedLockCounts[resourceType.typeId()].containsKey(resourceId)) { // We are still holding a shared lock, so swap the exclusive lock for that lockMaps[resourceType.typeId()].put(resourceId, new SharedLock(this)); } else { releaseGlobalLock(lockMaps[resourceType.typeId()], resourceId); } } }
// TODO These kinds of variadic APIs look generally problematic to me. // TODO Say we're trying to grab the locks on [1, 2, 3]. Getting the lock on 1 // TODO succeeds, but getting the lock on 2 fails. Then we return 'false', leave // TODO the lock on 1 held, and never try to grab the lock on 3. // TODO That sounds like a mess to me. Basically, if you try to grab more than // TODO one lock at a time, and methods like this one returns 'false', then you // TODO have no idea what locks you did or did not get. // TODO I think the API with batched lock-grabbing should be dropped completely. // TODO Especially considering the implementation of Forseti, or the general // TODO concept of lock managers as a whole, I don't think batched lock-grabbing // TODO will ever give any noticable performance benefit anyway. @Override public boolean trySharedLock(Locks.ResourceType resourceType, long... resourceIds) { ConcurrentMap<Long, ForsetiLockManager.Lock> lockMap = lockMaps[resourceType.typeId()]; Map<Long, Integer> heldShareLocks = sharedLockCounts[resourceType.typeId()]; Map<Long, Integer> heldExclusiveLocks = exclusiveLockCounts[resourceType.typeId()]; for (long resourceId : resourceIds) { Integer heldCount = heldShareLocks.get(resourceId); if (heldCount != null) { // We already have a lock on this, just increment our local reference counter. heldShareLocks.put(resourceId, heldCount + 1); continue; } if (heldExclusiveLocks.containsKey(resourceId)) { // We already have an exclusive lock, so just leave that in place. When the exclusive lock // is released, // it will be automatically downgraded to a shared lock, since we bumped the share lock // reference count. heldShareLocks.put(resourceId, 1); continue; } while (true) { ForsetiLockManager.Lock existingLock = lockMap.get(resourceId); if (existingLock == null) { // Try to create a new shared lock if (lockMap.putIfAbsent(resourceId, new SharedLock(this)) == null) { // Success! break; } } else if (existingLock instanceof SharedLock) { if (((SharedLock) existingLock).acquire(this)) { // Success! break; } else { return false; } } else if (existingLock instanceof ExclusiveLock) { return false; } else { throw new UnsupportedOperationException("Unknown lock type: " + existingLock); } } heldShareLocks.put(resourceId, 1); } return true; }
/** Attempt to upgrade a share lock that we hold to an exclusive lock. */ private boolean tryUpgradeToExclusiveWithShareLockHeld( Locks.ResourceType resourceType, ConcurrentMap<Long, ForsetiLockManager.Lock> lockMap, long resourceId, SharedLock sharedLock, int tries) throws AcquireLockTimeoutException { if (sharedLock.tryAcquireUpdateLock(this)) { try { // Now we just wait for all clients to release the the share lock while (sharedLock.numberOfHolders() > 1) { waitStrategies[resourceType.typeId()].apply(tries++); markAsWaitingFor(sharedLock, resourceType, resourceId); } // No more people other than us holding this lock. Swap it to exclusive // TODO Wait, why do we need to do this? An update lock with zero shared holders is an // TODO exclusive lock, no? Why is it not enough to just atomically raise the update bit, // TODO and then wait for all the shared holders to relinquish their grasp? lockMap.put(resourceId, myExclusiveLock); return true; } catch (DeadlockDetectedException e) { sharedLock.releaseUpdateLock(this); throw e; } catch (Throwable e) { sharedLock.releaseUpdateLock(this); clearWaitList(); throw new RuntimeException(e); } } return false; }
/** * Attempt to upgrade a share lock to an exclusive lock, grabbing the share lock if we don't hold * it. */ private boolean tryUpgradeSharedToExclusive( Locks.ResourceType resourceType, ConcurrentMap<Long, ForsetiLockManager.Lock> lockMap, long resourceId, SharedLock sharedLock) throws AcquireLockTimeoutException { int tries = 0; if (!sharedLockCounts[resourceType.typeId()].containsKey(resourceId)) { // We don't hold the shared lock, we need to grab it to upgrade it to an exclusive one if (!sharedLock.acquire(this)) { return false; } try { if (tryUpgradeToExclusiveWithShareLockHeld( resourceType, lockMap, resourceId, sharedLock, tries)) { return true; } else { releaseGlobalLock(lockMap, resourceId); return false; } } catch (Throwable e) { releaseGlobalLock(lockMap, resourceId); throw e; } } else { // We do hold the shared lock, so no reason to deal with the complexity in the case above. return tryUpgradeToExclusiveWithShareLockHeld( resourceType, lockMap, resourceId, sharedLock, tries); } }
@Override public void write(ChannelBuffer buffer) throws IOException { buffer.writeInt(type.typeId()); buffer.writeInt(resourceIds.length); for (long entity : resourceIds) { buffer.writeLong(entity); } }
@Override public void acquireExclusive(Locks.ResourceType resourceType, long... resourceIds) throws AcquireLockTimeoutException { ConcurrentMap<Long, ForsetiLockManager.Lock> lockMap = lockMaps[resourceType.typeId()]; Map<Long, Integer> heldLocks = exclusiveLockCounts[resourceType.typeId()]; for (long resourceId : resourceIds) { Integer heldCount = heldLocks.get(resourceId); if (heldCount != null) { // We already have a lock on this, just increment our local reference counter. heldLocks.put(resourceId, heldCount + 1); continue; } // Grab the global lock ForsetiLockManager.Lock existingLock; int tries = 0; while ((existingLock = lockMap.putIfAbsent(resourceId, myExclusiveLock)) != null) { // If this is a shared lock: // Given a grace period of tries (to try and not starve readers), grab an update lock and // wait for it // to convert to an exclusive lock. if (tries > 50 && existingLock instanceof SharedLock) { // Then we should upgrade that lock SharedLock sharedLock = (SharedLock) existingLock; if (tryUpgradeSharedToExclusive(resourceType, lockMap, resourceId, sharedLock)) { break; } } waitStrategies[resourceType.typeId()].apply(tries++); markAsWaitingFor(existingLock, resourceType, resourceId); } clearWaitList(); heldLocks.put(resourceId, 1); } }
@Override public boolean tryExclusiveLock(Locks.ResourceType resourceType, long... resourceIds) { ConcurrentMap<Long, ForsetiLockManager.Lock> lockMap = lockMaps[resourceType.typeId()]; Map<Long, Integer> heldLocks = exclusiveLockCounts[resourceType.typeId()]; for (long resourceId : resourceIds) { Integer heldCount = heldLocks.get(resourceId); if (heldCount != null) { // We already have a lock on this, just increment our local reference counter. heldLocks.put(resourceId, heldCount + 1); continue; } // Grab the global lock ForsetiLockManager.Lock lock; if ((lock = lockMap.putIfAbsent(resourceId, myExclusiveLock)) != null) { if (lock instanceof SharedLock && sharedLockCounts[resourceType.typeId()].containsKey(resourceId)) { SharedLock sharedLock = (SharedLock) lock; if (sharedLock.tryAcquireUpdateLock(this)) { if (sharedLock.numberOfHolders() == 1) { lockMap.put(resourceId, myExclusiveLock); return true; } else { sharedLock.releaseUpdateLock(this); return false; } } } return false; } heldLocks.put(resourceId, 1); } return true; }