/*
* 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;
import org.infinispan.config.Configuration;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.context.Flag;
import org.infinispan.context.InvocationContext;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.jmx.annotations.MBean;
import org.infinispan.jmx.annotations.ManagedAttribute;
import org.infinispan.marshall.MarshalledValue;
import org.infinispan.util.Util;
import org.infinispan.util.concurrent.TimeoutException;
import org.infinispan.util.concurrent.locks.containers.LockContainer;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import org.rhq.helpers.pluginAnnotations.agent.DataType;
import org.rhq.helpers.pluginAnnotations.agent.Metric;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
/**
* Handles locks for the MVCC based LockingInterceptor
*
* @author Manik Surtani (<a href="mailto:manik@jboss.org">manik@jboss.org</a>)
* @author Mircea.Markus@jboss.com
* @since 4.0
*/
@MBean(objectName = "LockManager", description = "Manager that handles MVCC locks for entries")
public class LockManagerImpl implements LockManager {
protected Configuration configuration;
protected volatile LockContainer<?> lockContainer;
private static final Log log = LogFactory.getLog(LockManagerImpl.class);
protected static final boolean trace = log.isTraceEnabled();
private static final String ANOTHER_THREAD = "(another thread)";
private static final String SHARED_LOCK = "(shared lock)";
@Inject
public void injectDependencies(Configuration configuration, LockContainer<?> lockContainer) {
this.configuration = configuration;
this.lockContainer = lockContainer;
}
@Override
public final boolean lockAndRecord(Object key, InvocationContext ctx, long timeoutMillis) throws InterruptedException {
return internalLockAndRecord(key, ctx, timeoutMillis, false);
}
@Override
public final boolean shareLockAndRecord(Object key, InvocationContext ctx, long timeoutMillis) throws InterruptedException {
return internalLockAndRecord(key, ctx, timeoutMillis, true);
}
protected long getLockAcquisitionTimeout(InvocationContext ctx) {
return ctx.hasFlag(Flag.ZERO_LOCK_ACQUISITION_TIMEOUT) ?
0 : configuration.getLockAcquisitionTimeout();
}
@Override
public void unlock(Collection<Object> lockedKeys, Object lockOwner) {
log.tracef("Attempting to unlock keys %s", lockedKeys);
for (Object k : lockedKeys) {
lockContainer.releaseShareLock(lockOwner, k);
lockContainer.releaseExclusiveLock(lockOwner, k);
}
}
@Override
@SuppressWarnings("unchecked")
public void unlockAll(InvocationContext ctx) {
for (Object k : ctx.getLockedKeys()) {
if (trace) log.tracef("Attempting to unlock %s", k);
lockContainer.releaseShareLock(ctx.getLockOwner(), k);
lockContainer.releaseExclusiveLock(ctx.getLockOwner(), k);
}
ctx.clearLockedKeys();
}
@Override
public boolean ownsLock(Object key, Object owner) {
return lockContainer.ownsExclusiveLock(key, owner);
}
@Override
public boolean isLocked(Object key) {
return lockContainer.isExclusiveLocked(key);
}
@Override
public Object getOwner(Object key) {
if (lockContainer.isExclusiveLocked(key)) {
Lock l = lockContainer.getExclusiveLock(key);
if (l instanceof OwnableReentrantLock) {
return ((OwnableReentrantLock) l).getOwner();
} else {
// cannot determine owner, JDK Reentrant locks only provide best-effort guesses.
return ANOTHER_THREAD;
}
} else if (lockContainer.isSharedLocked(key)) {
return SHARED_LOCK;
}
return null;
}
@Override
public String printLockInfo() {
return lockContainer.toString();
}
@Override
public final boolean possiblyLocked(CacheEntry entry) {
return entry == null || entry.isChanged() || entry.isNull() || entry.isLockPlaceholder();
}
@ManagedAttribute(description = "The concurrency level that the MVCC Lock Manager has been configured with.")
@Metric(displayName = "Concurrency level", dataType = DataType.TRAIT)
public int getConcurrencyLevel() {
return configuration.getConcurrencyLevel();
}
@Override
@ManagedAttribute(description = "The number of exclusive locks that are held.")
@Metric(displayName = "Number of locks held")
public int getNumberOfLocksHeld() {
return lockContainer.getNumLocksHeld();
}
@ManagedAttribute(description = "The number of exclusive locks that are available.")
@Metric(displayName = "Number of locks available")
public int getNumberOfLocksAvailable() {
return lockContainer.size() - lockContainer.getNumLocksHeld();
}
@Override
public int getLockId(Object key) {
return lockContainer.getLockId(key);
}
@Override
public final boolean acquireLock(InvocationContext ctx, Object key, boolean share) throws InterruptedException, TimeoutException {
return acquireLock(ctx, key, -1, share);
}
@Override
public boolean acquireLock(InvocationContext ctx, Object key, long timeoutMillis, boolean share) throws InterruptedException, TimeoutException {
// don't EVER use lockManager.isLocked() since with lock striping it may be the case that we hold the relevant
// lock which may be shared with another key that we have a lock for already.
// nothing wrong, just means that we fail to record the lock. And that is a problem.
// Better to check our records and lock again if necessary.
if (!ctx.hasLockedKey(key) && !ctx.hasFlag(Flag.SKIP_LOCKING)) {
return lock(ctx, key, timeoutMillis < 0 ? getLockAcquisitionTimeout(ctx) : timeoutMillis, share);
} else {
logLockNotAcquired(ctx);
}
return false;
}
@Override
public final boolean acquireLockNoCheck(InvocationContext ctx, Object key) throws InterruptedException, TimeoutException {
if (!ctx.hasFlag(Flag.SKIP_LOCKING)) {
return lock(ctx, key, getLockAcquisitionTimeout(ctx), false);
} else {
logLockNotAcquired(ctx);
}
return false;
}
private boolean lock(InvocationContext ctx, Object key, long timeoutMillis, boolean share) throws InterruptedException {
if (share ? shareLockAndRecord(key, ctx, timeoutMillis) : lockAndRecord(key, ctx, timeoutMillis)) {
ctx.addLockedKey(key);
return true;
} else {
Object owner = getOwner(key);
// if lock cannot be acquired, expose the key itself, not the marshalled value
if (key instanceof MarshalledValue) {
key = ((MarshalledValue) key).get();
}
throw new TimeoutException("Unable to acquire lock after [" + Util.prettyPrintTime(getLockAcquisitionTimeout(ctx)) + "] on key [" + key + "] for requestor [" +
ctx.getLockOwner() + "]! Lock held by [" + owner + "]");
}
}
private void logLockNotAcquired(InvocationContext ctx) {
if (trace) {
if (ctx.hasFlag(Flag.SKIP_LOCKING))
log.trace("SKIP_LOCKING flag used!");
else
log.trace("Already own lock for entry");
}
}
protected boolean internalLockAndRecord(Object key, InvocationContext ctx, long timeoutMillis, boolean share) throws InterruptedException {
if (trace) log.tracef("Attempting to %s lock %s with acquisition timeout of %s millis", (share ? "share" : "exclusive")
, key, timeoutMillis);
if (tryAcquire(key, ctx.getLockOwner(), timeoutMillis, share)) {
if (trace) log.tracef("Successfully acquired lock %s!", key);
return true;
}
// couldn't acquire lock!
if (log.isDebugEnabled()) {
log.debugf("Failed to acquire lock %s, owner is %s", key, getOwner(key));
Object owner = ctx.getLockOwner();
Set<Map.Entry<Object, CacheEntry>> entries = ctx.getLookedUpEntries().entrySet();
List<Object> lockedKeys = new ArrayList<Object>(entries.size());
for (Map.Entry<Object, CacheEntry> e : entries) {
Object lockedKey = e.getKey();
if (ownsLock(lockedKey, owner)) {
lockedKeys.add(lockedKey);
}
}
log.debugf("This transaction (%s) already owned locks %s", owner, lockedKeys);
}
return false;
}
protected final boolean tryAcquire(Object key, Object owner, long timeoutMillis, boolean share) throws InterruptedException {
return (share ? lockContainer.acquireShareLock(owner, key, timeoutMillis, MILLISECONDS) :
lockContainer.acquireExclusiveLock(owner, key, timeoutMillis, MILLISECONDS)) != null;
}
}