/******************************************************************************* * 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.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import com.freescale.deadlockpreventer.QueryService.IBundleInfo; public class Logger { private boolean abortOnErrors = false; private boolean reportRecurentConflicts = false; private ArrayList<ConflictReport> alreadyReportedConflicts = new ArrayList<ConflictReport>(); private String debugException = IllegalMonitorStateException.class .getCanonicalName(); private Class<?> classToThrow = OrderingException.class; private IConflictListener listener = new DefaultListener(); public class DefaultListener 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) { if (abortOnErrors && ((type & Analyzer.TYPE_ERROR) != 0)) return IConflictListener.ABORT; return IConflictListener.CONTINUE | IConflictListener.LOG_TO_CONSOLE; } } public Logger() { String value = System.getProperty(Settings.REPORT_SERVICE); if (value != null) listener = new NetworkClientListener(value); value = System.getProperty(Settings.QUERY_SERVICE); if (value != null) { QueryService.Client client = new QueryService.Client(value); client.start(); } value = System.getProperty(Settings.LOG_TO_FILE); if (value != null) listener = new FileListener(value); value = System.getProperty(Settings.LOG_TO_STREAM); if (value != null) listener = new StdListener(value.equals("out") ? System.out : System.err); reportRecurentConflicts = Boolean .getBoolean(Settings.REPORT_RECURENT_CONFLICTS); abortOnErrors = Boolean.getBoolean(Settings.ABORT_ON_ERRORS); value = System.getProperty(Settings.THROWING_CLASS); if (value != null) { try { Class<?> cls = Class.forName(value); if (cls.isInstance(RuntimeException.class)) classToThrow = cls; } catch (ClassNotFoundException e) { e.printStackTrace(); } } } public void setListener(IConflictListener listener) { if (listener == null) listener = new DefaultListener(); this.listener = listener; } public boolean reportConflict(int type, String threadID, String conflictThreadID, LockInfo info, LockInfo precedent, LockInfo conflictLock, LockInfo conflictPrecedent) { if (!reportRecurentConflicts) { synchronized (alreadyReportedConflicts) { ConflictReport report = new ConflictReport(threadID, conflictThreadID, info != null ? info.getLock() : null, precedent != null ? precedent.getLock() : null, conflictLock != null ? conflictLock.getLock() : null, conflictPrecedent != null ? conflictPrecedent.getLock() : null); if (alreadyReportedConflicts.contains(report)) return false; alreadyReportedConflicts.add(report); } } boolean shouldThrow = false; StringBuffer buffer = new StringBuffer(); buffer.append(getPrintOutHeader() + getMessageHeader(type, threadID) + "\n"); String indent = " "; printMergedStacks(buffer, info, precedent, indent); buffer.append("\nand thread: " + conflictThreadID + "\n"); printMergedStacks(buffer, conflictPrecedent, conflictLock, indent); int result = listener .report(type, threadID, conflictThreadID, CustomLock.getExternal(info.getLock()), info.stackTrace, CustomLock.getExternal(precedent.getLock()), precedent.stackTrace, conflictLock != null ? CustomLock .getExternal(conflictLock.getLock()) : null, conflictLock != null ? conflictLock.stackTrace : null, conflictPrecedent != null ? CustomLock .getExternal(conflictPrecedent.getLock()) : null, conflictPrecedent != null ? conflictPrecedent.stackTrace : null, buffer.toString()); if ((result & IConflictListener.DEBUG) != 0) { debug(); } if ((result & IConflictListener.EXCEPTION) != 0) shouldThrow = true; if ((result & IConflictListener.LOG_TO_CONSOLE) != 0) System.out.println(buffer.toString()); if ((result & IConflictListener.ABORT) != 0) System.exit(-1); return shouldThrow; } /* The stack traces collected stack inside the Analyzer code, so in order * to show only the relevant stack traces to the user, we must skip the first * 3 stack trace elements, as shown below: java.lang.Thread.getStackTrace(Thread.java:1436) com.freescale.deadlockpreventer.Analyzer.enterLock_(Analyzer.java:267) com.freescale.deadlockpreventer.Analyzer.enterLock(Analyzer.java:242) * */ public static final int FIRST_STACK_TRACE_ELEMENT = 3; private void printMergedStacks(StringBuffer buffer, LockInfo lock, LockInfo precedent, String indent) { StackTraceElement[] elements = lock.stackTrace; StackTraceElement[] precedentElements = precedent.stackTrace; if (elements != null && elements.length > FIRST_STACK_TRACE_ELEMENT && precedentElements != null && precedentElements.length > FIRST_STACK_TRACE_ELEMENT) { buffer.append(indent + elements[FIRST_STACK_TRACE_ELEMENT].toString() + " <-- acquiring: " + Util.getUniqueIdentifier(lock.getLock())); // Find how many elements are common to both stack traces. // If the lock stack trace is: // A // B // C // And the precedent stack trace is: // D // B // C // then the number of common stack trace elements is 2. int commonStackTraceElements = 0; int maximumCommonStackTraceElements = Math.min(elements.length - FIRST_STACK_TRACE_ELEMENT, precedentElements.length - FIRST_STACK_TRACE_ELEMENT); for (; commonStackTraceElements < maximumCommonStackTraceElements; commonStackTraceElements++) { String lastLine = elements[elements.length - commonStackTraceElements - 1].toString(); String precedentLastLine = precedentElements[precedentElements.length - commonStackTraceElements - 1].toString(); if (!lastLine.equals(precedentLastLine)) break; } for (int i = FIRST_STACK_TRACE_ELEMENT + 1; i < (elements.length - maximumCommonStackTraceElements); i++) { buffer.append("\n" + indent + elements[i].toString()); } buffer.append("\n" + indent + precedentElements[FIRST_STACK_TRACE_ELEMENT].toString() + " <-- acquiring: " + Util.getUniqueIdentifier(precedent.getLock())); for (int i = FIRST_STACK_TRACE_ELEMENT + 1; i < precedentElements.length; i++) { buffer.append("\n" + indent + precedentElements[i].toString()); } } } static String getPrintOutHeader() { return "***DEADLOCK PREVENTER*** "; } private String getMessageHeader(int type, String threadId) { String msg = "ERROR"; switch (type) { case Analyzer.TYPE_ERROR: msg = "ERROR: Inconsistent locking order while acquiring locks in thread : " + threadId; break; case Analyzer.TYPE_WARNING: msg = "WARNING: Inconsistent locking order while acquiring locks in thread " + threadId; break; case Analyzer.TYPE_ERROR_SIGNAL: msg = "ERROR: Inconsistent messaging order while signaling objects in thread " + threadId; break; } return msg; } private String getMessageHeader(int type, LockInfo info) { String msg = "ERROR"; switch (type) { case Analyzer.TYPE_ERROR: msg = "ERROR: Inconsistent locking order while acquiring lock: " + Util.getUniqueIdentifier(info.getLock()); break; case Analyzer.TYPE_WARNING: msg = "WARNING: Inconsistent locking order while acquiring lock: " + Util.getUniqueIdentifier(info.getLock()); break; case Analyzer.TYPE_ERROR_SIGNAL: msg = "ERROR: Inconsistent messaging order while signaling objects: " + Util.getUniqueIdentifier(info.getLock()); break; } return msg; } public static void dumpLockInformation(String file) { File outputFile = new File(file); if (!outputFile.getParentFile().exists()) outputFile.getParentFile().mkdirs(); try { if (!outputFile.exists()) outputFile.createNewFile(); FileWriter writer = new FileWriter(outputFile); dumpLockInformation(writer); writer.close(); } catch (IOException e) { e.printStackTrace(); } } public static void dumpLockInformation(Writer writer) { ILock[] locks = new Statistics().locks(); dumpLockInformation(locks, writer); } public static void dumpLockInformation(ILock[] locks, Writer writer) { try { for (ILock lock : locks) { writer.write("lock: " + lock.getID() + ", precedents(" + lock.getPrecedents().length + ") followers(" + lock.getFollowers().length + ")\n"); writeStack(writer, " ", lock.getStackTrace()); if (lock.getPrecedents().length > 0) { writer.write(" precedents:\n"); for (IContext context : lock.getPrecedents()) { writer.write(" " + context.getLock().getID() + ", thread id(" + context.getThreadID() + ")\n"); writeStack(writer, " ", context.getStackTrace()); } } if (lock.getFollowers().length > 0) { writer.write(" followers:\n"); for (IContext context : lock.getFollowers()) { writer.write(" " + context.getLock().getID() + ", thread id(" + context.getThreadID() + ")\n"); writeStack(writer, " ", context.getStackTrace()); } } } } catch (IOException e) { e.printStackTrace(); } } private static void writeStack(Writer writer, String prefix, String[] stackTrace) throws IOException { for (String stack : stackTrace) { writer.write(prefix + stack + "\n"); } } public void throwException(int type, LockInfo info) { String msg = Logger.getPrintOutHeader() + getMessageHeader(type, info); try { try { Constructor<?> constr = classToThrow .getConstructor(String.class); Object obj = constr.newInstance(msg); throw (RuntimeException) obj; } catch (IllegalArgumentException e) { throw (RuntimeException) classToThrow.newInstance(); } catch (NoSuchMethodException e) { throw (RuntimeException) classToThrow.newInstance(); } catch (InvocationTargetException e) { throw (RuntimeException) classToThrow.newInstance(); } } catch (SecurityException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } throw new RuntimeException(msg); } private void debug() { try { Class<?> cls = Class.forName(debugException); Object obj = cls.newInstance(); throw (Throwable) obj; } catch (ClassNotFoundException e) { e.printStackTrace(); throw new IllegalMonitorStateException(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (Throwable e) { e.printStackTrace(); } } public static void printStrackTrace(StackTraceElement[] stackTrace) { StringBuffer buffer = new StringBuffer(); printStrackTrace(buffer, new String(), stackTrace, 0); System.out.print(buffer.toString()); } public static void printStrackTrace(StringBuffer buffer, String header, StackTraceElement[] stackTrace, int startOffset) { for (int i = startOffset; i < stackTrace.length; i++) buffer.append(header + stackTrace[i].toString() + "\n"); } public static void dumpBundleInfo(IBundleInfo info, PrintWriter writer) { try { writer.write("bundle info: " + info.getName()); writeStack(writer, " ", info.getPackages()); } catch (IOException e) { e.printStackTrace(); } } }