/*
* JBoss, Home of Professional Open Source.
* Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.jboss.cache.lock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
import org.jboss.cache.NodeSPI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
/**
* Lock object which grants and releases locks, and associates locks with
* owners . The methods to acquire and release a lock require an owner
* (Object). When a lock is acquired, we store the current owner with the lock.
* When the same owner (but possibly running in a different thread )
* wants to acquire the lock, it is immediately granted. When an owner
* different from the one currently owning the lock wants to release the lock,
* we do nothing (no-op).
*
* Consider the following example:
*
* FIFOSemaphore lock=new FIFOSemaphore(1);
* lock.acquire();
* lock.acquire();
* lock.release();
*
* This would block on the second acquire() (although we currently already hold
* the lock) because the lock only has 1 permit. So IdentityLock will allow the
* following code to work properly:
*
* IdentityLock lock=new IdentityLock();
* lock.readLock().acquire(this, timeout);
* lock.writeLock().acquire(this, timeout);
* lock.release(this);
*
* If the owner is null, then the current thread is taken by default. This allow the following
* code to work:
*
* IdentityLock lock=new IdentityLock();
* lock.readLock().attempt(null, timeout);
* lock.writeLock().attempt(null, timeout);
* lock.release(null);
*
*
* Note that the Object given as owner is required to implement {@link Object#equals}. For
* a use case see the examples in the testsuite.
*
* @author Bela Ban Apr 11, 2003
* @author Ben Wang July 2003
* @deprecated will be removed when we drop support for Pessimistic Locking and Optimistic Locking
*/
@Deprecated
@SuppressWarnings("deprecation")
public class IdentityLock implements NodeLock
{
/**
* Initialized property for debugging "print_lock_details"
*/
private boolean PRINT_LOCK_DETAILS = Boolean.getBoolean("print_lock_details");
private static final Log log = LogFactory.getLog(IdentityLock.class);
private static final boolean trace = log.isTraceEnabled();
private final LockStrategy lock;
private final LockMap map;
private final boolean mustReacquireRead;
private NodeSPI, ?> node;
/**
* Creates a new IdentityLock using the LockFactory passed in.
*
* @param factory to create lock strategies
* @param node to lock
*/
public IdentityLock(LockStrategyFactory factory, NodeSPI node)
{
lock = factory.getLockStrategy();
mustReacquireRead = lock instanceof LockStrategyReadCommitted;
map = new LockMap();
this.node = node;
}
/**
* Returns the node for this lock, may be null
.
*/
public Node getNode()
{
return node;
}
/**
* Returns the FQN this lock, may be null
.
*/
public Fqn getFqn()
{
if (node == null)
{
return null;
}
return node.getFqn();
}
/**
* Return a copy of the reader lock owner in List. Size is zero is not available. Note that this list
* is synchronized.
*
* @return Collection of readers
*/
public Collection getReaderOwners()
{
return map.readerOwners();
}
/**
* Return the writer lock owner object. Null if not available.
*
* @return Object owner
*/
public Object getWriterOwner()
{
return map.writerOwner();
}
public LockMap getLockMap()
{
return map;
}
/**
* Acquire a write lock with a timeout of timeout
milliseconds.
* Note that if the current owner owns a read lock, it will be upgraded
* automatically. However, if upgrade fails, i.e., timeout, the read lock will
* be released automatically.
*
* @param caller Can't be null.
* @param timeout
* @return boolean True if lock was acquired and was not held before, false if lock was held
* @throws LockingException
* @throws TimeoutException
*/
public boolean acquireWriteLock(Object caller, long timeout) throws LockingException, TimeoutException, InterruptedException
{
if (trace)
{
log.trace(new StringBuilder("acquiring WL: fqn=").append(getFqn()).append(", caller=").append(caller).
append(", lock=").append(toString(PRINT_LOCK_DETAILS)));
}
boolean flag = acquireWriteLock0(caller, timeout);
if (trace)
{
log.trace(new StringBuilder("acquired WL: fqn=").append(getFqn()).append(", caller=").append(caller).
append(", lock=").append(toString(PRINT_LOCK_DETAILS)));
}
return flag;
}
private boolean acquireWriteLock0(Object caller, long timeout) throws LockingException, TimeoutException, InterruptedException
{
if (caller == null)
{
throw new IllegalArgumentException("acquireWriteLock(): null caller");
}
if (map.isOwner(caller, LockMap.OWNER_WRITE))
{
if (trace)
{
log.trace("acquireWriteLock(): caller already owns lock for " + getFqn() + " (caller=" + caller + ')');
}
return false;// owner already has the lock
}
// Check first if we need to upgrade
if (map.isOwner(caller, LockMap.OWNER_READ))
{
// Currently is a reader owner. Obtain the writer ownership then.
Lock wLock;
try
{
if (trace)
{
log.trace("upgrading RL to WL for " + caller + ", timeout=" + timeout + ", locks: " + map.printInfo());
}
wLock = lock.upgradeLockAttempt(timeout);
}
catch (UpgradeException ue)
{
String errStr = "acquireWriteLock(): lock upgrade failed for " + getFqn() + " (caller=" + caller + ", lock info: " + toString(true) + ')';
log.trace(errStr, ue);
throw new UpgradeException(errStr, ue);
}
if (wLock == null)
{
release(caller);// bug fix: remember to release the read lock before throwing the exception
map.removeReader(caller);
String errStr = "upgrade lock for " + getFqn() + " could not be acquired after " + timeout + " ms." +
" Lock map ownership " + map.printInfo() + " (caller=" + caller + ", lock info: " + toString(true) + ')';
log.trace(errStr);
throw new UpgradeException(errStr);
}
try
{
if (trace)
{
log.trace("upgrading lock for " + getFqn());
}
map.upgrade(caller);
}
catch (OwnerNotExistedException ex)
{
throw new UpgradeException("Can't upgrade lock to WL for " + getFqn() + ", error in LockMap.upgrade()", ex);
}
}
else
{
// Not a current reader owner. Obtain the writer ownership then.
boolean rc = lock.writeLock().tryLock(timeout, TimeUnit.MILLISECONDS);
// we don't need to synchronize from here on because we own the semaphore
if (!rc)
{
String errStr = "write lock for " + getFqn() + " could not be acquired after " + timeout + " ms. " +
"Locks: " + map.printInfo() + " (caller=" + caller + ", lock info: " + toString(true) + ')';
log.trace(errStr);
throw new TimeoutException(errStr);
}
map.setWriterIfNotNull(caller);
}
return true;
}
/**
* Acquire a read lock with a timeout period of timeout
milliseconds.
*
* @param caller Can't be null.
* @param timeout
* @return boolean True if lock was acquired and was not held before, false if lock was held
* @throws LockingException
* @throws TimeoutException
*/
public boolean acquireReadLock(Object caller, long timeout) throws LockingException, TimeoutException, InterruptedException
{
if (trace)
{
log.trace(new StringBuilder("acquiring RL: fqn=").append(getFqn()).append(", caller=").append(caller).
append(", lock=").append(toString(PRINT_LOCK_DETAILS)));
}
boolean flag = acquireReadLock0(caller, timeout);
if (trace)
{
log.trace(new StringBuilder("acquired RL: fqn=").append(getFqn()).append(", caller=").append(caller).
append(", lock=").append(toString(PRINT_LOCK_DETAILS)));
}
return flag;
}
private boolean acquireReadLock0(Object caller, long timeout)
throws LockingException, TimeoutException, InterruptedException
{
boolean rc;
if (caller == null)
{
throw new IllegalArgumentException("owner is null");
}
boolean hasRead = false;
boolean hasRequired = false;
if (mustReacquireRead)
{
hasRequired = map.isOwner(caller, LockMap.OWNER_WRITE);
if (!hasRequired)
{
hasRead = map.isOwner(caller, LockMap.OWNER_READ);
}
}
else if (map.isOwner(caller, LockMap.OWNER_ANY))
{
hasRequired = true;
}
if (hasRequired)
{
if (trace)
{
StringBuilder sb = new StringBuilder(64);
sb.append("acquireReadLock(): caller ").append(caller).append(" already owns lock for ").append(getFqn());
log.trace(sb.toString());
}
return false;// owner already has the lock
}
rc = lock.readLock().tryLock(timeout, TimeUnit.MILLISECONDS);
// we don't need to synchronize from here on because we own the semaphore
if (!rc)
{
StringBuilder sb = new StringBuilder();
sb.append("read lock for ").append(getFqn()).append(" could not be acquired by ").append(caller);
sb.append(" after ").append(timeout).append(" ms. " + "Locks: ").append(map.printInfo());
sb.append(", lock info: ").append(toString(true));
String errMsg = sb.toString();
log.trace(errMsg);
throw new TimeoutException(errMsg);
}
// Only add to the map if we didn't already have the lock
if (!hasRead)
{
map.addReader(caller);// this is synchronized internally, we don't need to synchronize here
}
return true;
}
/**
* Release the lock held by the owner.
*
* @param caller Can't be null.
*/
public void release(Object caller)
{
if (caller == null)
{
throw new IllegalArgumentException("IdentityLock.release(): null owner object.");
}
// Check whether to release reader or writer lock.
if (map.isOwner(caller, LockMap.OWNER_READ))
{
map.removeReader(caller);
lock.readLock().unlock();
}
else if (map.isOwner(caller, LockMap.OWNER_WRITE))
{
map.removeWriter();
lock.writeLock().unlock();
}
}
/**
* Release all locks associated with this instance.
*/
public void releaseAll()
{
try
{
if ((map.writerOwner()) != null)
{
// lock_.readLock().release();
lock.writeLock().unlock();
}
map.releaseReaderOwners(lock);
}
finally
{
map.removeAll();
}
}
/**
* Same as releaseAll now.
*/
public void releaseForce()
{
releaseAll();
}
/**
* Check if there is a read lock.
*/
public boolean isReadLocked()
{
return map.isReadLocked();
}
/**
* Check if there is a write lock.
*/
public boolean isWriteLocked()
{
return map.writerOwner() != null;
}
/**
* Check if there is a read or write lock
*/
public boolean isLocked()
{
return isReadLocked() || isWriteLocked();
}
/**
* Am I a lock owner?
*
* @param o
*/
public boolean isOwner(Object o)
{
return map.isOwner(o, LockMap.OWNER_ANY);
}
@Override
public String toString()
{
return toString(false);
}
public String toString(boolean print_lock_details)
{
StringBuilder sb = new StringBuilder();
toString(sb, print_lock_details);
return sb.toString();
}
public void toString(StringBuilder sb)
{
toString(sb, false);
}
public void toString(StringBuilder sb, boolean print_lock_details)
{
boolean printed_read_owners = false;
Collection read_owners = lock != null ? getReaderOwners() : null;
if (read_owners != null && read_owners.size() > 0)
{
// Fix for JBCACHE-310 -- can't just call new ArrayList(read_owners) :(
// Creating the ArrayList and calling addAll doesn't work either
// Looking at the details of how this is implemented vs. the 2 prev
// options, this doesn't look like it should be slower
Iterator iter = read_owners.iterator();
read_owners = new ArrayList(read_owners.size());
while (iter.hasNext())
{
read_owners.add(iter.next());
}
sb.append("read owners=").append(read_owners);
printed_read_owners = true;
}
Object write_owner = lock != null ? getWriterOwner() : null;
if (write_owner != null)
{
if (printed_read_owners)
{
sb.append(", ");
}
sb.append("write owner=").append(write_owner);
}
if (read_owners == null && write_owner == null)
{
sb.append("");
}
if (print_lock_details)
{
sb.append(" (").append(lock.toString()).append(')');
}
}
public boolean acquire(Object caller, long timeout, LockType lock_type) throws LockingException, TimeoutException, InterruptedException
{
try
{
if (lock_type == LockType.NONE)
{
return true;
}
else if (lock_type == LockType.READ)
{
return acquireReadLock(caller, timeout);
}
else
{
return acquireWriteLock(caller, timeout);
}
}
catch (UpgradeException e)
{
StringBuilder buf = new StringBuilder("failure upgrading lock: fqn=").append(getFqn()).append(", caller=").append(caller).
append(", lock=").append(toString(true));
if (trace)
{
log.trace(buf.toString());
}
throw new UpgradeException(buf.toString(), e);
}
catch (LockingException e)
{
StringBuilder buf = new StringBuilder("failure acquiring lock: fqn=").append(getFqn()).append(", caller=").append(caller).
append(", lock=").append(toString(true));
if (trace)
{
log.trace(buf.toString());
}
throw new LockingException(buf.toString(), e);
}
catch (TimeoutException e)
{
StringBuilder buf = new StringBuilder("failure acquiring lock: fqn=").append(getFqn()).append(", caller=").append(caller).
append(", lock=").append(toString(true));
if (trace)
{
log.trace(buf.toString());
}
throw new TimeoutException(buf.toString(), e);
}
}
public Set acquireAll(Object caller, long timeout, LockType lock_type)
throws LockingException, TimeoutException, InterruptedException
{
return acquireAll(caller, timeout, lock_type, false);
}
public Set acquireAll(Object caller, long timeout, LockType lock_type, boolean excludeInternalFqns)
throws LockingException, TimeoutException, InterruptedException
{
boolean acquired;
if (lock_type == LockType.NONE || (excludeInternalFqns && node.getCache().getInternalFqns().contains(getFqn())))
{
return Collections.emptySet();
}
Set retval = new HashSet();
acquired = acquire(caller, timeout, lock_type);
if (acquired)
{
retval.add(this);
}
for (NodeSPI n : node.getChildrenDirect())
{
retval.addAll(n.getLock().acquireAll(caller, timeout, lock_type, excludeInternalFqns));
}
return retval;
}
public void releaseAll(Object owner)
{
for (NodeSPI n : node.getChildrenDirect())
{
n.getLock().releaseAll(owner);
}
release(owner);
}
private void printIndent(StringBuilder sb, int indent)
{
if (sb != null)
{
for (int i = 0; i < indent; i++)
{
sb.append(" ");
}
}
}
public void printLockInfo(StringBuilder sb, int indent)
{
boolean locked = isLocked();
printIndent(sb, indent);
sb.append(Fqn.SEPARATOR).append(node.getFqn().getLastElement());
if (locked)
{
sb.append("\t(");
toString(sb);
sb.append(")");
}
for (NodeSPI n : node.getChildrenDirect())
{
sb.append("\n");
n.getLock().printLockInfo(sb, indent + 4);
}
}
}