/**
* Copyright (c) 2002-2013 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.impl.transaction;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.neo4j.kernel.DeadlockDetectedException;
/**
* The LockManager can lock resources for reading or writing. By doing this one
* may achieve different transaction isolation levels. A resource can for now be
* any object (but null).
* <p>
* When acquiring a lock you have to release it. Failure to do so will result in
* the resource being blocked to all other transactions. Put all locks in a try -
* finally block.
* <p>
* Multiple locks on the same resource held by the same transaction requires the
* transaction to invoke the release lock method multiple times. If a tx has
* invoked <CODE>getReadLock</CODE> on the same resource x times in a row it
* must invoke <CODE>releaseReadLock</CODE> x times to release all the locks.
* <p>
* LockManager just maps locks to resources and they do all the hard work
* together with a resource allocation graph.
*/
public class LockManager
{
private final Map<Object,RWLock> resourceLockMap =
new HashMap<Object,RWLock>();
private final RagManager ragManager;
public LockManager( TransactionManager tm )
{
ragManager = new RagManager( tm );
}
public long getDetectedDeadlockCount()
{
return ragManager.getDeadlockCount();
}
/**
* Tries to acquire read lock on <CODE>resource</CODE> for the current
* transaction. If read lock can't be acquired the transaction will wait for
* the lransaction until it can acquire it. If waiting leads to dead lock a
* {@link DeadlockDetectedException} will be thrown.
*
* @param resource
* The resource
* @throws DeadlockDetectedException
* If a deadlock is detected
* @throws IllegalResourceException
*/
public void getReadLock( Object resource )
throws DeadlockDetectedException, IllegalResourceException
{
if ( resource == null )
{
throw new IllegalResourceException( "Null parameter" );
}
RWLock lock = null;
synchronized ( resourceLockMap )
{
lock = resourceLockMap.get( resource );
if ( lock == null )
{
lock = new RWLock( resource, ragManager );
resourceLockMap.put( resource, lock );
}
lock.mark();
}
lock.acquireReadLock();
}
/**
* Tries to acquire write lock on <CODE>resource</CODE> for the current
* transaction. If write lock can't be acquired the transaction will wait
* for the lock until it can acquire it. If waiting leads to dead lock a
* {@link DeadlockDetectedException} will be thrown.
*
* @param resource
* The resource
* @throws DeadlockDetectedException
* If a deadlock is detected
* @throws IllegalResourceException
*/
public void getWriteLock( Object resource )
throws DeadlockDetectedException, IllegalResourceException
{
if ( resource == null )
{
throw new IllegalResourceException( "Null parameter" );
}
RWLock lock = null;
synchronized ( resourceLockMap )
{
lock = resourceLockMap.get( resource );
if ( lock == null )
{
lock = new RWLock( resource, ragManager );
resourceLockMap.put( resource, lock );
}
lock.mark();
}
lock.acquireWriteLock();
}
/**
* Releases a read lock held by the current transaction on <CODE>resource</CODE>.
* If current transaction don't have read lock a
* {@link LockNotFoundException} will be thrown.
*
* @param resource
* The resource
* @throws IllegalResourceException
* @throws LockNotFoundException
*/
public void releaseReadLock( Object resource, Transaction tx )
throws LockNotFoundException, IllegalResourceException
{
if ( resource == null )
{
throw new IllegalResourceException( "Null parameter" );
}
RWLock lock = null;
synchronized ( resourceLockMap )
{
lock = resourceLockMap.get( resource );
if ( lock == null )
{
throw new LockNotFoundException( "Lock not found for: "
+ resource );
}
if ( !lock.isMarked() && lock.getReadCount() == 1 &&
lock.getWriteCount() == 0 &&
lock.getWaitingThreadsCount() == 0 )
{
resourceLockMap.remove( resource );
}
lock.releaseReadLock(tx);
}
}
/**
* Releases a read lock held by the current transaction on <CODE>resource</CODE>.
* If current transaction don't have read lock a
* {@link LockNotFoundException} will be thrown.
*
* @param resource
* The resource
* @throws IllegalResourceException
* @throws LockNotFoundException
*/
public void releaseWriteLock( Object resource, Transaction tx )
throws LockNotFoundException, IllegalResourceException
{
if ( resource == null )
{
throw new IllegalResourceException( "Null parameter" );
}
RWLock lock = null;
synchronized ( resourceLockMap )
{
lock = resourceLockMap.get( resource );
if ( lock == null )
{
throw new LockNotFoundException( "Lock not found for: "
+ resource );
}
if ( !lock.isMarked() && lock.getReadCount() == 0 &&
lock.getWriteCount() == 1 &&
lock.getWaitingThreadsCount() == 0 )
{
resourceLockMap.remove( resource );
}
lock.releaseWriteLock(tx);
}
}
/**
* Utility method for debugging. Dumps info to console of txs having locks
* on resources.
*
* @param resource
*/
public void dumpLocksOnResource( Object resource )
{
RWLock lock = null;
synchronized ( resourceLockMap )
{
if ( !resourceLockMap.containsKey( resource ) )
{
System.out.println( "No locks on " + resource );
return;
}
lock = resourceLockMap.get( resource );
}
lock.dumpStack();
}
/**
* Utility method for debugging. Dumps the resource allocation graph to
* console.
*/
public void dumpRagStack()
{
ragManager.dumpStack();
}
/**
* Utility method for debugging. Dumps info about each lock to console.
*/
public void dumpAllLocks()
{
synchronized ( resourceLockMap )
{
Iterator<RWLock> itr = resourceLockMap.values().iterator();
int emptyLockCount = 0;
while ( itr.hasNext() )
{
RWLock lock = itr.next();
if ( lock.getWriteCount() > 0 || lock.getReadCount() > 0 )
{
lock.dumpStack();
}
else
{
if ( lock.getWaitingThreadsCount() > 0 )
{
lock.dumpStack();
}
emptyLockCount++;
}
}
if ( emptyLockCount > 0 )
{
System.out.println( "There are " + emptyLockCount
+ " empty locks" );
}
else
{
System.out.println( "There are no empty locks" );
}
}
}
}