/** * Copyright (c) 2002-2010 "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 Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.kernel.impl.transaction; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Test; import org.neo4j.kernel.DeadlockDetectedException; import org.neo4j.kernel.impl.transaction.LockManager; public class TestDeadlockDetection { private static LockManager lm = new LockManager( new PlaceboTm() ); private static class HelperThread extends Thread { private static final int DO_NOTHING_TASK = 0; private static final int GET_WRITELOCK_TASK = 1; private static final int GET_READLOCK_TASK = 2; private static final int RELEASE_WRITELOCK_TASK = 3; private static final int RELEASE_READLOCK_TASK = 4; private static final int QUIT_TASK = 5; private String name = null; private int nextTask = 0; private boolean taskCompleted = true; private Object resource = null; private boolean deadlockOnLastWait = false; HelperThread( String name ) { super(); this.name = name; } public synchronized void run() { try { while ( nextTask != QUIT_TASK ) { switch ( nextTask ) { case DO_NOTHING_TASK: wait( 10 ); break; case GET_WRITELOCK_TASK: try { lm.getWriteLock( this.resource ); deadlockOnLastWait = false; } catch ( DeadlockDetectedException e ) { deadlockOnLastWait = true; } taskCompleted = true; nextTask = DO_NOTHING_TASK; break; case GET_READLOCK_TASK: try { lm.getReadLock( this.resource ); deadlockOnLastWait = false; } catch ( DeadlockDetectedException e ) { deadlockOnLastWait = true; } taskCompleted = true; nextTask = DO_NOTHING_TASK; break; case RELEASE_WRITELOCK_TASK: lm.releaseWriteLock( this.resource ); taskCompleted = true; nextTask = DO_NOTHING_TASK; break; case RELEASE_READLOCK_TASK: lm.releaseReadLock( this.resource ); taskCompleted = true; nextTask = DO_NOTHING_TASK; break; case QUIT_TASK: break; default: throw new RuntimeException( "Unknown task " + nextTask ); } } } catch ( Exception e ) { taskCompleted = true; System.out .println( "" + this + " unable to execute task, " + e ); e.printStackTrace(); throw new RuntimeException( e ); } } synchronized void waitForCompletionOfTask() { while ( !taskCompleted ) { try { wait( 20 ); } catch ( InterruptedException e ) { } } } boolean isLastGetLockDeadLock() { return deadlockOnLastWait; } synchronized void getWriteLock( Object resource ) { if ( !taskCompleted ) { throw new RuntimeException( "Previous task not completed" ); } this.resource = resource; taskCompleted = false; nextTask = GET_WRITELOCK_TASK; } synchronized void getReadLock( Object resource ) { if ( !taskCompleted ) { throw new RuntimeException( "Previous task not completed" ); } this.resource = resource; taskCompleted = false; nextTask = GET_READLOCK_TASK; } synchronized void releaseWriteLock( Object resource ) { if ( !taskCompleted ) { throw new RuntimeException( "Previous task not completed" ); } this.resource = resource; taskCompleted = false; nextTask = RELEASE_WRITELOCK_TASK; } synchronized void releaseReadLock( Object resource ) { if ( !taskCompleted ) { throw new RuntimeException( "Previous task not completed" ); } this.resource = resource; taskCompleted = false; nextTask = RELEASE_READLOCK_TASK; } void quit() { this.resource = null; taskCompleted = false; nextTask = QUIT_TASK; } public String toString() { return name; } } private static class ResourceObject { private String name = null; ResourceObject( String name ) { this.name = name; } public String toString() { return this.name; } } @Test public void testDeadlockDetection() { Object r1 = new ResourceObject( "R1" ); Object r2 = new ResourceObject( "R2" ); Object r3 = new ResourceObject( "R3" ); Object r4 = new ResourceObject( "R4" ); HelperThread t1 = new HelperThread( "T1" ); HelperThread t2 = new HelperThread( "T2" ); HelperThread t3 = new HelperThread( "T3" ); HelperThread t4 = new HelperThread( "T4" ); try { t1.start(); t2.start(); t3.start(); t4.start(); t1.getReadLock( r1 ); t1.waitForCompletionOfTask(); t1.getReadLock( r4 ); t1.waitForCompletionOfTask(); t2.getReadLock( r2 ); t2.waitForCompletionOfTask(); t2.getReadLock( r3 ); t2.waitForCompletionOfTask(); t3.getReadLock( r3 ); t3.waitForCompletionOfTask(); t3.getWriteLock( r1 ); // t3-r1-t1 t2.getWriteLock( r4 ); sleepSome(); // t2-r4-t1 t1.getWriteLock( r2 ); t1.waitForCompletionOfTask(); assertTrue( t1.isLastGetLockDeadLock() ); // t1-r2-t2-r4-t1 // resolve and try one more time t1.releaseReadLock( r4 ); t2.waitForCompletionOfTask(); // will give r4 to t2 t1.getWriteLock( r2 ); // t1-r2-t2 t2.releaseReadLock( r2 ); t1.waitForCompletionOfTask(); // will give r2 to t1 t1.getWriteLock( r4 ); // t1-r4-t2 // dead lock sleepSome(); t2.getWriteLock( r2 ); t2.waitForCompletionOfTask(); assertTrue( t2.isLastGetLockDeadLock() ); // t2-r2-t3-r1-t1-r4-t2 or t2-r2-t1-r4-t2 t2.releaseWriteLock( r4 ); t1.waitForCompletionOfTask(); // give r4 to t1 t1.releaseWriteLock( r4 ); t2.getReadLock( r4 ); t2.waitForCompletionOfTask(); t1.releaseWriteLock( r2 ); t1.waitForCompletionOfTask(); t1.getReadLock( r2 ); t1.waitForCompletionOfTask(); t1.releaseReadLock( r1 ); t3.waitForCompletionOfTask(); // give r1 to t3 t3.getReadLock( r2 ); t3.waitForCompletionOfTask(); t3.releaseWriteLock( r1 ); t1.getReadLock( r1 ); t1.waitForCompletionOfTask(); // give r1->t1 t1.getWriteLock( r4 ); t3.getWriteLock( r1 ); t4.getReadLock( r2 ); t4.waitForCompletionOfTask(); // deadlock t2.getWriteLock( r2 ); t2.waitForCompletionOfTask(); assertTrue( t2.isLastGetLockDeadLock() ); // t2-r2-t3-r1-t1-r4-t2 // resolve t2.releaseReadLock( r4 ); t1.waitForCompletionOfTask(); t1.releaseWriteLock( r4 ); t1.waitForCompletionOfTask(); t1.releaseReadLock( r1 ); t1.waitForCompletionOfTask(); t2.getReadLock( r4 ); t3.waitForCompletionOfTask(); // give r1 to t3 t3.releaseWriteLock( r1 ); t1.getReadLock( r1 ); t1.waitForCompletionOfTask(); // give r1 to t1 t1.getWriteLock( r4 ); t3.releaseReadLock( r2 ); t3.waitForCompletionOfTask(); t3.getWriteLock( r1 ); // cleanup t2.releaseReadLock( r4 ); t1.waitForCompletionOfTask(); // give r4 to t1 t1.releaseWriteLock( r4 ); t1.waitForCompletionOfTask(); t1.releaseReadLock( r1 ); t3.waitForCompletionOfTask(); // give r1 to t3 t3.releaseWriteLock( r1 ); t3.waitForCompletionOfTask(); t1.releaseReadLock( r2 ); t4.releaseReadLock( r2 ); t2.releaseReadLock( r3 ); t3.releaseReadLock( r3 ); t1.waitForCompletionOfTask(); t2.waitForCompletionOfTask(); t3.waitForCompletionOfTask(); t4.waitForCompletionOfTask(); // -- special case... t1.getReadLock( r1 ); t1.waitForCompletionOfTask(); t2.getReadLock( r1 ); t2.waitForCompletionOfTask(); t1.getWriteLock( r1 ); // t1->r1-t1&t2 sleepSome(); t2.getWriteLock( r1 ); t2.waitForCompletionOfTask(); assertTrue( t2.isLastGetLockDeadLock() ); // t2->r1->t1->r1->t2 t2.releaseReadLock( r1 ); t1.waitForCompletionOfTask(); t1.releaseReadLock( r1 ); t1.waitForCompletionOfTask(); t1.releaseWriteLock( r1 ); t1.waitForCompletionOfTask(); } catch ( Exception e ) { // RagManager.getManager().dumpStack(); // LockManager.getManager().dumpRagStack(); e.printStackTrace(); fail( "Deadlock detection failed" + e ); } finally { t1.quit(); t2.quit(); t3.quit(); t4.quit(); } } private void sleepSome() { try { Thread.sleep( 1000 ); } catch ( InterruptedException e ) { } } public static class StressThread extends Thread { private static java.util.Random rand = new java.util.Random( System .currentTimeMillis() ); private static final Object READ = new Object(); private static final Object WRITE = new Object(); private static ResourceObject resources[] = new ResourceObject[10]; private static boolean go = false; private String name; private int numberOfIterations; private int depthCount; private float readWriteRatio; StressThread( String name, int numberOfIterations, int depthCount, float readWriteRatio ) { super(); this.name = name; this.numberOfIterations = numberOfIterations; this.depthCount = depthCount; this.readWriteRatio = readWriteRatio; } public void run() { try { while ( !go ) { try { sleep( 100 ); } catch ( InterruptedException e ) { } } java.util.Stack<Object> lockStack = new java.util.Stack<Object>(); java.util.Stack<ResourceObject> resourceStack = new java.util.Stack<ResourceObject>(); try { for ( int i = 0; i < numberOfIterations; i++ ) { int depth = depthCount; do { float f = rand.nextFloat(); int n = rand.nextInt( resources.length ); if ( f < readWriteRatio ) { lm.getReadLock( resources[n] ); lockStack.push( READ ); } else { lm.getWriteLock( resources[n] ); lockStack.push( WRITE ); } resourceStack.push( resources[n] ); } while ( --depth > 0 ); /* * try { sleep( rand.nextInt( 100 ) ); } catch ( * InterruptedException e ) {} */ while ( !lockStack.isEmpty() ) { if ( lockStack.pop() == READ ) { lm.releaseReadLock( resourceStack.pop() ); } else { lm.releaseWriteLock( resourceStack.pop() ); } } } } catch ( DeadlockDetectedException e ) { // System.out.println( "Deadlock detected!" ); } finally { while ( !lockStack.isEmpty() ) { if ( lockStack.pop() == READ ) { lm.releaseReadLock( resourceStack.pop() ); } else { lm.releaseWriteLock( resourceStack.pop() ); } } } } catch ( Exception e ) { e.printStackTrace(); throw new RuntimeException( e ); } } public String toString() { return this.name; } } @Test public void testStressMultipleThreads() { for ( int i = 0; i < StressThread.resources.length; i++ ) { StressThread.resources[i] = new ResourceObject( "RX" + i ); } Thread stressThreads[] = new Thread[50]; StressThread.go = false; for ( int i = 0; i < stressThreads.length; i++ ) { stressThreads[i] = new StressThread( "T" + i, 100, 10, 0.80f ); } for ( int i = 0; i < stressThreads.length; i++ ) { stressThreads[i].start(); } StressThread.go = true; } }