/*******************************************************************************
* Licensed 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.kato.katoview.commands;
import java.util.HashMap;
import java.util.Stack;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.SortedMap;
import java.util.Vector;
import javax.tools.diagnostics.image.*;
import javax.tools.diagnostics.runtime.java.*;
import org.apache.kato.katoview.Output;
import org.apache.kato.katoview.commands.helpers.Exceptions;
import org.apache.kato.katoview.commands.helpers.MonitorNode;
import org.apache.kato.katoview.commands.helpers.NodeList;
import org.apache.kato.katoview.commands.helpers.Utils;
public class DeadlockCommand extends Command {
public DeadlockCommand(Output o){
super(o, "deadlock", "displays information about deadlocks if there are any",
"parameters: none\n\n" +
"The \"deadlock\" command shows detailed information about deadlocks " +
"or \"no deadlocks detected\" if there are no deadlocks. A deadlock " +
"situation consists of one or more deadlock loops and zero or more " +
"branches attached to those loops. This command prints out each " +
"branch attached to a loop and then the loop itself. If there is a " +
"split in a deadlock branch, separate branches will be created for " +
"each side of the split in the branch. Deadlock branches start with " +
"a monitor that has no threads waiting on it and the continues until " +
"it reaches a monitor that exists in another deadlock branch or loop. " +
"Deadlock loops start and end with the same monitor.\n\n" +
"Monitors are represented by their owner and the object associated with " +
"the given monitor. For example, \"3435 (0x45ae67)\" represents the " +
"monitor that is owned by the thread with id 3435 and is associated " +
"the object at address 0x45ae67. Objects can be viewed by using a " +
"command like \"x/j 0x45ae67\" and threads can be viewed using a " +
"command like \"info thread 3435\".\n"
);
child_commands = null;
}
public void doCommand(Stack args, Image loadedImage, HashMap properties)
{
Iterator itRuntime = Utils.getRuntimes(loadedImage);
int jrnum = 1;
while (itRuntime.hasNext())
{
SortedMap monitorNodes = new TreeMap();
JavaRuntime jr = (JavaRuntime)itRuntime.next();
Iterator itMonitor = jr.getMonitors().iterator();
int nodeListNum = 0;
out.print("\n deadlocks for runtime #" + jrnum + "\n");
// Step 1. iterate over all monitors, creating a MonitorNode for each monitor that
// contains the monitor (JavaMonitor) and some parameters and adding that MonitorNode
// to a Hashtable, indexed by owner (JavaThread object address)
while (itMonitor.hasNext()) {
JavaMonitor monitor = (JavaMonitor)itMonitor.next();
MonitorNode node = new MonitorNode(monitor);
JavaThread owner = null;
Long id = null;
try {
owner = monitor.getOwner();
} catch (CorruptDataException e) {
out.error("exception encountered while getting monitor owner: " +
Exceptions.getCorruptDataExceptionString());
return;
}
if (null == owner) {
// A monitor with no owner cannot be involved in a deadlock, according to the
// algorithm used here anyway, because in order for a monitor to be in a deadlock,
// its owner must be in a deadlock or an owner somewhere down the chain of
// ownership must own the given monitor. Since there is no owner, we can't get
// the monitor's owner or the next monitor in a potential deadlock chain.
continue;
} else {
JavaObject threadObject;
try {
threadObject = owner.getObject();
} catch (CorruptDataException e) {
out.error("exception encountered while getting owner's JavaObject: " +
Exceptions.getCorruptDataExceptionString());
return;
}
id = new Long(threadObject.getID().getAddress());
}
// Note: we used to give up here with an error if there was already
// a monitor node in the table with the same key (thread). This is very common (a
// thread owning multiple monitors).
monitorNodes.put(id, node);
}
Iterator values = monitorNodes.values().iterator();
// Step 2. iterate over Hashtable and for every MonitorNode, iterate over monitor m1's
// enter waiters (JavaMonitor.getEnterWaiters()), which are JavaThreads, and for each
// enter waiter, set that waiter's MonitorNode's waitingOn to m1.
while (values.hasNext()) {
MonitorNode currNode = (MonitorNode)values.next();
JavaMonitor currMonitor = currNode.getMonitor();
Iterator itWaiters = currMonitor.getEnterWaiters().iterator();
while (itWaiters.hasNext()) {
JavaThread waiter = (JavaThread)itWaiters.next();
JavaObject threadObject;
Long id = null;
try {
threadObject = waiter.getObject();
} catch (CorruptDataException e) {
out.error("exception encountered while getting waiter's ImageThread: " +
Exceptions.getCorruptDataExceptionString());
return;
}
id = new Long(threadObject.getID().getAddress());
MonitorNode waiterNode = (MonitorNode)monitorNodes.get(id);
if (null != waiterNode) {
waiterNode.waitingOn = currNode;
}
}
}
values = monitorNodes.values().iterator();
int visit = 1;
Vector lists = new Vector();
// Step 3. iterate over Hashtable and for every MonitorNode m1:
// Step 3a. set a unique visit number, visit > 0 (visit++ would work)
// Step 3b. iterate over waitingOns, setting visit number, until a null
// waitingOn is reached (no deadlock), a deadlocked node is reached
// (m1 is part of a branch that joins a deadlock loop), or a node with
// the same visit is reached (m1 is part of a deadlock loop)
// Step 3c. if deadlock found, start at m1 and iterate over all
// waitingOns until a deadlocked node is found, setting deadlock
// (branch or loop, depending on result in 3b) along the way
// note: Step 4* are not laid out precisely as specified; the instructions
// listed for Step 4* are integrated into Step 3
// Step 4. for each MonitorNode m1 where inList == false and m1 is part
// of a deadlock loop *, create a new list and push it on the list
// stack right away:
// Step 4a. iterate over all enter waiters, setting inList to true at
// every MonitorNode
// Step 4b. if there are no enter waiters, terminate the current list
// Step 4c. if there is one enter waiter: if the enter waiter is the
// current list, stop; else continue creating the current list,
// iterating to that thread's MonitorNode
// Step 4d. if there is more than one enter waiter, continue creating
// current list and start a new list, pushing it on the list stack
// right away
while (values.hasNext())
{
MonitorNode startNode = (MonitorNode)values.next();
MonitorNode currNode = startNode;
MonitorNode endNode;
if (0 != startNode.visit)
{
continue;
}
while (true)
{
currNode.visit = visit;
if (null == currNode.waitingOn)
{
currNode.deadlock = MonitorNode.NO_DEADLOCK;
break;
}
if (isDeadlocked(currNode.waitingOn))
{
// we've encountered a deadlocked node in the chain;
// set branch deadlock for all nodes between startNode
// and currNode
endNode = currNode.waitingOn;
currNode = startNode;
NodeList branchList = null;
while (currNode != endNode)
{
if (null == branchList)
{
branchList = new NodeList(currNode, nodeListNum++);
}
currNode.deadlock = MonitorNode.BRANCH_DEADLOCK;
currNode = currNode.waitingOn;
branchList.add(currNode);
if (currNode != endNode)
currNode.inList = branchList;
}
if (endNode.inList.isLoop())
{
lists.insertElementAt(branchList, lists.indexOf(endNode.inList));
}
else
{
NodeList oldList = endNode.inList;
// FIXME: the below line will cause problems with at least
// one case that was not considered when attachOrSplit was
// coded: if a NodeList n1 has already been split and another
// NodeList n2 tries to attach to the end of n1, then n1 will
// allow n2 to attach to n1, while what n1 should really do is
// just return n2 and not allow n2 to attach to itself
NodeList split = endNode.inList.attachOrSplit(branchList, nodeListNum++);
if (null != split)
{
lists.insertElementAt(split, lists.indexOf(oldList));
lists.insertElementAt(branchList, lists.indexOf(oldList));
}
}
break;
}
if (currNode.waitingOn.visit == visit)
{
// we've encountered a node in the same visit as the current
// visit, ie. we've found a loop; first flag the whole loop
// with a loop deadlock flag, then flag the rest of the nodes
// in the chain with a branch deadlock
endNode = currNode.waitingOn;
currNode = endNode;
NodeList loopList = new NodeList(currNode, nodeListNum++);
lists.insertElementAt(loopList, 0);
do
{
currNode.deadlock = MonitorNode.LOOP_DEADLOCK;
currNode = currNode.waitingOn;
loopList.add(currNode);
currNode.inList = loopList;
} while (currNode != endNode);
currNode = startNode;
NodeList branchList = null;
while (currNode != endNode)
{
if (null == branchList)
{
branchList = new NodeList(currNode, nodeListNum++);
lists.insertElementAt(branchList, 0);
}
currNode.deadlock = MonitorNode.BRANCH_DEADLOCK;
currNode = currNode.waitingOn;
branchList.add(currNode);
if (currNode != endNode)
currNode.inList = branchList;
}
break;
}
currNode = currNode.waitingOn;
}
visit++;
}
if (lists.isEmpty())
{
out.print("\n");
out.print("\t no deadlocks detected");
out.print("\n");
return;
}
boolean lastListWasLoop = true;
Iterator itList = lists.iterator();
// Step 5. print the lists
while (itList.hasNext())
{
NodeList list = (NodeList)itList.next();
if (list.isLoop()) {
out.print("\n deadlock loop:\n");
lastListWasLoop = true;
} else if (lastListWasLoop) { // && !list.isLoop()
out.print("\n\n deadlock branch(es):\n");
lastListWasLoop = false;
}
out.print("\t " + list.toString());
out.print("\n");
}
out.print("\n");
jrnum++;
}
}
private boolean isDeadlocked(MonitorNode node)
{
return MonitorNode.LOOP_DEADLOCK == node.deadlock ||
MonitorNode.BRANCH_DEADLOCK == node.deadlock;
}
}