/* * The MIT License * * Copyright (c) 2014 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.github.olivergondza.dumpling.query; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.annotation.Nonnull; import com.github.olivergondza.dumpling.model.ProcessRuntime; import com.github.olivergondza.dumpling.model.ProcessThread; import com.github.olivergondza.dumpling.model.ThreadLock; import com.github.olivergondza.dumpling.model.ThreadSet; /** * Detect deadlocks in thread set. * * @author ogondza */ public final class Deadlocks implements SingleThreadSetQuery<Deadlocks.Result<?, ?, ?>> { private boolean showStackTraces = false; public Deadlocks showStackTraces() { this.showStackTraces = true; return this; } /** * @param threads Include only cycles that contain at least one of input threads. */ @Override public @Nonnull < SetType extends ThreadSet<SetType, RuntimeType, ThreadType>, RuntimeType extends ProcessRuntime<RuntimeType, SetType, ThreadType>, ThreadType extends ProcessThread<ThreadType, SetType, RuntimeType> > Result<SetType, RuntimeType, ThreadType> query(@Nonnull SetType threads) { return new Result<SetType, RuntimeType, ThreadType>(threads, showStackTraces); } /** * Deadlock detection result. * * A set of all deadlocks found. Involved threads are all threads that are part of any deadlock. * * @author ogondza */ public final static class Result< SetType extends ThreadSet<SetType, RuntimeType, ThreadType>, RuntimeType extends ProcessRuntime<RuntimeType, SetType, ThreadType>, ThreadType extends ProcessThread<ThreadType, SetType, RuntimeType> > extends SingleThreadSetQuery.Result<SetType, RuntimeType, ThreadType> { private final @Nonnull Set<SetType> deadlocks; private final @Nonnull SetType involved; /*package*/ Result(@Nonnull SetType input, boolean showStackTraces) { super(showStackTraces); final HashSet<SetType> deadlocks = new HashSet<SetType>(1); final LinkedHashSet<ThreadType> involved = new LinkedHashSet<ThreadType>(2); // No need to visit threads more than once final Set<ThreadType> analyzed = new HashSet<ThreadType>(input.size()); for (ThreadType thread: input) { ArrayList<ThreadType> cycleCandidate = new ArrayList<ThreadType>(2); for (ThreadType blocking = thread.getBlockingThread(); blocking != null; blocking = blocking.getBlockingThread()) { if (analyzed.contains(thread)) break; int beginning = cycleCandidate.indexOf(blocking); if (beginning != -1) { @SuppressWarnings("null") @Nonnull List<ThreadType> cycle = cycleCandidate.subList(beginning, cycleCandidate.size()); deadlocks.add(input.derive(cycle)); involved.addAll(cycle); analyzed.addAll(cycleCandidate); break; } cycleCandidate.add(blocking); } analyzed.add(thread); } this.deadlocks = Collections.unmodifiableSet(deadlocks); this.involved = input.derive(involved); } /** * Get found deadlocks. * * @return {@link Set} of {@link ThreadSet}s representing found deadlocks. */ public @Nonnull Set<SetType> getDeadlocks() { return deadlocks; } @Override protected void printResult(PrintStream out) { int i = 1; for(SetType deadlock: deadlocks) { HashSet<ThreadLock> involvedLocks = new HashSet<ThreadLock>(deadlock.size()); boolean allMonitors = true; for(ThreadType thread: deadlock) { involvedLocks.add(thread.getWaitingToLock()); involvedLocks.add(thread.getWaitingOnLock()); if (thread.getWaitingToLock() == null) { allMonitors = false; } } out.printf("%n%sDeadlock #%d:%n", allMonitors ? "Monitor ": "", i++); for(ThreadType thread: deadlock) { out.println(thread.getHeader()); if (thread.getWaitingToLock() != null) { out.printf("\tWaiting to %s%n", thread.getWaitingToLock()); } else if (thread.getWaitingOnLock() != null) { out.printf("\tWaiting on %s%n", thread.getWaitingOnLock()); } else { assert false; } for (ThreadLock lock: thread.getAcquiredLocks()) { char mark = involvedLocks.contains(lock) ? '*' : ' '; out.printf("\tAcquired %c %s%n", mark, lock); } } } } @Override protected SetType involvedThreads() { return involved; } @Override protected void printSummary(PrintStream out) { out.printf("Deadlocks: %d%n", deadlocks.size()); } @Override public int exitCode() { return deadlocks.size(); } } }