/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.github.geophile.erdo.transaction;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
class DeadlockDetector
{
public Set<Transaction> victims()
{
int detectionId = detectionCounter++;
LOG.log(Level.INFO, "Starting deadlock detection {0}", detectionId);
Set<Transaction> victims = new HashSet<Transaction>();
// WaitsFor.waiter -> WaitsFor
Map<Transaction, WaitsFor> dependencies = lockManager.dependencies();
List<Path> paths = new ArrayList<Path>();
for (WaitsFor waitsFor : dependencies.values()) {
paths.add(new Path(waitsFor));
}
int extensions = 0;
List<Path> cycles = new ArrayList<Path>();
logPaths(detectionId, 0, paths, cycles);
do {
for (Iterator<Path> pathIterator = paths.iterator(); pathIterator.hasNext();) {
Path path = pathIterator.next();
Transaction pathLast = path.last();
WaitsFor extension = dependencies.get(pathLast);
if (extension == null) {
pathIterator.remove();
} else if (!path.extend(extension.copy())) {
// path contains cycle
cycles.add(path);
pathIterator.remove();
}
}
logPaths(detectionId, ++extensions, paths, cycles);
} while (!paths.isEmpty());
for (Path path : cycles) {
victims.add(path.victim());
}
return victims;
}
public DeadlockDetector(LockManager lockManager)
{
this.lockManager = lockManager;
}
// For use by this class
private void logPaths(int detectionId, int extensions, List<Path> paths, List<Path> cycles)
{
if (paths.isEmpty()) {
LOG.log(Level.INFO, "{0}.{1} NO PATHS", new Object[]{detectionId, extensions});
} else {
LOG.log(Level.INFO, "{0}.{1} paths", new Object[]{detectionId, extensions});
for (Path path : paths) {
LOG.log(Level.INFO, "{0}.{1} {2}", new Object[]{detectionId, extensions, path});
}
}
if (cycles.isEmpty()) {
LOG.log(Level.INFO, "{0}.{1} NO CYCLES", new Object[]{detectionId, extensions});
} else {
LOG.log(Level.INFO, "{0}.{1} cycles", new Object[]{detectionId, extensions});
for (Path path : cycles) {
LOG.log(Level.INFO, "{0}.{1} {2}", new Object[]{detectionId, extensions, path});
}
}
}
// Class state
private static final Logger LOG = Logger.getLogger(DeadlockDetector.class.getName());
// Object state
private final LockManager lockManager;
private int detectionCounter = 0;
// Inner classes
private static class Path
{
public String toString()
{
StringBuilder buffer = new StringBuilder();
buffer.append(head.waiter());
WaitsFor w = head;
do {
buffer.append(" -> ");
buffer.append(w.owner());
w = w.ownerWaitsFor();
} while (w != null);
return buffer.toString();
}
public Path(WaitsFor waitsFor)
{
this.head = waitsFor;
this.tail = waitsFor;
transactions.put(head.waiter(), head.waiter());
transactions.put(head.owner(), head.owner());
}
// Returns true iff cycle is not formed
public boolean extend(WaitsFor extension)
{
assert tail.owner() == extension.waiter();
tail.ownerWaitsFor(extension);
tail = extension;
Transaction replaced = transactions.put(extension.owner(), extension.owner());
boolean cycle = replaced != null;
if (cycle) {
firstWaiterInCycle = extension.owner();
}
return !cycle;
}
public Transaction first()
{
return head.waiter();
}
public Transaction last()
{
return tail.owner();
}
public Transaction victim()
{
Transaction victim = null;
// Get to start of cycle
WaitsFor w = head;
while (w.waiter() != firstWaiterInCycle) {
w = w.ownerWaitsFor();
}
// Pick the transaction in the cycle with the max start time. This should be the
// newest one in the cycle, wasting the least amount of work. Arguments could be made
// for other criteria.
long maxStartTime = Long.MIN_VALUE;
do {
Transaction waiter = w.waiter();
if (waiter.startTime() > maxStartTime) {
maxStartTime = waiter.startTime();
victim = waiter;
}
w = w.ownerWaitsFor();
} while (w != null);
assert victim != null;
return victim;
}
private final WaitsFor head;
private WaitsFor tail;
private IdentityHashMap<Transaction, Transaction> transactions =
new IdentityHashMap<Transaction, Transaction>();
private Transaction firstWaiterInCycle;
}
}