/******************************************************************************* * Copyright (c) 2010 Freescale Semiconductor. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Serge Beauchamp (Freescale Semiconductor) - initial API and implementation *******************************************************************************/ package com.freescale.deadlockpreventer.tests; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.HashMap; import java.util.concurrent.locks.ReentrantLock; import org.eclipse.core.runtime.jobs.ILock; import org.eclipse.core.runtime.jobs.Job; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.freescale.deadlockpreventer.Analyzer; import com.freescale.deadlockpreventer.IConflictListener; import com.freescale.deadlockpreventer.NetworkClientListener; import com.freescale.deadlockpreventer.NetworkServer; import com.freescale.deadlockpreventer.ReportService; public class Main { @Test public void testProperty() { final Property property = new Property(); fork(new Runnable() { @Override public void run() { property.get("foo"); } }); property.set("bar", "value"); assertConflictError(); } private Object lockA = new Object(); private String lockB = new String(); private Object lockC = new Object(); private Object lockD = new Object(); private Object lockE = new Object(); private ILock lock_Eclipse_A = Job.getJobManager().newLock(); private ILock lock_Eclipse_B = Job.getJobManager().newLock(); ConflictReporter defaultReporter = new ConflictReporter(); private void assertNoConflict() { assertTrue(defaultReporter.noReports); defaultReporter.reset(); } private void assertConflictError() { assertTrue(defaultReporter.gotError); defaultReporter.reset(); } private void assertConflictSignalError() { assertTrue(defaultReporter.gotErrorSignal); defaultReporter.reset(); } private void assertConflictWarning() { assertTrue(defaultReporter.gotWarning); defaultReporter.reset(); } @Before public void setUp() throws Exception { Analyzer.instance().setReportWarningsInSameThread(true); Analyzer.instance().setThrowsOnError(false); Analyzer.instance().setThrowsOnWarning(false); Analyzer.instance().setListener(defaultReporter); assertTrue(Analyzer.instance().isActive()); assertTrue(Analyzer.instance().shouldInstrument(Main.class)); assertTrue(Analyzer.instance().getCurrentLockCount() == 0); } @After public void cleanUp() { assertTrue(Analyzer.instance().getCurrentLockCount() == 0); Analyzer.instance().setReportWarningsInSameThread(false); Analyzer.instance().setThrowsOnError(false); Analyzer.instance().setThrowsOnWarning(false); Analyzer.instance().setListener(null); assertTrue(Analyzer.instance().getInternalErrorCount() == 0); } @Test public void test0() { assertTrue(Analyzer.instance().isActive()); } @Test public void test1() { synchronized(this) { code(); } assertNoConflict(); } @Test public synchronized void test2() { code(); assertNoConflict(); } @Test public synchronized void test3() { synchronized(this) { code(); } assertNoConflict(); } @Test public synchronized void test4() { synchronized(lockA) { code(); } assertNoConflict(); } @Test public void generateWarning() { test_a(); test_b(); assertConflictWarning(); } @Test public void generateError() { fork(new Runnable() { @Override public void run() { test_c(); } }); test_d(); assertConflictError(); } private String getLock_b() { return lockB; } private void test_a() { synchronized(lockB) { synchronized(this) { code(); } } } private synchronized void test_b() { synchronized(getLock_b()) { code(); } } private void test_c() { synchronized(lockC) { synchronized(this) { code(); } } } private synchronized void test_d() { synchronized(lockC) { code(); } } @Test public void testRequiresMultiLevelDependents() { test_e(); test_f(); assertConflictWarning(); } private void test_e() { synchronized(lock_Eclipse_A) { synchronized(this) { code(); } } } private synchronized void test_f() { synchronized(lock_Eclipse_A) { code(); } } @Test public void generateEclipseWarning() { test_e1(); test_f1(); assertConflictWarning(); } private void test_e1() { try { lock_Eclipse_A.acquire(); synchronized(this) { code(); } } finally { lock_Eclipse_A.release(); } } private synchronized void test_f1() { try { lock_Eclipse_A.acquire(); code(); } finally { lock_Eclipse_A.release(); } } @Test public void generateErrorMultiLevel() { fork(new Runnable() { @Override public void run() { test_c1(); } }); test_d1(); assertConflictError(); } private void test_c1() { synchronized(lockD) { synchronized(this) { synchronized(lockE) { code(); } } } } private synchronized void test_d1() { synchronized(lockE) { synchronized(lockD) { code(); } } } private void code() { System.out.println(Thread.currentThread().getStackTrace()[2]); } @Test public void generateEclipseError() { test_e2(); test_f2(); assertConflictWarning(); } private void test_e2() { try { lock_Eclipse_A.acquire(); try { lock_Eclipse_B.acquire(); code(); } finally { lock_Eclipse_B.release(); } } finally { lock_Eclipse_A.release(); } } private void test_f2() { try { lock_Eclipse_B.acquire(); try { lock_Eclipse_A.acquire(); code(); } finally { lock_Eclipse_A.release(); } } finally { lock_Eclipse_B.release(); } } Object slave1 = new Object(); Object slave2 = new Object(); Object master = new Object(); @Test public void testMasterLockNonLock() { synchronized(master) { synchronized(slave1) { synchronized(slave2) { } } } synchronized(slave2) { synchronized(slave1) { } } assertConflictWarning(); } Object slave1a = new String("slave1a"); Object slave2a = new String("slave2a"); Object master_a = new String("master_a");; @Test public void testMasterLock() { synchronized(master_a) { synchronized(slave1a) { synchronized(slave2a) { } } } // the following should work synchronized(master_a) { synchronized(slave2a) { synchronized(slave1a) { } } } assertNoConflict(); } Object recursive1 = new Object(); Object recursive2 = new Object(); @Test public void testRecursive() { synchronized(recursive1) { synchronized(recursive2) { synchronized(recursive1) { } } } // the order <recursive2, recursive1> should not have been recorded synchronized(recursive1) { synchronized(recursive2) { } } assertNoConflict(); } private final class ServerListener implements ReportService.IListener { public boolean wasCalled = false; public int report(String type, String threadID, String conflictThreadID, String lock, String[] lockStack, String precedent, String[] precedentStack, String conflict, String[] conflictStack, String conflictPrecedent, String[] conflictPrecedentStack, String message) { wasCalled = true; return IConflictListener.CONTINUE; } } private final class ConflictReporter implements IConflictListener { @Override public int report(int type, String threadID, String conflictThreadID, Object lock, StackTraceElement[] lockStack, Object precedent, StackTraceElement[] precedentStack, Object conflict, StackTraceElement[] conflictStack, Object conflictPrecedent, StackTraceElement[] conflictPrecedentStack, String message) { noReports = false; if (type == Analyzer.TYPE_WARNING) gotWarning = true; if (type == Analyzer.TYPE_ERROR) gotError = true; if (type == Analyzer.TYPE_ERROR_SIGNAL) gotErrorSignal = true; this.lock = lock; this.precedent = precedent; this.conflict = conflict; this.conflictPrecedent = conflictPrecedent; return IConflictListener.CONTINUE | IConflictListener.LOG_TO_CONSOLE; } public void reset() { noReports = true; gotWarning = false; gotError = false; gotErrorSignal = false; } public boolean noReports = true; public boolean gotWarning = false; public boolean gotError = false; public boolean gotErrorSignal = false; public Object lock; public Object precedent; public Object conflict; public Object conflictPrecedent; public boolean hasNoErrors() { return gotWarning == false && gotError == false && gotErrorSignal == false; } } static class Entry { boolean gotWarning = false; boolean gotError = false; } @Test public void useJDKLocks() { Analyzer.instance().setThrowsOnWarning(false); ConflictReporter reporter = new ConflictReporter(); Analyzer.instance().setListener(reporter); ReentrantLock lock = new ReentrantLock(); ReentrantLock lock2 = new ReentrantLock(); try { lock.lock(); try { lock2.lock(); } finally { lock2.unlock(); } } finally { lock.unlock(); } try { try { lock2.lock(); try { lock.lock(); } finally { lock.unlock(); } } finally { lock2.unlock(); } } finally { assertTrue(reporter.gotWarning); assertTrue(reporter.lock == lock); assertTrue(reporter.precedent == lock2); assertTrue(reporter.conflict == lock); assertTrue(reporter.conflictPrecedent == lock2); } } @Test public void useJDKLocksError() { Analyzer.instance().setThrowsOnWarning(false); Analyzer.instance().setThrowsOnError(false); ConflictReporter reporter = new ConflictReporter(); Analyzer.instance().setListener(reporter); final ReentrantLock lock = new ReentrantLock(); final ReentrantLock lock2 = new ReentrantLock(); fork(new Runnable() { @Override public void run() { try { lock.lock(); try { lock2.lock(); } finally { lock2.unlock(); } } finally { lock.unlock(); } } }); try { try { lock2.lock(); try { lock.lock(); } finally { lock.unlock(); } } finally { lock2.unlock(); } } finally { assertTrue(reporter.gotError); } } Object slave1b = new String("slave1b"); Object slave2b = new String("slave2b"); Object master_b = new String("master_b");; @Test public void testMasterLockInThread() { Runnable runnable = new Runnable() { public void run() { synchronized(master_b) { synchronized(slave1b) { synchronized(slave2b) { } } } } }; // the following should work synchronized(master_b) { synchronized(slave2b) { synchronized(slave1b) { } } } fork(runnable); assertNoConflict(); } Object slave1c = new String("slave1c"); Object slave2c = new String("slave2c"); Object master_c = new String("master_c");; @Test public void testMasterLockNotCovered() { synchronized(master_c) { synchronized(slave1c) { synchronized(slave2c) { } } } synchronized(master_c) { synchronized(slave2c) { synchronized(slave1c) { } } } // the following should fail synchronized(slave2c) { synchronized(slave1c) { } } assertConflictWarning(); } @Test public void testReturnStatements() { returnVoid(); boolean b = returnBoolean(); assertTrue(b == true); int i = returnInt(); assertTrue(i == 2); char c = returnChar(); assertTrue(c == 's'); long l = returnLong(); assertTrue(l == 23); float f = returnFloat(); assertTrue(f == 23.2f); double d = returnDouble(); assertTrue(d == 23.2); int[] a = returnArray(); assertTrue(a[0]== 2 && a[1] == 3); Object o = returnObject(); assertTrue(o.equals("string")); Object[] oa = returnObjectArray(); assertTrue(oa[0].equals("hi") && oa[1].equals("hello")); } private synchronized Object returnObject() { code(); return "string"; } private synchronized Object[] returnObjectArray() { code(); return new Object[] {"hi", "hello"}; } private synchronized void returnVoid() { code(); } private synchronized boolean returnBoolean() { code(); return true; } private synchronized int returnInt() { code(); return 2; } private synchronized char returnChar() { code(); return 's'; } private synchronized long returnLong() { code(); return 23; } private synchronized float returnFloat() { code(); return 23.2f; } private synchronized double returnDouble() { code(); return 23.2; } private synchronized int[] returnArray() { code(); return new int[] {2, 3}; } static class DeadlockingToString { boolean toStringCalled = false; public String toString() { toStringCalled = true; return super.toString(); } } @Test public void testToStringIsNotCalled() { DeadlockingToString lock = new DeadlockingToString(); synchronized(lock) { code(); } assertTrue(lock.toStringCalled == false); } @Test public void testToStringIsNotCalledWithConflict() { final DeadlockingToString lock1 = new DeadlockingToString(); final DeadlockingToString lock2 = new DeadlockingToString(); fork(new Runnable() { public void run() { synchronized(lock1) { synchronized(lock2) { code(); } } } }); synchronized(lock2) { synchronized(lock1) { code(); } } assertConflictError(); assertTrue(lock1.toStringCalled == false); assertTrue(lock2.toStringCalled == false); } @Test public void testToStringIsNotCalledWithConflictInNetworkClient() { NetworkServer server = new NetworkServer(); server.start(0); String key = server.createNewSessionKey(ReportService.ID); ReportService service = new ReportService(); server.registerSevice(key, service); ServerListener listener = new ServerListener(); service.setListener(listener); NetworkClientListener client = new NetworkClientListener("localhost:" + server.getListeningPort() + ":" + key); Analyzer.instance().setListener(client); final DeadlockingToString lock1 = new DeadlockingToString(); final DeadlockingToString lock2 = new DeadlockingToString(); fork(new Runnable() { public void run() { synchronized(lock1) { synchronized(lock2) { code(); } } } }); synchronized(lock2) { synchronized(lock1) { code(); } } client.stop(); server.stop(); assertTrue(lock1.toStringCalled == false); assertTrue(lock2.toStringCalled == false); assertTrue(listener.wasCalled); } private void fork(Runnable runnable) { Thread thread = new Thread(runnable); thread.start(); try { thread.join(); } catch (InterruptedException e1) { e1.printStackTrace(); } } @Test public void testLocalStackIsProper() { final ReentrantLock lock = new ReentrantLock(); final ReentrantLock lock2 = new ReentrantLock(); lock.lock(); assertTrue(Analyzer.instance().getCurrentLockCount() == 1); fork(new Runnable() { @Override public void run() { assertTrue(Analyzer.instance().getCurrentLockCount() == 0); lock2.lock(); assertTrue(Analyzer.instance().getCurrentLockCount() == 1); } }); assertTrue(Analyzer.instance().getCurrentLockCount() == 1); lock.unlock(); assertTrue(Analyzer.instance().getCurrentLockCount() == 0); assertNoConflict(); } @Test public void testAcquiringOnOtherThreadIsSupported() { final CustomLock lock = new CustomLock(); lock.lock(); assertTrue(Analyzer.instance().getCurrentLockCount() == 1); fork(new Runnable() { @Override public void run() { assertTrue(Analyzer.instance().getCurrentLockCount() == 0); lock.unlock(); assertTrue(Analyzer.instance().getCurrentLockCount() == 0); } }); assertTrue(Analyzer.instance().getCurrentLockCount() == 0); final CustomLock lock2 = new CustomLock(); assertTrue(Analyzer.instance().getCurrentLockCount() == 0); fork(new Runnable() { @Override public void run() { assertTrue(Analyzer.instance().getCurrentLockCount() == 0); lock2.lock(); assertTrue(Analyzer.instance().getCurrentLockCount() == 1); } }); assertTrue(Analyzer.instance().getCurrentLockCount() == 0); lock2.unlock(); assertTrue(Analyzer.instance().getCurrentLockCount() == 0); assertTrue(Analyzer.instance().getInternalErrorCount() == 0); assertNoConflict(); } @Test public void testSignaling() { Analyzer.instance().setThrowsOnError(false); ConflictReporter reporter = new ConflictReporter(); Analyzer.instance().setListener(reporter); final CustomSignal signal = new CustomSignal(); signal.signal_wait(); assertTrue(Analyzer.instance().getCurrentLockCount() == 0); fork(new Runnable() { @Override public void run() { assertTrue(Analyzer.instance().getCurrentLockCount() == 0); signal.signal_notify(); assertTrue(Analyzer.instance().getCurrentLockCount() == 0); } }); assertTrue(Analyzer.instance().getCurrentLockCount() == 0); assertTrue(reporter.hasNoErrors()); } @Test public void testSignalingWithLocks() { Analyzer.instance().setThrowsOnError(false); final CustomSignal signal = new CustomSignal(); Object lock1 = new Object(); final Object lock2 = new Object(); synchronized(lock1) { signal.signal_wait(); } fork(new Runnable() { @Override public void run() { synchronized(lock2) { signal.signal_notify(); } } }); assertNoConflict(); } @Test public void testSignalingWithLocks2() { Analyzer.instance().setThrowsOnError(false); final CustomSignal signal = new CustomSignal(); Object lock1 = new Object(); synchronized(lock1) { signal.signal_wait(); } synchronized(lock1) { signal.signal_notify(); } assertNoConflict(); } @Test public void testSignalingWithSameLock() { Analyzer.instance().setThrowsOnError(false); final CustomSignal signal = new CustomSignal(); Object lock1 = new Object(); final Object lock2 = new Object(); final Object lock3 = new Object(); synchronized(lock3) { synchronized(lock1) { signal.signal_wait(); } } fork(new Runnable() { @Override public void run() { synchronized(lock3) { synchronized(lock2) { signal.signal_notify(); } } } }); assertConflictSignalError(); } @Test public void testSignalingWithSameLock2() { Analyzer.instance().setThrowsOnError(false); final CustomSignal signal = new CustomSignal(); final Object lock1 = new Object(); final Object lock2 = new Object(); final Object lock3 = new Object(); synchronized(lock3) { synchronized(lock1) { signal.signal_wait(); } } fork(new Runnable() { @Override public void run() { synchronized(lock1) { synchronized(lock2) { signal.signal_notify(); } } } }); assertConflictSignalError(); } @Test public void testSignalingWithSameLock3() { Analyzer.instance().setThrowsOnError(false); final CustomSignal signal = new CustomSignal(); final Object lock1 = new Object(); final Object lock2 = new Object(); final Object lock3 = new Object(); synchronized(lock3) { synchronized(lock1) { signal.signal_wait(); } } fork(new Runnable() { @Override public void run() { synchronized(lock2) { synchronized(lock1) { signal.signal_notify(); } } } }); assertConflictSignalError(); } @Test public void testSignalingWithDeferredAcquisition() { Analyzer.instance().setThrowsOnError(false); ConflictReporter reporter = new ConflictReporter(); Analyzer.instance().setListener(reporter); final CustomSignal signal = new CustomSignal(); final Object lock1 = new Object(); synchronized(lock1) { signal.signal_wait(); } fork(new Runnable() { @Override public void run() { signal.signal_notify(); } }); assertFalse(reporter.gotErrorSignal); // second acquisition signal.signal_wait(); fork(new Runnable() { @Override public void run() { synchronized(lock1) { signal.signal_notify(); } } }); assertTrue(reporter.gotErrorSignal); assertTrue(reporter.conflict == signal); assertTrue(reporter.lock == signal); assertTrue(reporter.precedent == lock1); assertTrue(reporter.conflictPrecedent == lock1); } @Test public void testPerformance() { long time = System.currentTimeMillis(); long count = 0; for (int i = 0; i < 100000; i++) { String lock = new String("lock_" + i); synchronized(lock) { count += System.currentTimeMillis(); } } System.out.println("time for " + (System.currentTimeMillis() - time) + ", count:" + count); } } class Property { static final public String DEFAULT_VALUE = "some default"; HashMap<String, String> map = new HashMap<String, String>(); synchronized void set(String key, String value) { synchronized(map) { map.put(key, value); } } String get(String key) { synchronized(map) { String value = map.get(key); if (value == null) { value = DEFAULT_VALUE; set(key, value); } return value; } } }