/*
* JBoss, Home of Professional Open Source
* Copyright 2009 Red Hat Inc. and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.util.concurrent.locks.containers;
import org.infinispan.util.concurrent.ConcurrentMapFactory;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
/**
* An abstract lock container that creates and maintains a new lock per entry
*
* @author Manik Surtani
* @since 4.0
*/
public abstract class AbstractPerEntryLockContainer<L extends Lock> extends AbstractLockContainer<L> {
protected final ConcurrentMap<Object, L> locks;
protected AbstractPerEntryLockContainer(int concurrencyLevel) {
locks = ConcurrentMapFactory.makeConcurrentMap(16, concurrencyLevel);
}
@Override
public int getNumLocksHeld() {
return locks.size();
}
@Override
public int size() {
return locks.size();
}
@Override
public L acquireExclusiveLock(Object lockOwner, Object key, long timeout, TimeUnit unit) throws InterruptedException {
return acquire(lockOwner, key, timeout, unit, false);
}
@Override
public L acquireShareLock(Object lockOwner, Object key, long timeout, TimeUnit unit) throws InterruptedException {
return acquire(lockOwner, key, timeout, unit, true);
}
@Override
public void releaseExclusiveLock(Object lockOwner, Object key) {
L l = locks.get(key);
if (l != null) unlockExclusive(l, lockOwner);
}
@Override
public void releaseShareLock(Object lockOwner, Object key) {
L l = locks.get(key);
if (l != null) unlockShare(l, lockOwner);
}
@Override
public int getLockId(Object key) {
return System.identityHashCode(getExclusiveLock(key));
}
protected abstract L newLock();
protected final L getLockFromMap(Object key, boolean putIfAbsent) {
// this is an optimisation. It is not foolproof as we may still be creating new locks unnecessarily (thrown away
// when we do a putIfAbsent) but it minimises the chances somewhat, for the cost of an extra CHM get.
L lock = locks.get(key);
if (lock == null && putIfAbsent) {
lock = newLock();
L existingLock = locks.putIfAbsent(key, lock);
if (existingLock != null) lock = existingLock;
}
return lock;
}
private L acquire(Object lockOwner, Object key, long timeout, TimeUnit unit, boolean share) throws InterruptedException {
while (true) {
L lock = share ? getShareLock(key) : getExclusiveLock(key);
boolean locked;
try {
locked = share ?
tryShareLock(lock, timeout, unit, lockOwner) :
tryExclusiveLock(lock, timeout, unit, lockOwner);
} catch (InterruptedException ie) {
if (share) {
safeShareRelease(lock, lockOwner);
} else {
safeExclusiveRelease(lock, lockOwner);
}
throw ie;
} catch (Throwable th) {
locked = false;
}
if (locked) {
// lock acquired. Now check if it is the *correct* lock!
L existingLock = locks.putIfAbsent(key, lock);
if (existingLock != null && existingLock != lock) {
// we have the wrong lock! Unlock and retry.
if (share) {
safeShareRelease(lock, lockOwner);
} else {
safeExclusiveRelease(lock, lockOwner);
}
} else {
// we got the right lock.
return lock;
}
} else {
// we couldn't acquire the lock within the timeout period
return null;
}
}
}
}