package org.infinispan.util.concurrent.locks;
import org.infinispan.context.InvocationContext;
import org.infinispan.context.impl.TxInvocationContext;
import org.infinispan.factories.annotations.Start;
import org.infinispan.jmx.annotations.MBean;
import org.infinispan.jmx.annotations.ManagedAttribute;
import org.infinispan.jmx.annotations.ManagedOperation;
import org.infinispan.remoting.transport.Address;
import org.infinispan.transaction.xa.DeadlockDetectingGlobalTransaction;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import java.util.concurrent.atomic.AtomicLong;
/**
* Lock manager in charge with processing deadlock detections.
*
* @author Mircea.Markus@jboss.com
*/
@MBean(description = "Information about the number of deadlocks that were detected")
public class DeadlockDetectingLockManager extends LockManagerImpl {
private static final Log log = LogFactory.getLog(DeadlockDetectingLockManager.class);
protected volatile long spinDuration;
protected volatile boolean exposeJmxStats;
private volatile boolean isSync;
private AtomicLong detectedRemoteDeadlocks = new AtomicLong(0);
private AtomicLong detectedLocalDeadlocks = new AtomicLong(0);
private AtomicLong locallyInterruptedTransactions = new AtomicLong(0);
private AtomicLong overlapWithNotDeadlockAwareLockOwners = new AtomicLong(0);
@Start
public void init() {
spinDuration = configuration.getDeadlockDetectionSpinDuration();
exposeJmxStats = configuration.isExposeJmxStatistics();
isSync = configuration.getCacheMode().isSynchronous();
}
public boolean lockAndRecord(Object key, InvocationContext ctx) throws InterruptedException {
long lockTimeout = getLockAcquisitionTimeout(ctx);
if (trace) log.trace("Attempting to lock {0} with acquisition timeout of {1} millis", key, lockTimeout);
if (ctx.isInTxScope()) {
if (trace) log.trace("Using early dead lock detection");
final long start = System.currentTimeMillis();
long now;
while ((now = System.currentTimeMillis()) < (start + lockTimeout)) {
if (lockContainer.acquireLock(key, spinDuration, MILLISECONDS)) {
if (trace) log.trace("successfully acquired lock on " + key + ", returning ...");
return true;
} else {
if (trace)
log.trace("Could not acquire lock on '" + key + "' as it is locked by '" + getOwner(key) + "', check for dead locks");
Object owner = getOwner(key);
if (!(owner instanceof DeadlockDetectingGlobalTransaction)) {
if (trace)
log.trace("Owner is not instance of DeadlockDetectingGlobalTransaction: " + owner + ", continuing ...");
if (exposeJmxStats) overlapWithNotDeadlockAwareLockOwners.incrementAndGet();
continue; //try to acquire lock again, for the rest of the time
}
DeadlockDetectingGlobalTransaction lockOwnerTx = (DeadlockDetectingGlobalTransaction) owner;
if (isSync && !ctx.isOriginLocal() && !lockOwnerTx.isRemote()) {
return remoteVsRemoteDld(key, ctx, lockTimeout, start, now, lockOwnerTx);
}
if ((ctx.isOriginLocal() && !lockOwnerTx.isRemote()) || (!isSync && !ctx.isOriginLocal() && !lockOwnerTx.isRemote())) {
localVsLocalDld(ctx, lockOwnerTx);
}
}
}
} else {
if (lockContainer.acquireLock(key, lockTimeout, MILLISECONDS)) {
return true;
}
}
// couldn't acquire lock!
return false;
}
private void localVsLocalDld(InvocationContext ctx, DeadlockDetectingGlobalTransaction lockOwnerTx) {
if (trace) log.trace("Looking for local vs local deadlocks");
DeadlockDetectingGlobalTransaction thisThreadsTx = (DeadlockDetectingGlobalTransaction) ctx.getLockOwner();
boolean weOwnLock = ownsLock(lockOwnerTx.getLockInterntion(), thisThreadsTx);
if (trace) {
log.trace("Other owner's intention is " + lockOwnerTx.getLockInterntion() + ". Do we(" + thisThreadsTx + ") own lock for it? " + weOwnLock + ". Lock owner is " + getOwner(lockOwnerTx.getLockInterntion()));
}
if (weOwnLock) {
boolean iShouldInterrupt = thisThreadsTx.thisWillInterrupt(lockOwnerTx);
if (trace)
log.trace("deadlock situation detected. Shall I interrupt?" + iShouldInterrupt );
if (iShouldInterrupt) {
lockOwnerTx.interruptProcessingThread();
if (exposeJmxStats) detectedLocalDeadlocks.incrementAndGet();
}
}
}
private boolean remoteVsRemoteDld(Object key, InvocationContext ctx, long lockTimeout, long start, long now, DeadlockDetectingGlobalTransaction lockOwnerTx) throws InterruptedException {
TxInvocationContext remoteTxContext = (TxInvocationContext) ctx;
Address origin = remoteTxContext.getGlobalTransaction().getAddress();
DeadlockDetectingGlobalTransaction remoteGlobalTransaction = (DeadlockDetectingGlobalTransaction) ctx.getLockOwner();
boolean thisShouldInterrupt = remoteGlobalTransaction.thisWillInterrupt(lockOwnerTx);
if (trace) log.trace("Should I interrupt other transaction ? " + thisShouldInterrupt);
boolean isDeadLock = (configuration.getCacheMode().isReplicated() || lockOwnerTx.isReplicatingTo(origin)) && !lockOwnerTx.isRemote();
if (thisShouldInterrupt && isDeadLock) {
lockOwnerTx.interruptProcessingThread();
if (exposeJmxStats) {
detectedRemoteDeadlocks.incrementAndGet();
locallyInterruptedTransactions.incrementAndGet();
}
return lockForTheRemainingTime(key, lockTimeout, start, now);
} else if (!isDeadLock) {
return lockForTheRemainingTime(key, lockTimeout, start, now);
} else {
if (trace)
log.trace("Not trying to acquire lock anymore, as we're in deadlock and this will be rollback at origin");
if (exposeJmxStats) {
detectedRemoteDeadlocks.incrementAndGet();
}
remoteGlobalTransaction.setMarkedForRollback(true);
throw new DeadlockDetectedException("Deadlock situation detected on tx: " + remoteTxContext.getLockOwner());
}
}
private boolean lockForTheRemainingTime(Object key, long lockTimeout, long start, long now) throws InterruptedException {
long remainingLockingTime = (start + lockTimeout) - now;
if (remainingLockingTime < 0)
throw new IllegalStateException("No remaining time!!! The outer while condition MUST make sure this always stands true!");
if (trace) log.trace("trying to lock for the remaining time: " + remainingLockingTime + " millis ");
return lockContainer.acquireLock(key, remainingLockingTime, MILLISECONDS);
}
public void setExposeJmxStats(boolean exposeJmxStats) {
this.exposeJmxStats = exposeJmxStats;
}
@ManagedAttribute(description = "Number of situtations when we try to determine a deadlock and the other lock owner is e.g. a local tx. In this scenario we cannot run the deadlock detection mechanism")
public long getOverlapWithNotDeadlockAwareLockOwners() {
return overlapWithNotDeadlockAwareLockOwners.get();
}
@ManagedAttribute(description = "Number of locally originated transactions that were interrupted as a deadlock situation was detected")
public long getLocallyInterruptedTransactions() {
return locallyInterruptedTransactions.get();
}
@ManagedAttribute(description = "Number of remote deadlocks detected")
public long getDetectedRemoteDeadlocks() {
return detectedRemoteDeadlocks.get();
}
@ManagedAttribute (description = "Number of local detected deadlocks")
public long getDetectedLocalDeadlocks() {
return detectedLocalDeadlocks.get();
}
@ManagedAttribute (description = "Total number of local detected deadlocks")
public long getTotalNumberOfDetectedDeadlocks() {
return detectedRemoteDeadlocks.get() + detectedLocalDeadlocks.get();
}
@ManagedOperation(description = "Resets statistics gathered by this component")
public void resetStatistics() {
overlapWithNotDeadlockAwareLockOwners.set(0);
locallyInterruptedTransactions.set(0);
detectedRemoteDeadlocks.set(0);
detectedLocalDeadlocks.set(0);
}
}