/*
* Copyright (C) 2013 eXo Platform SAS.
*
* 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.exoplatform.container;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* This class is used to be aware of all the {@link Lock} currently used to prevent
* deadlocks
*
* @author <a href="mailto:nfilotto@exoplatform.com">Nicolas Filotto</a>
* @version $Id$
*
*/
public class LockManager
{
/**
* The logger
*/
private static final Log LOG = ExoLogger.getLogger("exo.kernel.container.mt.LockManager");
/**
* The singleton
*/
private static final LockManager INSTANCE = new LockManager();
/**
* Current lockable resources
*/
private final ConcurrentMap<Thread, Lockable> locks = new ConcurrentHashMap<Thread, Lockable>();
/**
* The total amount of uncompleted tasks
*/
private final AtomicInteger totalUncompletedTasks = new AtomicInteger();
private LockManager()
{
}
/**
* The unique instance of the {@link LockManager}
*/
public static LockManager getInstance()
{
return INSTANCE;
}
/**
* Gives a new {@link Lock} instance
*/
public Lock createLock()
{
return new InternalReentrantLock();
}
/**
* Creates a new {@link RunnableFuture} instance
*/
public <T> RunnableFuture<T> createRunnableFuture(Runnable runnable, T value)
{
return new InternalFutureTask<T>(runnable, value);
}
/**
* Creates a new {@link RunnableFuture} instance
*/
public <T> RunnableFuture<T> createRunnableFuture(Callable<T> callable)
{
return new InternalFutureTask<T>(callable);
}
/**
* Gives the total amount of uncompleted tasks
*/
int getTotalUncompletedTasks()
{
return totalUncompletedTasks.get();
}
/**
* Increments and get the total amount of uncompleted tasks
*/
int incrementAndGetTotalUncompletedTasks()
{
return totalUncompletedTasks.incrementAndGet();
}
/**
* Indicates whether or not there are some remaining lockable resources
*/
boolean isEmpty()
{
return locks.isEmpty();
}
/**
* Registers a lockable resource for the current thread
*/
private void register(Lockable l)
{
locks.put(Thread.currentThread(), l);
}
/**
* Unregisters a lockable resource for the current thread
*/
private void unregister(Lockable l)
{
locks.remove(Thread.currentThread(), l);
}
/**
* Checks if there is a deadlock, if so an {@link InterruptedException}
* will be thrown
*/
private void checkDeadLock(Lockable l) throws InterruptedException
{
if (!l.isLocked())
{
LOG.trace("The lock is not locked so we cannot have a deadlock");
return;
}
final Thread owner = l.getOwner();
if (owner == null || owner == Thread.currentThread())
{
LOG.trace("The lock is not locked or the lock owner is the current "
+ "thread so we cannot have a deadlock");
return;
}
Thread currentOwner = owner;
while (true)
{
Lockable lock = locks.get(currentOwner);
if (lock == null)
{
LOG.trace("The owner has no lockable resource to acquire so we cannot have a deadlock");
return;
}
// We first check the locks
Thread lockToAcquireOwner = lock.getOwner();
if (lockToAcquireOwner == null)
{
LOG.trace("The lockable resource has no owner anymore so we cannot have a deadlock");
return;
}
else if (lockToAcquireOwner == Thread.currentThread())
{
// A potential deadlock has been detected
if (owner == l.getOwner() && l.isLocked())
{
LOG.debug("A deadlock has been detected, both threads will be interrupted");
// The owner did not change so we have a deadlock, so
// we will interrupt both threads
owner.interrupt();
throw new InterruptedException();
}
else
{
LOG.trace("The owner has changed or the resource is no more locked so we cannot have a deadlock");
return;
}
}
currentOwner = lockToAcquireOwner;
}
}
/**
* Internal sub-class of {@link ReentrantLock} needed to be able to register
* and unregister all the locks automatically
*/
private class InternalReentrantLock extends ReentrantLock implements Lockable
{
/**
* The serial version UID
*/
private static final long serialVersionUID = 1696442015918441687L;
/**
* {@inheritDoc}
*/
public Thread getOwner()
{
return super.getOwner();
}
/**
* {@inheritDoc}
*/
@Override
public void lock()
{
register(this);
super.lock();
unregister(this);
}
/**
* {@inheritDoc}
*/
@Override
public void lockInterruptibly() throws InterruptedException
{
register(this);
try
{
checkDeadLock(this);
super.lockInterruptibly();
}
finally
{
unregister(this);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean tryLock()
{
register(this);
boolean result = super.tryLock();
unregister(this);
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
{
register(this);
try
{
checkDeadLock(this);
return super.tryLock(timeout, unit);
}
finally
{
unregister(this);
}
}
}
/**
* Internal sub-class of {@link FutureTask} needed to be able to register
* and unregister all the tasks automatically
*/
private class InternalFutureTask<V> extends FutureTask<V> implements Lockable
{
/**
* The current owner of exclusive mode synchronization.
*/
private final AtomicReference<Thread> exclusiveOwnerThread = new AtomicReference<Thread>();
/**
* {@inheritDoc}
*/
public InternalFutureTask(Callable<V> callable)
{
super(callable);
}
/**
* {@inheritDoc}
*/
public InternalFutureTask(Runnable runnable, V result)
{
super(runnable, result);
}
/**
* Checks if there is a deadlock, if so it will interrupt the thread waiting for the lock
*/
private void checkDeadLock()
{
try
{
LockManager.this.checkDeadLock(this);
}
catch (InterruptedException e)
{
LOG.debug("An InterruptedException has been caught, but a task must not be interrupted");
}
}
/**
* {@inheritDoc}
*/
@Override
public V get() throws InterruptedException, ExecutionException
{
register(this);
checkDeadLock();
try
{
return super.get();
}
finally
{
unregister(this);
}
}
/**
* {@inheritDoc}
*/
@Override
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{
register(this);
checkDeadLock();
try
{
return super.get(timeout, unit);
}
finally
{
unregister(this);
}
}
/**
* {@inheritDoc}
*/
@Override
public void run()
{
exclusiveOwnerThread.compareAndSet(null, Thread.currentThread());
try
{
super.run();
}
finally
{
totalUncompletedTasks.decrementAndGet();
exclusiveOwnerThread.compareAndSet(Thread.currentThread(), null);
}
}
/**
* Gives the Owner of the task
*/
public Thread getOwner()
{
return exclusiveOwnerThread.get();
}
/**
* Indicates whether the task is locked or not, in practice it will be considered as locked if it is not done
*/
public boolean isLocked()
{
return !isDone();
}
}
/**
* Defines a lockable resource
*/
private static interface Lockable
{
/**
* Gives the owner in case the resource is locked
*/
Thread getOwner();
/**
* Indicates whether the resource is locked or not
*/
boolean isLocked();
}
}