/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.util;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import org.h2.engine.SysProperties;
import org.h2.mvstore.db.MVTable;

Detects deadlocks between threads. Prints out data in the same format as the CTRL-BREAK handler, but includes information about table locks.
/** * Detects deadlocks between threads. Prints out data in the same format as the * CTRL-BREAK handler, but includes information about table locks. */
public class ThreadDeadlockDetector { private static final String INDENT = " "; private static ThreadDeadlockDetector detector; private final ThreadMXBean threadBean; private ThreadDeadlockDetector() { this.threadBean = ManagementFactory.getThreadMXBean(); // a daemon thread // delay: 10 ms // period: 10000 ms (100 seconds) Timer threadCheck = new Timer("ThreadDeadlockDetector", true); threadCheck.schedule(new TimerTask() { @Override public void run() { checkForDeadlocks(); } }, 10, 10_000); }
Initialize the detector.
/** * Initialize the detector. */
public static synchronized void init() { if (detector == null) { detector = new ThreadDeadlockDetector(); } }
Checks if any threads are deadlocked. If any, print the thread dump information.
/** * Checks if any threads are deadlocked. If any, print the thread dump * information. */
void checkForDeadlocks() { long[] deadlockedThreadIds = threadBean.findDeadlockedThreads(); if (deadlockedThreadIds == null) { return; } dumpThreadsAndLocks("ThreadDeadlockDetector - deadlock found :", threadBean, deadlockedThreadIds, System.out); }
Dump all deadlocks (if any).
Params:
  • msg – the message
/** * Dump all deadlocks (if any). * * @param msg the message */
public static void dumpAllThreadsAndLocks(String msg) { dumpAllThreadsAndLocks(msg, System.out); }
Dump all deadlocks (if any).
Params:
  • msg – the message
  • out – the output
/** * Dump all deadlocks (if any). * * @param msg the message * @param out the output */
public static void dumpAllThreadsAndLocks(String msg, PrintStream out) { final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); final long[] allThreadIds = threadBean.getAllThreadIds(); dumpThreadsAndLocks(msg, threadBean, allThreadIds, out); } private static void dumpThreadsAndLocks(String msg, ThreadMXBean threadBean, long[] threadIds, PrintStream out) { final StringWriter stringWriter = new StringWriter(); final PrintWriter print = new PrintWriter(stringWriter); print.println(msg); final HashMap<Long, String> tableWaitingForLockMap; final HashMap<Long, ArrayList<String>> tableExclusiveLocksMap; final HashMap<Long, ArrayList<String>> tableSharedLocksMap; if (SysProperties.THREAD_DEADLOCK_DETECTOR) { tableWaitingForLockMap = MVTable.WAITING_FOR_LOCK .getSnapshotOfAllThreads(); tableExclusiveLocksMap = MVTable.EXCLUSIVE_LOCKS .getSnapshotOfAllThreads(); tableSharedLocksMap = MVTable.SHARED_LOCKS .getSnapshotOfAllThreads(); } else { tableWaitingForLockMap = new HashMap<>(); tableExclusiveLocksMap = new HashMap<>(); tableSharedLocksMap = new HashMap<>(); } final ThreadInfo[] infos = threadBean.getThreadInfo(threadIds, true, true); for (ThreadInfo ti : infos) { printThreadInfo(print, ti); printLockInfo(print, ti.getLockedSynchronizers(), tableWaitingForLockMap.get(ti.getThreadId()), tableExclusiveLocksMap.get(ti.getThreadId()), tableSharedLocksMap.get(ti.getThreadId())); } print.flush(); // Dump it to system.out in one block, so it doesn't get mixed up with // other stuff when we're using a logging subsystem. out.println(stringWriter.getBuffer()); out.flush(); } private static void printThreadInfo(PrintWriter print, ThreadInfo ti) { // print thread information printThread(print, ti); // print stack trace with locks StackTraceElement[] stackTrace = ti.getStackTrace(); MonitorInfo[] monitors = ti.getLockedMonitors(); for (int i = 0; i < stackTrace.length; i++) { StackTraceElement e = stackTrace[i]; print.println(INDENT + "at " + e.toString()); for (MonitorInfo mi : monitors) { if (mi.getLockedStackDepth() == i) { print.println(INDENT + " - locked " + mi); } } } print.println(); } private static void printThread(PrintWriter print, ThreadInfo ti) { print.print("\"" + ti.getThreadName() + "\"" + " Id=" + ti.getThreadId() + " in " + ti.getThreadState()); if (ti.getLockName() != null) { print.append(" on lock=").append(ti.getLockName()); } if (ti.isSuspended()) { print.append(" (suspended)"); } if (ti.isInNative()) { print.append(" (running in native)"); } print.println(); if (ti.getLockOwnerName() != null) { print.println(INDENT + " owned by " + ti.getLockOwnerName() + " Id=" + ti.getLockOwnerId()); } } private static void printLockInfo(PrintWriter print, LockInfo[] locks, String tableWaitingForLock, ArrayList<String> tableExclusiveLocks, ArrayList<String> tableSharedLocksMap) { print.println(INDENT + "Locked synchronizers: count = " + locks.length); for (LockInfo li : locks) { print.println(INDENT + " - " + li); } if (tableWaitingForLock != null) { print.println(INDENT + "Waiting for table: " + tableWaitingForLock); } if (tableExclusiveLocks != null) { print.println(INDENT + "Exclusive table locks: count = " + tableExclusiveLocks.size()); for (String name : tableExclusiveLocks) { print.println(INDENT + " - " + name); } } if (tableSharedLocksMap != null) { print.println(INDENT + "Shared table locks: count = " + tableSharedLocksMap.size()); for (String name : tableSharedLocksMap) { print.println(INDENT + " - " + name); } } print.println(); } }