/******************************************************************************* * 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; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; public class Analyzer { public static final int LOCK_NORMAL = 0; public static final int UNLOCK_NORMAL = 1; public static final int LOCK_COUNTED = 2; public static final int UNLOCK_COUNTED = 3; public static final int TYPE_ERROR = 1; public static final int TYPE_WARNING = 2; public static final int TYPE_ERROR_SIGNAL = 3; private boolean trace = false; private boolean isDebug = false; private boolean reportWarningInSameThreadConflicts = false; private boolean throwsOnError = false; private boolean throwsOnWarning = false; private boolean isActive = false; private boolean writeInstrumentedClasses = false; private static int s_internalErrorCount = 0; private HashMap<String, Boolean> instrumentationRestrictions = new HashMap<String, Boolean>(); private Logger logger = new Logger(); ThreadLocal<Integer> enablementCount = new ThreadLocal<Integer>() { protected Integer initialValue() { return new Integer(1); } }; static private Analyzer s_instance; public void setListener(IConflictListener listener) { logger.setListener(listener); } public boolean shouldWriteInstrumentedClasses() { return writeInstrumentedClasses; } public boolean isActive() { return isActive; } public void addRestriction(Class<?> cls, boolean shouldInstrument) { instrumentationRestrictions.put(cls.getCanonicalName().replace('.', '/'), shouldInstrument); } public void setReportWarningsInSameThread(boolean value) { reportWarningInSameThreadConflicts = value; } public void setThrowsOnWarning(boolean value) { throwsOnWarning = value; } public void setThrowsOnError(boolean value) { throwsOnError = value; } public static Analyzer instance() { synchronized(Analyzer.class) { if (s_instance == null) s_instance = new Analyzer(); } return s_instance; } private Analyzer() { String value = System.getProperty(Settings.DUMP_LOCK_INFO); if (value != null) setupDumpOnExit(value); reportWarningInSameThreadConflicts = Boolean.getBoolean(Settings.REPORT_WARNINGS); throwsOnError = Boolean.getBoolean(Settings.THROWS_ON_ERROR); throwsOnWarning = Boolean.getBoolean(Settings.THROWS_ON_WARNING); trace = Boolean.getBoolean(Settings.TRACE); writeInstrumentedClasses = Boolean.getBoolean(Settings.WRITE_INSTRUMENTED_CLASSES); // separated by semicolons (;) value = System.getProperty(Settings.INSTRUMENT_ONLY_LIST); if (value != null) { String classes[] = value.split(";"); for (String cls : classes) instrumentationRestrictions.put(cls.replace('.', '/'), true); } } private void setupDumpOnExit(final String file) { System.setSecurityManager(new SecurityManager() { @Override public void checkExit(int status) { Logger.dumpLockInformation(file); super.checkExit(status); } }); } void activate() { isActive = true; } void enable() { Integer count = enablementCount.get(); enablementCount.set(count + 1); } void disable() { Integer count = enablementCount.get(); enablementCount.set(count - 1); } boolean isEnabled() { return enablementCount.get() > 0; } // last item is the latest acquired static public class AcquisitionOrder { ArrayList<LockInfo> order = new ArrayList<LockInfo>(); StackTraceElement[] defaultStackTrace = null; public LockInfo find(LockInfo precedent) { for (int i = 0; i < order.size(); i++) { LockInfo lockInfo = order.get(i); if (lockInfo.getLock() == precedent.getLock()) return lockInfo; } return null; } } ObjectCache<CustomLock> customLocks = new ObjectCache<CustomLock>(); private CustomLock createCustomLock(Object lock, boolean scoped) { CustomLock customLock; synchronized(customLocks) { customLock = customLocks.get(lock); if (customLock == null) { customLock = new CustomLock(lock, scoped); customLocks.put(lock, customLock); } } return customLock; } static class ThreadLocalEx<T> { protected HashMap<Long, T> map = new HashMap<Long, T>(); public T get() { synchronized (this) { return map.get(Thread.currentThread().getId()); } } public void set(T t) { synchronized (this) { map.put(Thread.currentThread().getId(), t); } } public ArrayList<T> getAll() { synchronized (this) { ArrayList<T> list = new ArrayList<T>(map.values()); return list; } } } ThreadLocalEx<AcquisitionOrder> threadLocal = new ThreadLocalEx<AcquisitionOrder>(); // The global order records each lock and their precedent. // So for example, if a given thread acquires A->B->D->C, then the global order will contain: // A: // B: A // D: A B // C: A B D // So it means that for any given lock, we can know which locks were previously acquired // It also records the guards for each precedent, so that we know to avoid reporting incorrect // lock order when there's a master lock previously acquired. In the example above, the (guards) were: // A: // B: A // D: A B(A) // C: A B(A) D(A B) // If there's a new lock acquisition, for example A->D->C, the guards will be updated so contain the intersection // of all previous guards, for instance: // A: // B: A // D: A B(A) // C: A B(A) D(A) // Phantom guards will be kept to record stack traces, so that if we have the following lock order: B->C->D // (conflict with C->D, D-C),we can know not to report the conflict with the stack trace of "A->B->D->C" // (since B was an active guard then), but instead with "A->D-C", since B wasn't an active guard. ObjectCache<AcquisitionOrder> globalOrder = new ObjectCache<AcquisitionOrder>(); static public void enterLockCustom(Object lock) { Analyzer instance = instance(); CustomLock customLock = instance.createCustomLock(lock, true); instance.enterLock_(customLock, 1); } static public void enterLockCustomUnscoped(Object lock) { Analyzer instance = instance(); CustomLock customLock = instance.createCustomLock(lock, false); instance.enterLock_(customLock, 1); } static public void enterLockCustom(Object lock, int count) { Analyzer instance = instance(); CustomLock customLock = instance.createCustomLock(lock, true); instance.enterLock_(customLock, count); } static public void enterLockCustom(Object lock, long count) { Analyzer instance = instance(); CustomLock customLock = instance.createCustomLock(lock, true); instance.enterLock_(customLock, (int) count); } static public void enterLock(Object lock) { instance().enterLock_(lock, 1); } private void enterLock_(Object lock, int count) { if (!isEnabled()) return; AcquisitionOrder localOrder = threadLocal.get(); if (localOrder == null) { localOrder = new AcquisitionOrder(); threadLocal.set(localOrder); } boolean rollBack = false; LockInfo info = new LockInfo(); try { synchronized(localOrder) { if (trace) System.out.println("enter lock(" + count + "): " + Util.getUniqueIdentifier(lock)); Thread currentThread = Thread.currentThread(); // registering lock info.setLock(lock); info.count = count; info.threadId = getThreadID(currentThread); info.stackTrace = currentThread.getStackTrace(); localOrder.order.add(info); if ((lock instanceof CustomLock) && !((CustomLock) lock).scoped) rollBack = true; // we rollback the insertion of the lock in the local stack for (int localIndex = 0; localIndex < localOrder.order.size() - 1; localIndex++) { LockInfo precedent = localOrder.order.get(localIndex); if (precedent.getLock() == lock) { // If the precedent is the lock, then do not record nor verify the next precedent // as such, because they are effectively acquired after, not before. // For example: // Locking A, B, A // Records only (A->B) as lock order, not (A->B, B->A) return; } } // ensure none of the previously acquired locks were acquired in a different order for (int localIndex = 0; localIndex < localOrder.order.size() - 1; localIndex++) { LockInfo precedent = localOrder.order.get(localIndex); if (precedent.getLock() != lock) { synchronized(globalOrder) { AcquisitionOrder precedentOrder = globalOrder.getFromKey(precedent.getLockKey(), precedent.getLock()); if (precedentOrder != null) { LockInfo conflict = precedentOrder.find(info); List<LockInfo> subList = localIndex > 0? localOrder.order.subList(0, localIndex):new ArrayList<LockInfo>(); if (conflict != null && !conflict.containsCommonGuard(subList)) { ArrayList<Entry<String, StackTraceElement[]>> threadConflicts = conflict.getConflictingThreads(subList, getThreadID()); if (threadConflicts.isEmpty()) { if (reportWarningInSameThreadConflicts) { LockInfo precedentConflict = new LockInfo(); precedentConflict.setLock(precedent.getLock()); precedentConflict.stackTrace = conflict.getContext(getThreadID()); boolean shouldThrow = logger.reportConflict(TYPE_WARNING, getThreadID(), getThreadID(), info, precedent, conflict, precedentConflict); if (throwsOnWarning || shouldThrow) logger.throwException(TYPE_WARNING, info); } } else { boolean shouldThrow = false; Iterator<Entry<String, StackTraceElement[]>> iterator = threadConflicts.iterator(); while (iterator.hasNext()) { Entry<String, StackTraceElement[]> entry = iterator.next(); LockInfo precedentConflict = new LockInfo(); precedentConflict.setLock(precedent.getLock()); precedentConflict.stackTrace = entry.getValue(); shouldThrow |= logger.reportConflict(TYPE_ERROR, getThreadID(), entry.getKey(), info, precedent, conflict, precedentConflict); } if (throwsOnError || shouldThrow) logger.throwException(TYPE_ERROR, info); } } } } } } // record the precedence synchronized(globalOrder) { AcquisitionOrder order = globalOrder.getOrCreate(lock, AcquisitionOrder.class); if (order.defaultStackTrace == null) order.defaultStackTrace = info.stackTrace; ArrayList<LockInfo> guardList = new ArrayList<LockInfo>(); for (int localIndex = 0; localIndex < localOrder.order.size() - 1; localIndex++) { LockInfo precedent = localOrder.order.get(localIndex); LockInfo found = order.find(precedent); if (found == null) { found = precedent.copy(); found.setAcquiringContext(info); order.order.add(found); } found.registerContext(getThreadID(), info.stackTrace, guardList); guardList.add(precedent); } } } } finally { if (rollBack) leaveLock_(info.getLock(), count, false); } } private static String getThreadID() { Thread thread = Thread.currentThread(); return Long.toString(thread.getId()) + " (" + thread.getName() + ")"; } private static String getThreadID(Thread thread) { return Long.toString(thread.getId()) + " (" + thread.getName() + ")"; } static public void leaveLockCustom(Object lock) { Analyzer instance = instance(); CustomLock customLock = instance.createCustomLock(lock, true); instance.leaveLock_(customLock, 1, false); } static public void leaveLockCustom(Object lock, int count) { Analyzer instance = instance(); CustomLock customLock = instance.createCustomLock(lock, true); instance.leaveLock_(customLock, count, false); } static public void leaveLockCustomUnscoped(Object lock) { Analyzer instance = instance(); CustomLock customLock = instance.createCustomLock(lock, false); instance.leaveLock_(customLock, 1, false); } static public void leaveLockCustom(Object lock, long count) { Analyzer instance = instance(); CustomLock customLock = instance.createCustomLock(lock, false); instance.leaveLock_(customLock, (int) count, false); } static public void leaveLock(Object lock) { instance().leaveLock_(lock, 1, false); } private void leaveLock_(Object lock, int count, boolean rollback) { if (!isEnabled()) return; AcquisitionOrder localOrder = threadLocal.get(); if (localOrder == null || (leaveLockInThread(localOrder, lock, count) == null)) { ArrayList<AcquisitionOrder> orders = threadLocal.getAll(); LockInfo otherThreadLockAcquisitionInfo = null; for (AcquisitionOrder order : orders) { otherThreadLockAcquisitionInfo = leaveLockInThread(order, lock, count); if (otherThreadLockAcquisitionInfo != null) break; } if (!rollback) { boolean isScoped = !(lock instanceof CustomLock) || ((CustomLock)lock).scoped; if ((otherThreadLockAcquisitionInfo == null) && isScoped) { synchronized(unkownLocks) { Boolean value = unkownLocks.get(lock); if (value == null) { unkownLocks.put(lock, true); error("ERROR: leaving unknown lock (" + Util.getUniqueIdentifier(lock) + ")"); } } } if (localOrder != null) { synchronized(localOrder) { // if we release the lock on a different thread, we must verify that no common locks // exist, since otherwise, it can cause a deadlock. // For example, if thread1 acquires A -> B -> C, then thread2 acquires A then releases C // this means that thread1 can be blocked on C while thread2 is block on A, never getting // to release C. // To verify this, we must get the global precedents of C, and see if any match the current // list of precedents on the local thread list, and see if any of those precedents were // acquired in a different thread. String currentThreadID = getThreadID(); for (int localIndex = 0; localIndex < localOrder.order.size(); localIndex++) { LockInfo precedent = localOrder.order.get(localIndex); if (precedent.getLock() != lock) { synchronized(globalOrder) { AcquisitionOrder precedentOrder = globalOrder.get(lock); if (precedentOrder != null) { LockInfo commonPredecent = precedentOrder.find(precedent); if (commonPredecent != null) { ArrayList<Entry<String, StackTraceElement[]>> otherThreads = commonPredecent.findOtherContextThanThread(currentThreadID); if (otherThreads.size() > 0) { LockInfo currentLock = new LockInfo(); Thread currentThread = Thread.currentThread(); currentLock.setLock(lock); currentLock.count = 1; currentLock.threadId = getThreadID(currentThread); currentLock.stackTrace = currentThread.getStackTrace(); boolean shouldThrow = logger.reportConflict(TYPE_ERROR_SIGNAL, currentThreadID, currentThreadID, currentLock, commonPredecent, commonPredecent.getAquiringContext(), precedent); if (throwsOnError || shouldThrow) logger.throwException(TYPE_ERROR_SIGNAL, precedent); } } } } } } } } } } } private LockInfo leaveLockInThread(AcquisitionOrder localOrder, Object lock, int count) { synchronized(localOrder) { if (trace) System.out.println("leave lock(" + count + "): " + Util.getUniqueIdentifier(lock)); for (int i = localOrder.order.size() - 1; i >= 0; i--) { LockInfo info = localOrder.order.get(i); if (info.getLock() == lock) { localOrder.order.remove(i); return info; } } return null; } } ObjectCache<Boolean> unkownLocks = new ObjectCache<Boolean>(); public int getInternalErrorCount() { return s_internalErrorCount; } static void error(String str) { s_internalErrorCount++; System.out.println(Logger.getPrintOutHeader() + str); Logger.printStrackTrace(Thread.currentThread().getStackTrace()); } public boolean shouldInstrument(Class<?> cls) { return shouldInstrument(cls.getCanonicalName().replace('.', '/')); } public boolean shouldInstrument(String className) { if (instrumentationRestrictions.isEmpty()) return true; Boolean value = instrumentationRestrictions.get(className); if (value == null) return false; return value; } public boolean isDebug() { return isDebug; } public int getCurrentLockCount() { AcquisitionOrder localOrder = threadLocal.get(); if (localOrder == null) return 0; return localOrder.order.size(); } }