diff dwtx/core/internal/jobs/DeadlockDetector.d @ 122:9d0585bcb7aa

Add core.jobs package
author Frank Benoit <benoit@tionex.de>
date Tue, 12 Aug 2008 02:34:21 +0200
parents
children 862b05e0334a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/core/internal/jobs/DeadlockDetector.d	Tue Aug 12 02:34:21 2008 +0200
@@ -0,0 +1,721 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.core.internal.jobs.DeadlockDetector;
+
+import tango.core.Thread;
+import tango.io.Stdout;
+import tango.text.convert.Format;
+
+import dwt.dwthelper.utils;
+import dwtx.dwtxhelper.Collection;
+
+import dwtx.core.internal.runtime.RuntimeLog;
+import dwtx.core.runtime.Assert;
+import dwtx.core.runtime.IStatus;
+import dwtx.core.runtime.MultiStatus;
+import dwtx.core.runtime.Status;
+import dwtx.core.runtime.jobs.ILock;
+import dwtx.core.runtime.jobs.ISchedulingRule;
+import dwtx.core.internal.jobs.Deadlock;
+import dwtx.core.internal.jobs.JobManager;
+
+/**
+ * Stores all the relationships between locks (rules are also considered locks),
+ * and the threads that own them. All the relationships are stored in a 2D integer array.
+ * The rows in the array are threads, while the columns are locks.
+ * Two corresponding arrayLists store the actual threads and locks.
+ * The index of a thread in the first arrayList is the index of the row in the graph.
+ * The index of a lock in the second arrayList is the index of the column in the graph.
+ * An entry greater than 0 in the graph is the number of times a thread in the entry's row
+ * acquired the lock in the entry's column.
+ * An entry of -1 means that the thread is waiting to acquire the lock.
+ * An entry of 0 means that the thread and the lock have no relationship.
+ *
+ * The difference between rules and locks is that locks can be suspended, while
+ * rules are implicit locks and as such cannot be suspended.
+ * To resolve deadlock, the graph will first try to find a thread that only owns
+ * locks. Failing that, it will find a thread in the deadlock that owns at least
+ * one lock and suspend it.
+ *
+ * Deadlock can only occur among locks, or among locks in combination with rules.
+ * Deadlock among rules only is impossible. Therefore, in any deadlock one can always
+ * find a thread that owns at least one lock that can be suspended.
+ *
+ * The implementation of the graph assumes that a thread can only own 1 rule at
+ * any one time. It can acquire that rule several times, but a thread cannot
+ * acquire 2 non-conflicting rules at the same time.
+ *
+ * The implementation of the graph will sometimes also find and resolve bogus deadlocks.
+ *      graph:              assuming this rule hierarchy:
+ *         R2 R3 L1                     R1
+ *      J1  1  0  0                    /  \
+ *      J2  0  1 -1                   R2  R3
+ *      J3 -1  0  1
+ *
+ * If in the above situation job4 decides to acquire rule1, then the graph will transform
+ * to the following:
+ *         R2 R3 R1 L1
+ *      J1  1  0  1  0
+ *      J2  1  1  1 -1
+ *      J3 -1  0  0  1
+ *      J4  0  0 -1  0
+ *
+ * and the graph will assume that job2 and job3 are deadlocked and suspend lock1 of job3.
+ * The reason the deadlock is bogus is that the deadlock is unlikely to actually happen (the threads
+ * are currently not deadlocked, but might deadlock later on when it is too late to detect it)
+ * Therefore, in order to make sure that no deadlock is possible,
+ * the deadlock will still be resolved at this point.
+ */
+class DeadlockDetector {
+    private static int NO_STATE = 0;
+    //state variables in the graph
+    private static int WAITING_FOR_LOCK = -1;
+    //empty matrix
+    private static const int[][] EMPTY_MATRIX = null;
+    //matrix of relationships between threads and locks
+    private int[][] graph = EMPTY_MATRIX;
+    //index is column in adjacency matrix for the lock
+    private final ArrayList locks;
+    //index is row in adjacency matrix for the thread
+    private final ArrayList lockThreads;
+    //whether the graph needs to be resized
+    private bool resize = false;
+
+    public this(){
+        locks = new ArrayList();
+        lockThreads = new ArrayList();
+    }
+
+    /**
+     * Recursively check if any of the threads that prevent the current thread from running
+     * are actually deadlocked with the current thread.
+     * Add the threads that form deadlock to the deadlockedThreads list.
+     */
+    private bool addCycleThreads(ArrayList deadlockedThreads, Thread next) {
+        //get the thread that block the given thread from running
+        Thread[] blocking = blockingThreads(next);
+        //if the thread is not blocked by other threads, then it is not part of a deadlock
+        if (blocking.length is 0)
+            return false;
+        bool inCycle = false;
+        for (int i = 0; i < blocking.length; i++) {
+            //if we have already visited the given thread, then we found a cycle
+            if (deadlockedThreads.contains(blocking[i])) {
+                inCycle = true;
+            } else {
+                //otherwise, add the thread to our list and recurse deeper
+                deadlockedThreads.add(blocking[i]);
+                //if the thread is not part of a cycle, remove it from the list
+                if (addCycleThreads(deadlockedThreads, blocking[i]))
+                    inCycle = true;
+                else
+                    deadlockedThreads.remove(blocking[i]);
+            }
+        }
+        return inCycle;
+    }
+
+    /**
+     * Get the thread(s) that own the lock this thread is waiting for.
+     */
+    private Thread[] blockingThreads(Thread current) {
+        //find the lock this thread is waiting for
+        ISchedulingRule lock = cast(ISchedulingRule) getWaitingLock(current);
+        return getThreadsOwningLock(lock);
+    }
+
+    /**
+     * Check that the addition of a waiting thread did not produce deadlock.
+     * If deadlock is detected return true, else return false.
+     */
+    private bool checkWaitCycles(int[] waitingThreads, int lockIndex) {
+        /**
+         * find the lock that this thread is waiting for
+         * recursively check if this is a cycle (i.e. a thread waiting on itself)
+         */
+        for (int i = 0; i < graph.length; i++) {
+            if (graph[i][lockIndex] > NO_STATE) {
+                if (waitingThreads[i] > NO_STATE) {
+                    return true;
+                }
+                //keep track that we already visited this thread
+                waitingThreads[i]++;
+                for (int j = 0; j < graph[i].length; j++) {
+                    if (graph[i][j] is WAITING_FOR_LOCK) {
+                        if (checkWaitCycles(waitingThreads, j))
+                            return true;
+                    }
+                }
+                //this thread is not involved in a cycle yet, so remove the visited flag
+                waitingThreads[i]--;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true IFF the matrix contains a row for the given thread.
+     * (meaning the given thread either owns locks or is waiting for locks)
+     */
+    bool contains(Thread t) {
+        return lockThreads.contains(t);
+    }
+
+    /**
+     * A new rule was just added to the graph.
+     * Find a rule it conflicts with and update the new rule with the number of times
+     * it was acquired implicitly when threads acquired conflicting rule.
+     */
+    private void fillPresentEntries(ISchedulingRule newLock, int lockIndex) {
+        //fill in the entries for the new rule from rules it conflicts with
+        for (int j = 0; j < locks.size(); j++) {
+            if ((j !is lockIndex) && (newLock.isConflicting(cast(ISchedulingRule) locks.get(j)))) {
+                for (int i = 0; i < graph.length; i++) {
+                    if ((graph[i][j] > NO_STATE) && (graph[i][lockIndex] is NO_STATE))
+                        graph[i][lockIndex] = graph[i][j];
+                }
+            }
+        }
+        //now back fill the entries for rules the current rule conflicts with
+        for (int j = 0; j < locks.size(); j++) {
+            if ((j !is lockIndex) && (newLock.isConflicting(cast(ISchedulingRule) locks.get(j)))) {
+                for (int i = 0; i < graph.length; i++) {
+                    if ((graph[i][lockIndex] > NO_STATE) && (graph[i][j] is NO_STATE))
+                        graph[i][j] = graph[i][lockIndex];
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns all the locks owned by the given thread
+     */
+    private Object[] getOwnedLocks(Thread current) {
+        ArrayList ownedLocks = new ArrayList(1);
+        int index = indexOf(current, false);
+
+        for (int j = 0; j < graph[index].length; j++) {
+            if (graph[index][j] > NO_STATE)
+                ownedLocks.add(locks.get(j));
+        }
+        if (ownedLocks.size() is 0)
+            Assert.isLegal(false, "A thread with no locks is part of a deadlock."); //$NON-NLS-1$
+        return ownedLocks.toArray();
+    }
+
+    /**
+     * Returns an array of threads that form the deadlock (usually 2).
+     */
+    private Thread[] getThreadsInDeadlock(Thread cause) {
+        ArrayList deadlockedThreads = new ArrayList(2);
+        /**
+         * if the thread that caused deadlock doesn't own any locks, then it is not part
+         * of the deadlock (it just caused it because of a rule it tried to acquire)
+         */
+        if (ownsLocks(cause))
+            deadlockedThreads.add(cause);
+        addCycleThreads(deadlockedThreads, cause);
+        return arraycast!(Thread)( deadlockedThreads.toArray());
+    }
+
+    /**
+     * Returns the thread(s) that own the given lock.
+     */
+    private Thread[] getThreadsOwningLock(ISchedulingRule rule) {
+        if (rule is null)
+            return new Thread[0];
+        int lockIndex = indexOf(rule, false);
+        ArrayList blocking = new ArrayList(1);
+        for (int i = 0; i < graph.length; i++) {
+            if (graph[i][lockIndex] > NO_STATE)
+                blocking.add(lockThreads.get(i));
+        }
+        if ((blocking.size() is 0) && (JobManager.DEBUG_LOCKS))
+            Stdout.formatln(Format("Lock {} is involved in deadlock but is not owned by any thread.", rule )); //$NON-NLS-1$ //$NON-NLS-2$
+        if ((blocking.size() > 1) && (cast(ILock)rule ) && (JobManager.DEBUG_LOCKS))
+            Stdout.formatln(Format("Lock {} is owned by more than 1 thread, but it is not a rule.", rule )); //$NON-NLS-1$ //$NON-NLS-2$
+        return arraycast!(Thread)( blocking.toArray());
+    }
+
+    /**
+     * Returns the lock the given thread is waiting for.
+     */
+    private Object getWaitingLock(Thread current) {
+        int index = indexOf(current, false);
+        //find the lock that this thread is waiting for
+        for (int j = 0; j < graph[index].length; j++) {
+            if (graph[index][j] is WAITING_FOR_LOCK)
+                return locks.get(j);
+        }
+        //it can happen that a thread is not waiting for any lock (it is not really part of the deadlock)
+        return null;
+    }
+
+    /**
+     * Returns the index of the given lock in the lock array. If the lock is
+     * not present in the array, it is added to the end.
+     */
+    private int indexOf(ISchedulingRule lock, bool add) {
+        int index = locks.indexOf(cast(Object)lock);
+        if ((index < 0) && add) {
+            locks.add(cast(Object)lock);
+            resize = true;
+            index = locks.size() - 1;
+        }
+        return index;
+    }
+
+    /**
+     * Returns the index of the given thread in the thread array. If the thread
+     * is not present in the array, it is added to the end.
+     */
+    private int indexOf(Thread owner, bool add) {
+        int index = lockThreads.indexOf(owner);
+        if ((index < 0) && add) {
+            lockThreads.add(owner);
+            resize = true;
+            index = lockThreads.size() - 1;
+        }
+        return index;
+    }
+
+    /**
+     * Returns true IFF the adjacency matrix is empty.
+     */
+    bool isEmpty() {
+        return (locks.size() is 0) && (lockThreads.size() is 0) && (graph.length is 0);
+    }
+
+    /**
+     * The given lock was acquired by the given thread.
+     */
+    void lockAcquired(Thread owner, ISchedulingRule lock) {
+        int lockIndex = indexOf(lock, true);
+        int threadIndex = indexOf(owner, true);
+        if (resize)
+            resizeGraph();
+        if (graph[threadIndex][lockIndex] is WAITING_FOR_LOCK)
+            graph[threadIndex][lockIndex] = NO_STATE;
+        /**
+         * acquire all locks that conflict with the given lock
+         * or conflict with a lock the given lock will acquire implicitly
+         * (locks are acquired implicitly when a conflicting lock is acquired)
+         */
+        ArrayList conflicting = new ArrayList(1);
+        //only need two passes through all the locks to pick up all conflicting rules
+        int NUM_PASSES = 2;
+        conflicting.add(cast(Object)lock);
+        graph[threadIndex][lockIndex]++;
+        for (int i = 0; i < NUM_PASSES; i++) {
+            for (int k = 0; k < conflicting.size(); k++) {
+                ISchedulingRule current = cast(ISchedulingRule) conflicting.get(k);
+                for (int j = 0; j < locks.size(); j++) {
+                    ISchedulingRule possible = cast(ISchedulingRule) locks.get(j);
+                    if (current.isConflicting(possible) && !conflicting.contains(cast(Object)possible)) {
+                        conflicting.add(cast(Object)possible);
+                        graph[threadIndex][j]++;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * The given lock was released by the given thread. Update the graph.
+     */
+    void lockReleased(Thread owner, ISchedulingRule lock) {
+        int lockIndex = indexOf(lock, false);
+        int threadIndex = indexOf(owner, false);
+        //make sure the lock and thread exist in the graph
+        if (threadIndex < 0) {
+            if (JobManager.DEBUG_LOCKS)
+                Stdout.formatln("[lockReleased] Lock {} was already released by thread {}", lock, owner.name()); //$NON-NLS-1$ //$NON-NLS-2$
+            return;
+        }
+        if (lockIndex < 0) {
+            if (JobManager.DEBUG_LOCKS)
+                Stdout.formatln("[lockReleased] Thread {} already released lock {}", owner.name(), lock); //$NON-NLS-1$ //$NON-NLS-2$
+            return;
+        }
+        //if this lock was suspended, set it to NO_STATE
+        if ((cast(ILock)lock ) && (graph[threadIndex][lockIndex] is WAITING_FOR_LOCK)) {
+            graph[threadIndex][lockIndex] = NO_STATE;
+            return;
+        }
+        //release all locks that conflict with the given lock
+        //or release all rules that are owned by the given thread, if we are releasing a rule
+        for (int j = 0; j < graph[threadIndex].length; j++) {
+            if ((lock.isConflicting(cast(ISchedulingRule) locks.get(j))) || (!(cast(ILock)lock ) && !(cast(ILock)locks.get(j)) && (graph[threadIndex][j] > NO_STATE))) {
+                if (graph[threadIndex][j] is NO_STATE) {
+                    if (JobManager.DEBUG_LOCKS)
+                        Stdout.formatln("[lockReleased] More releases than acquires for thread {} and lock {}", owner.name(), lock); //$NON-NLS-1$ //$NON-NLS-2$
+                } else {
+                    graph[threadIndex][j]--;
+                }
+            }
+        }
+        //if this thread just released the given lock, try to simplify the graph
+        if (graph[threadIndex][lockIndex] is NO_STATE)
+            reduceGraph(threadIndex, lock);
+    }
+
+    /**
+     * The given scheduling rule is no longer used because the job that invoked it is done.
+     * Release this rule regardless of how many times it was acquired.
+     */
+    void lockReleasedCompletely(Thread owner, ISchedulingRule rule) {
+        int ruleIndex = indexOf(rule, false);
+        int threadIndex = indexOf(owner, false);
+        //need to make sure that the given thread and rule were not already removed from the graph
+        if (threadIndex < 0) {
+            if (JobManager.DEBUG_LOCKS)
+                Stdout.formatln("[lockReleasedCompletely] Lock {} was already released by thread {}", rule, owner.name()); //$NON-NLS-1$ //$NON-NLS-2$
+            return;
+        }
+        if (ruleIndex < 0) {
+            if (JobManager.DEBUG_LOCKS)
+                Stdout.formatln("[lockReleasedCompletely] Thread {} already released lock {}", owner.name(), rule); //$NON-NLS-1$ //$NON-NLS-2$
+            return;
+        }
+        /**
+         * set all rules that are owned by the given thread to NO_STATE
+         * (not just rules that conflict with the rule we are releasing)
+         * if we are releasing a lock, then only update the one entry for the lock
+         */
+        for (int j = 0; j < graph[threadIndex].length; j++) {
+            if (!(cast(ILock)locks.get(j) ) && (graph[threadIndex][j] > NO_STATE))
+                graph[threadIndex][j] = NO_STATE;
+        }
+        reduceGraph(threadIndex, rule);
+    }
+
+    /**
+     * The given thread could not get the given lock and is waiting for it.
+     * Update the graph.
+     */
+    Deadlock lockWaitStart(Thread client, ISchedulingRule lock) {
+        setToWait(client, lock, false);
+        int lockIndex = indexOf(lock, false);
+        int[] temp = new int[lockThreads.size()];
+        //check if the addition of the waiting thread caused deadlock
+        if (!checkWaitCycles(temp, lockIndex))
+            return null;
+        //there is a deadlock in the graph
+        Thread[] threads = getThreadsInDeadlock(client);
+        Thread candidate = resolutionCandidate(threads);
+        ISchedulingRule[] locksToSuspend = realLocksForThread(candidate);
+        Deadlock deadlock = new Deadlock(threads, locksToSuspend, candidate);
+        //find a thread whose locks can be suspended to resolve the deadlock
+        if (JobManager.DEBUG_LOCKS)
+            reportDeadlock(deadlock);
+        if (JobManager.DEBUG_DEADLOCK)
+            throw new IllegalStateException(Format("Deadlock detected. Caused by thread {}.", client.name())); //$NON-NLS-1$
+        // Update the graph to indicate that the locks will now be suspended.
+        // To indicate that the lock will be suspended, we set the thread to wait for the lock.
+        // When the lock is forced to be released, the entry will be cleared.
+        for (int i = 0; i < locksToSuspend.length; i++)
+            setToWait(deadlock.getCandidate(), locksToSuspend[i], true);
+        return deadlock;
+    }
+
+    /**
+     * The given thread has stopped waiting for the given lock.
+     * Update the graph.
+     */
+    void lockWaitStop(Thread owner, ISchedulingRule lock) {
+        int lockIndex = indexOf(lock, false);
+        int threadIndex = indexOf(owner, false);
+        //make sure the thread and lock exist in the graph
+        if (threadIndex < 0) {
+            if (JobManager.DEBUG_LOCKS)
+                Stdout.formatln("Thread {} was already removed.", owner.name() ); //$NON-NLS-1$ //$NON-NLS-2$
+            return;
+        }
+        if (lockIndex < 0) {
+            if (JobManager.DEBUG_LOCKS)
+                Stdout.formatln("Lock {} was already removed.", lock ); //$NON-NLS-1$ //$NON-NLS-2$
+            return;
+        }
+        if (graph[threadIndex][lockIndex] !is WAITING_FOR_LOCK)
+            Assert.isTrue(false, Format("Thread {} was not waiting for lock {} so it could not time out.", owner.name(), (cast(Object)lock).toString())); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+        graph[threadIndex][lockIndex] = NO_STATE;
+        reduceGraph(threadIndex, lock);
+    }
+
+    /**
+     * Returns true IFF the given thread owns a single lock
+     */
+    private bool ownsLocks(Thread cause) {
+        int threadIndex = indexOf(cause, false);
+        for (int j = 0; j < graph[threadIndex].length; j++) {
+            if (graph[threadIndex][j] > NO_STATE)
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true IFF the given thread owns a single real lock.
+     * A real lock is a lock that can be suspended.
+     */
+    private bool ownsRealLocks(Thread owner) {
+        int threadIndex = indexOf(owner, false);
+        for (int j = 0; j < graph[threadIndex].length; j++) {
+            if (graph[threadIndex][j] > NO_STATE) {
+                Object lock = locks.get(j);
+                if (cast(ILock)lock )
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return true IFF this thread owns rule locks (i.e. implicit locks which
+     * cannot be suspended)
+     */
+    private bool ownsRuleLocks(Thread owner) {
+        int threadIndex = indexOf(owner, false);
+        for (int j = 0; j < graph[threadIndex].length; j++) {
+            if (graph[threadIndex][j] > NO_STATE) {
+                Object lock = locks.get(j);
+                if (!(cast(ILock)lock ))
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns an array of real locks that are owned by the given thread.
+     * Real locks are locks that implement the ILock interface and can be suspended.
+     */
+    private ISchedulingRule[] realLocksForThread(Thread owner) {
+        int threadIndex = indexOf(owner, false);
+        ArrayList ownedLocks = new ArrayList(1);
+        for (int j = 0; j < graph[threadIndex].length; j++) {
+            if ((graph[threadIndex][j] > NO_STATE) && (cast(ILock)locks.get(j) ))
+                ownedLocks.add(locks.get(j));
+        }
+        if (ownedLocks.size() is 0)
+            Assert.isLegal(false, "A thread with no real locks was chosen to resolve deadlock."); //$NON-NLS-1$
+        return arraycast!(ISchedulingRule)( ownedLocks.toArray());
+    }
+
+    /**
+     * The matrix has been simplified. Check if any unnecessary rows or columns
+     * can be removed.
+     */
+    private void reduceGraph(int row, ISchedulingRule lock) {
+        int numLocks = locks.size();
+        bool[] emptyColumns = new bool[numLocks];
+
+        /**
+         * find all columns that could possibly be empty
+         * (consist of locks which conflict with the given lock, or of locks which are rules)
+         */
+        for (int j = 0; j < numLocks; j++) {
+            if ((lock.isConflicting(cast(ISchedulingRule) locks.get(j))) || !(cast(ILock)locks.get(j)))
+                emptyColumns[j] = true;
+        }
+
+        bool rowEmpty = true;
+        int numEmpty = 0;
+        //check if the given row is empty
+        for (int j = 0; j < graph[row].length; j++) {
+            if (graph[row][j] !is NO_STATE) {
+                rowEmpty = false;
+                break;
+            }
+        }
+        /**
+         * Check if the possibly empty columns are actually empty.
+         * If a column is actually empty, remove the corresponding lock from the list of locks
+         * Start at the last column so that when locks are removed from the list,
+         * the index of the remaining locks is unchanged. Store the number of empty columns.
+         */
+        for (int j = emptyColumns.length - 1; j >= 0; j--) {
+            for (int i = 0; i < graph.length; i++) {
+                if (emptyColumns[j] && (graph[i][j] !is NO_STATE)) {
+                    emptyColumns[j] = false;
+                    break;
+                }
+            }
+            if (emptyColumns[j]) {
+                locks.remove(j);
+                numEmpty++;
+            }
+        }
+        //if no columns or rows are empty, return right away
+        if ((numEmpty is 0) && (!rowEmpty))
+            return;
+
+        if (rowEmpty)
+            lockThreads.remove(row);
+
+        //new graph (the list of locks and the list of threads are already updated)
+        final int numThreads = lockThreads.size();
+        numLocks = locks.size();
+        //optimize empty graph case
+        if (numThreads is 0 && numLocks is 0) {
+            graph = EMPTY_MATRIX;
+            return;
+        }
+        int[][] tempGraph = new int[][](numThreads,numLocks);
+
+        //the number of rows we need to skip to get the correct entry from the old graph
+        int numRowsSkipped = 0;
+        for (int i = 0; i < graph.length - numRowsSkipped; i++) {
+            if ((i is row) && rowEmpty) {
+                numRowsSkipped++;
+                //check if we need to skip the last row
+                if (i >= graph.length - numRowsSkipped)
+                    break;
+            }
+            //the number of columns we need to skip to get the correct entry from the old graph
+            //needs to be reset for every new row
+            int numColsSkipped = 0;
+            for (int j = 0; j < graph[i].length - numColsSkipped; j++) {
+                while (emptyColumns[j + numColsSkipped]) {
+                    numColsSkipped++;
+                    //check if we need to skip the last column
+                    if (j >= graph[i].length - numColsSkipped)
+                        break;
+                }
+                //need to break out of the outer loop
+                if (j >= graph[i].length - numColsSkipped)
+                    break;
+                tempGraph[i][j] = graph[i + numRowsSkipped][j + numColsSkipped];
+            }
+        }
+        graph = tempGraph;
+        Assert.isTrue(numThreads is graph.length, "Rows and threads don't match."); //$NON-NLS-1$
+        Assert.isTrue(numLocks is ((graph.length > 0) ? graph[0].length : 0), "Columns and locks don't match."); //$NON-NLS-1$
+    }
+
+    /**
+     * Adds a 'deadlock detected' message to the log with a stack trace.
+     */
+    private void reportDeadlock(Deadlock deadlock) {
+        String msg = "Deadlock detected. All locks owned by thread " ~ deadlock.getCandidate().name() ~ " will be suspended."; //$NON-NLS-1$ //$NON-NLS-2$
+        MultiStatus main = new MultiStatus(JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, new IllegalStateException());
+        Thread[] threads = deadlock.getThreads();
+        for (int i = 0; i < threads.length; i++) {
+            Object[] ownedLocks = getOwnedLocks(threads[i]);
+            Object waitLock = getWaitingLock(threads[i]);
+            StringBuffer buf = new StringBuffer("Thread "); //$NON-NLS-1$
+            buf.append(threads[i].name());
+            buf.append(" has locks: "); //$NON-NLS-1$
+            for (int j = 0; j < ownedLocks.length; j++) {
+                buf.append(Format("{}",ownedLocks[j]));
+                buf.append((j < ownedLocks.length - 1) ? ", " : " "); //$NON-NLS-1$ //$NON-NLS-2$
+            }
+            buf.append("and is waiting for lock "); //$NON-NLS-1$
+            buf.append(Format("{}",waitLock));
+            Status child = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, buf.toString(), null);
+            main.add(child);
+        }
+        RuntimeLog.log(main);
+    }
+
+    /**
+     * The number of threads/locks in the graph has changed. Update the
+     * underlying matrix.
+     */
+    private void resizeGraph() {
+        // a new row and/or a new column was added to the graph.
+        // since new rows/columns are always added to the end, just transfer
+        // old entries to the new graph, with the same indices.
+        final int newRows = lockThreads.size();
+        final int newCols = locks.size();
+        //optimize 0x0 and 1x1 matrices
+        if (newRows is 0 && newCols is 0) {
+            graph = EMPTY_MATRIX;
+            return;
+        }
+        int[][] tempGraph = new int[][](newRows,newCols);
+        for (int i = 0; i < graph.length; i++)
+            System.arraycopy(graph[i], 0, tempGraph[i], 0, graph[i].length);
+        graph = tempGraph;
+        resize = false;
+    }
+
+    /**
+     * Get the thread whose locks can be suspended. (i.e. all locks it owns are
+     * actual locks and not rules). Return the first thread in the array by default.
+     */
+    private Thread resolutionCandidate(Thread[] candidates) {
+        //first look for a candidate that has no scheduling rules
+        for (int i = 0; i < candidates.length; i++) {
+            if (!ownsRuleLocks(candidates[i]))
+                return candidates[i];
+        }
+        //next look for any candidate with a real lock (a lock that can be suspended)
+        for (int i = 0; i < candidates.length; i++) {
+            if (ownsRealLocks(candidates[i]))
+                return candidates[i];
+        }
+        //unnecessary, return the first entry in the array by default
+        return candidates[0];
+    }
+
+    /**
+     * The given thread is waiting for the given lock. Update the graph.
+     */
+    private void setToWait(Thread owner, ISchedulingRule lock, bool suspend) {
+        bool needTransfer = false;
+        /**
+         * if we are adding an entry where a thread is waiting on a scheduling rule,
+         * then we need to transfer all positive entries for a conflicting rule to the
+         * newly added rule in order to synchronize the graph.
+         */
+        if (!suspend && !(cast(ILock)lock))
+            needTransfer = true;
+        int lockIndex = indexOf(lock, !suspend);
+        int threadIndex = indexOf(owner, !suspend);
+        if (resize)
+            resizeGraph();
+
+        graph[threadIndex][lockIndex] = WAITING_FOR_LOCK;
+        if (needTransfer)
+            fillPresentEntries(lock, lockIndex);
+    }
+
+    /**
+     * Prints out the current matrix to standard output.
+     * Only used for debugging.
+     */
+    public String toDebugString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append(" :: \n"); //$NON-NLS-1$
+        for (int j = 0; j < locks.size(); j++) {
+            sb.append(" ");
+            sb.append( locks.get(j).toString );
+            sb.append(",");
+        }
+        sb.append("\n");
+        for (int i = 0; i < graph.length; i++) {
+            sb.append(" ");
+            sb.append( (cast(Thread) lockThreads.get(i)).name() );
+            sb.append(" : ");
+            for (int j = 0; j < graph[i].length; j++) {
+                sb.append(" ");
+                sb.append(Integer.toString(graph[i][j])); //$NON-NLS-1$
+                sb.append(",");
+            }
+            sb.append("\n");
+        }
+        sb.append("-------\n"); //$NON-NLS-1$
+        return sb.toString();
+    }
+}