/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.distributed.internal.deadlock;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.geode.distributed.internal.deadlock.MessageDependencyMonitor.MessageKey;
import org.apache.geode.internal.util.PluckStacks;
/**
* This class holds a graph of dependencies between objects
*
* It detects cycles in the graph by using the Depth First Search algorithm. Calling findCycle will
* return the first cycle that is discovered in the graph.
*
*
*
*/
public class DependencyGraph implements Serializable {
private static final long serialVersionUID = -6794339771271587648L;
/**
* The vertices of the graph. The key is the vertex, the value is the set of outgoing dependencies
* (ie the dependencies where this vertex is the depender).
*/
private Map<Object, Set<Dependency>> vertices = new LinkedHashMap();
/**
* The edges of the graph. This holds all of the dependencies in the graph.
*/
private Set<Dependency> edges = new LinkedHashSet<Dependency>();
/** add a collection of edges to this graph */
public void addEdges(Collection<Dependency> edges) {
for (Dependency dep : edges) {
addEdge(dep);
}
}
/**
* Add an edge to the dependency graph.
*/
public void addEdge(Dependency dependency) {
if (!edges.contains(dependency)) {
edges.add(dependency);
Set<Dependency> outboundEdges = vertices.get(dependency.getDepender());
if (outboundEdges == null) {
outboundEdges = new HashSet();
vertices.put(dependency.getDepender(), outboundEdges);
}
outboundEdges.add(dependency);
if (vertices.get(dependency.getDependsOn()) == null) {
vertices.put(dependency.getDependsOn(), new HashSet());
}
}
}
/**
* Find a cycle in the graph, if one exists.
*
* This method works by starting at any vertex and doing a depth first search. If it ever
* encounters a vertex that is currently in the middle of the search (as opposed to a vertex whose
* dependencies have been completely analyzed), then it returns the chain that starts from our
* start vertex and includes the cycle.
*/
public LinkedList<Dependency> findCycle() {
Set<Object> unvisited = new HashSet<Object>(vertices.keySet());
Set<Object> finished = new HashSet<Object>(vertices.size());
while (unvisited.size() > 0) {
Object start = unvisited.iterator().next();
CycleHolder cycle = new CycleHolder();
boolean foundCycle = visitCycle(start, unvisited, finished, cycle, 0);
if (foundCycle) {
return cycle.cycle;
}
}
return null;
}
/**
* This will find the longest call chain in the graph. If a cycle is detected it will be returned.
* Otherwise all subgraphs are traversed to find the one that has the most depth. This usually
* indicates the thread that is blocking the most other threads.
* <p>
*
* The findDependenciesWith method can then be used to find all top-level threads that are blocked
* by the culprit.
*/
public DependencyGraph findLongestCallChain() {
int depth = 0;
DependencyGraph deepest = null;
for (Object dep : vertices.keySet()) {
int itsDepth = getDepth(dep);
if (itsDepth > depth) {
deepest = getSubGraph(dep);
depth = itsDepth;
}
}
return deepest;
}
/**
* This returns a collection of top-level threads and their path to the given object. The object
* name is some substring of the toString of the object
*
* @param objectName
*/
public List<DependencyGraph> findDependenciesWith(String objectName) {
// first find a dependency containing the node. If we can't find one
// we can just quit
Object obj = null;
Dependency objDep = null;
for (Dependency dep : edges) {
if (dep.depender.toString().contains(objectName)) {
obj = dep.depender;
objDep = dep;
break;
}
if (dep.dependsOn.toString().contains(objectName)) {
obj = dep.dependsOn;
objDep = dep;
break;
}
}
if (obj == null) {
return Collections.emptyList();
}
// expand the dependency set to include all incoming
// references, references to those references, etc.
// This should give us a collection that includes all
// top-level nodes that have no references to them
Set<Object> dependsOnObj = new HashSet<>();
dependsOnObj.add(obj);
boolean anyAdded = true;
while (anyAdded) {
anyAdded = false;
for (Dependency dep : edges) {
if (dependsOnObj.contains(dep.dependsOn) && !dependsOnObj.contains(dep.depender)) {
anyAdded = true;
dependsOnObj.add(dep.depender);
}
}
}
// find all terminal nodes having no incoming
// dependers.
Set<Object> allDependants = new HashSet<>();
for (Dependency dep : edges) {
if ((dep.dependsOn instanceof LocalThread)) {
if (dep.depender instanceof MessageKey) {
allDependants.add(dep.dependsOn);
}
} else {
allDependants.add(dep.dependsOn);
}
}
List<DependencyGraph> result = new LinkedList<>();
for (Object depender : dependsOnObj) {
if (!allDependants.contains(depender)) {
result.add(getSubGraph(depender));
}
}
return result;
}
/**
* Visit a vertex for the purposes of finding a cycle in the graph.
*
* @param start the node
* @param unvisited the set of vertices that have not yet been visited
* @param finished the set of vertices that have been completely analyzed
* @param cycle an object used to record the any cycles that are detected
* @param depth the depth of the recursion chain up to this point
*/
private boolean visitCycle(Object start, Set<Object> unvisited, Set<Object> finished,
CycleHolder cycle, int depth) {
if (finished.contains(start)) {
return false;
}
if (!unvisited.remove(start)) {
return true;
}
cycle.processDepth(depth);
boolean foundCycle = false;
for (Dependency dep : vertices.get(start)) {
foundCycle |= visitCycle(dep.getDependsOn(), unvisited, finished, cycle, depth + 1);
if (foundCycle) {
cycle.add(dep);
break;
}
}
finished.add(start);
return foundCycle;
}
/** return the depth of the subgraph for the given object */
private int getDepth(Object depender) {
Set<Object> unvisited = new HashSet<Object>(vertices.keySet());
Set<Object> finished = new HashSet<Object>(vertices.size());
Object start = depender;
CycleHolder cycle = new CycleHolder();
boolean foundCycle = visitCycle(start, unvisited, finished, cycle, 0);
if (foundCycle) {
return Integer.MAX_VALUE;
} else {
return cycle.getMaxDepth();
}
}
/**
* Get the subgraph that the starting object has dependencies on.
*
* This does not include any objects that have dependencies on the starting object.
*/
public DependencyGraph getSubGraph(Object start) {
DependencyGraph result = new DependencyGraph();
populateSubGraph(start, result);
return result;
}
private void populateSubGraph(Object start, DependencyGraph result) {
if (result.vertices.keySet().contains(start)) {
return;
}
if (vertices.get(start) == null) {
return;
}
result.addVertex(start, vertices.get(start));
for (Dependency dep : result.vertices.get(start)) {
populateSubGraph(dep.getDependsOn(), result);
}
}
/**
* Add a vertex to the graph.
*/
private void addVertex(Object start, Set<Dependency> set) {
vertices.put(start, set);
edges.addAll(set);
}
public Collection<Dependency> getEdges() {
return edges;
}
public Collection<Object> getVertices() {
return vertices.keySet();
}
private static class CycleHolder {
private LinkedList<Dependency> cycle = new LinkedList<Dependency>();
private boolean cycleDone;
private int maxDepth = 0;
public void processDepth(int depth) {
if (depth > maxDepth) {
maxDepth = depth;
}
}
public int getMaxDepth() {
return maxDepth;
}
public void add(Dependency dep) {
if (cycleDone) {
return;
}
cycle.addFirst(dep);
Object lastVertex = cycle.getLast().getDependsOn();
if (dep.depender.equals(lastVertex)) {
cycleDone = true;
}
}
}
}