blob: 0d10824f912dc49c1ca980e43e6c300958265c13 [file] [log] [blame]
package com.google.inject.internal;
import com.google.inject.internal.CycleDetectingLock.CycleDetectingLockFactory;
import junit.framework.TestCase;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class CycleDetectingLockTest extends TestCase {
static final long DEADLOCK_TIMEOUT_SECONDS = 1;
/**
* Verifies that graph of threads' dependencies is not static and is calculated in runtime using
* information about specific locks.
*
* <pre>
* T1: Waits on S1
* T2: Locks B, sends S1, waits on S2
* T1: Locks A, start locking B, sends S2, waits on S3
* T2: Unlocks B, start locking A, sends S3, finishes locking A, unlocks A
* T1: Finishes locking B, unlocks B, unlocks A
* </pre>
*
* <p>This should succeed, even though T1 was locked on T2 and T2 is locked on T1 when T2 locks
* A. Incorrect implementation detects a cycle waiting on S3.
*/
public void testSingletonThreadsRuntimeCircularDependency() throws Exception {
final CyclicBarrier signal1 = new CyclicBarrier(2);
final CyclicBarrier signal2 = new CyclicBarrier(2);
final CyclicBarrier signal3 = new CyclicBarrier(2);
CycleDetectingLockFactory<String> lockFactory = new CycleDetectingLockFactory<String>();
final CycleDetectingLock<String> lockA =
lockFactory.new ReentrantCycleDetectingLock("A", new ReentrantLock() {
@Override
public void lock() {
if (Thread.currentThread().getName().equals("T2")) {
try {
signal3.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
assertEquals("T1", Thread.currentThread().getName());
}
super.lock();
}
});
final CycleDetectingLock<String> lockB =
lockFactory.new ReentrantCycleDetectingLock("B", new ReentrantLock() {
@Override
public void lock() {
if (Thread.currentThread().getName().equals("T1")) {
try {
signal2.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
signal3.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
assertEquals("T2", Thread.currentThread().getName());
}
super.lock();
}
});
Future<Void> firstThreadResult = Executors.newSingleThreadExecutor().submit(
new Callable<Void>() {
public Void call() throws Exception {
Thread.currentThread().setName("T1");
signal1.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
assertTrue(lockA.lockOrDetectPotentialLocksCycle().isEmpty());
assertTrue(lockB.lockOrDetectPotentialLocksCycle().isEmpty());
lockB.unlock();
lockA.unlock();
return null;
}
});
Future<Void> secondThreadResult = Executors.newSingleThreadExecutor().submit(
new Callable<Void>() {
public Void call() throws Exception {
Thread.currentThread().setName("T2");
assertTrue(lockB.lockOrDetectPotentialLocksCycle().isEmpty());
signal1.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
signal2.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
lockB.unlock();
assertTrue(lockA.lockOrDetectPotentialLocksCycle().isEmpty());
lockA.unlock();
return null;
}
});
firstThreadResult.get();
secondThreadResult.get();
}
}