comparison org.eclipse.core.jobs/src/org/eclipse/core/internal/jobs/DeadlockDetector.d @ 12:bc29606a740c

Added dwt-addons in original directory structure of eclipse.org
author Frank Benoit <benoit@tionex.de>
date Sat, 14 Mar 2009 18:23:29 +0100
parents
children 6f068362a363
comparison
equal deleted inserted replaced
11:43904fec5dca 12:bc29606a740c
1 /*******************************************************************************
2 * Copyright (c) 2003, 2006 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13 module org.eclipse.core.internal.jobs.DeadlockDetector;
14
15 import java.lang.JThread;
16 import tango.io.Stdout;
17 import tango.text.convert.Format;
18
19 import java.lang.all;
20 import java.util.ArrayList;
21 import java.util.Set;
22
23 import org.eclipse.core.internal.runtime.RuntimeLog;
24 import org.eclipse.core.runtime.Assert;
25 import org.eclipse.core.runtime.IStatus;
26 import org.eclipse.core.runtime.MultiStatus;
27 import org.eclipse.core.runtime.Status;
28 import org.eclipse.core.runtime.jobs.ILock;
29 import org.eclipse.core.runtime.jobs.ISchedulingRule;
30 import org.eclipse.core.internal.jobs.Deadlock;
31 import org.eclipse.core.internal.jobs.JobManager;
32
33 /**
34 * Stores all the relationships between locks (rules are also considered locks),
35 * and the threads that own them. All the relationships are stored in a 2D integer array.
36 * The rows in the array are threads, while the columns are locks.
37 * Two corresponding arrayLists store the actual threads and locks.
38 * The index of a thread in the first arrayList is the index of the row in the graph.
39 * The index of a lock in the second arrayList is the index of the column in the graph.
40 * An entry greater than 0 in the graph is the number of times a thread in the entry's row
41 * acquired the lock in the entry's column.
42 * An entry of -1 means that the thread is waiting to acquire the lock.
43 * An entry of 0 means that the thread and the lock have no relationship.
44 *
45 * The difference between rules and locks is that locks can be suspended, while
46 * rules are implicit locks and as such cannot be suspended.
47 * To resolve deadlock, the graph will first try to find a thread that only owns
48 * locks. Failing that, it will find a thread in the deadlock that owns at least
49 * one lock and suspend it.
50 *
51 * Deadlock can only occur among locks, or among locks in combination with rules.
52 * Deadlock among rules only is impossible. Therefore, in any deadlock one can always
53 * find a thread that owns at least one lock that can be suspended.
54 *
55 * The implementation of the graph assumes that a thread can only own 1 rule at
56 * any one time. It can acquire that rule several times, but a thread cannot
57 * acquire 2 non-conflicting rules at the same time.
58 *
59 * The implementation of the graph will sometimes also find and resolve bogus deadlocks.
60 * graph: assuming this rule hierarchy:
61 * R2 R3 L1 R1
62 * J1 1 0 0 / \
63 * J2 0 1 -1 R2 R3
64 * J3 -1 0 1
65 *
66 * If in the above situation job4 decides to acquire rule1, then the graph will transform
67 * to the following:
68 * R2 R3 R1 L1
69 * J1 1 0 1 0
70 * J2 1 1 1 -1
71 * J3 -1 0 0 1
72 * J4 0 0 -1 0
73 *
74 * and the graph will assume that job2 and job3 are deadlocked and suspend lock1 of job3.
75 * The reason the deadlock is bogus is that the deadlock is unlikely to actually happen (the threads
76 * are currently not deadlocked, but might deadlock later on when it is too late to detect it)
77 * Therefore, in order to make sure that no deadlock is possible,
78 * the deadlock will still be resolved at this point.
79 */
80 class DeadlockDetector {
81 private static int NO_STATE = 0;
82 //state variables in the graph
83 private static int WAITING_FOR_LOCK = -1;
84 //empty matrix
85 private static const int[][] EMPTY_MATRIX = null;
86 //matrix of relationships between threads and locks
87 private int[][] graph = EMPTY_MATRIX;
88 //index is column in adjacency matrix for the lock
89 private final ArrayList locks;
90 //index is row in adjacency matrix for the thread
91 private final ArrayList lockThreads;
92 //whether the graph needs to be resized
93 private bool resize = false;
94
95 public this(){
96 locks = new ArrayList();
97 lockThreads = new ArrayList();
98 }
99
100 /**
101 * Recursively check if any of the threads that prevent the current thread from running
102 * are actually deadlocked with the current thread.
103 * Add the threads that form deadlock to the deadlockedThreads list.
104 */
105 private bool addCycleThreads(ArrayList deadlockedThreads, JThread next) {
106 //get the thread that block the given thread from running
107 JThread[] blocking = blockingThreads(next);
108 //if the thread is not blocked by other threads, then it is not part of a deadlock
109 if (blocking.length is 0)
110 return false;
111 bool inCycle = false;
112 for (int i = 0; i < blocking.length; i++) {
113 //if we have already visited the given thread, then we found a cycle
114 if (deadlockedThreads.contains(blocking[i])) {
115 inCycle = true;
116 } else {
117 //otherwise, add the thread to our list and recurse deeper
118 deadlockedThreads.add(blocking[i]);
119 //if the thread is not part of a cycle, remove it from the list
120 if (addCycleThreads(deadlockedThreads, blocking[i]))
121 inCycle = true;
122 else
123 deadlockedThreads.remove(blocking[i]);
124 }
125 }
126 return inCycle;
127 }
128
129 /**
130 * Get the thread(s) that own the lock this thread is waiting for.
131 */
132 private JThread[] blockingThreads(JThread current) {
133 //find the lock this thread is waiting for
134 ISchedulingRule lock = cast(ISchedulingRule) getWaitingLock(current);
135 return getThreadsOwningLock(lock);
136 }
137
138 /**
139 * Check that the addition of a waiting thread did not produce deadlock.
140 * If deadlock is detected return true, else return false.
141 */
142 private bool checkWaitCycles(int[] waitingThreads, int lockIndex) {
143 /**
144 * find the lock that this thread is waiting for
145 * recursively check if this is a cycle (i.e. a thread waiting on itself)
146 */
147 for (int i = 0; i < graph.length; i++) {
148 if (graph[i][lockIndex] > NO_STATE) {
149 if (waitingThreads[i] > NO_STATE) {
150 return true;
151 }
152 //keep track that we already visited this thread
153 waitingThreads[i]++;
154 for (int j = 0; j < graph[i].length; j++) {
155 if (graph[i][j] is WAITING_FOR_LOCK) {
156 if (checkWaitCycles(waitingThreads, j))
157 return true;
158 }
159 }
160 //this thread is not involved in a cycle yet, so remove the visited flag
161 waitingThreads[i]--;
162 }
163 }
164 return false;
165 }
166
167 /**
168 * Returns true IFF the matrix contains a row for the given thread.
169 * (meaning the given thread either owns locks or is waiting for locks)
170 */
171 bool contains(JThread t) {
172 return lockThreads.contains(t);
173 }
174
175 /**
176 * A new rule was just added to the graph.
177 * Find a rule it conflicts with and update the new rule with the number of times
178 * it was acquired implicitly when threads acquired conflicting rule.
179 */
180 private void fillPresentEntries(ISchedulingRule newLock, int lockIndex) {
181 //fill in the entries for the new rule from rules it conflicts with
182 for (int j = 0; j < locks.size(); j++) {
183 if ((j !is lockIndex) && (newLock.isConflicting(cast(ISchedulingRule) locks.get(j)))) {
184 for (int i = 0; i < graph.length; i++) {
185 if ((graph[i][j] > NO_STATE) && (graph[i][lockIndex] is NO_STATE))
186 graph[i][lockIndex] = graph[i][j];
187 }
188 }
189 }
190 //now back fill the entries for rules the current rule conflicts with
191 for (int j = 0; j < locks.size(); j++) {
192 if ((j !is lockIndex) && (newLock.isConflicting(cast(ISchedulingRule) locks.get(j)))) {
193 for (int i = 0; i < graph.length; i++) {
194 if ((graph[i][lockIndex] > NO_STATE) && (graph[i][j] is NO_STATE))
195 graph[i][j] = graph[i][lockIndex];
196 }
197 }
198 }
199 }
200
201 /**
202 * Returns all the locks owned by the given thread
203 */
204 private Object[] getOwnedLocks(JThread current) {
205 ArrayList ownedLocks = new ArrayList(1);
206 int index = indexOf(current, false);
207
208 for (int j = 0; j < graph[index].length; j++) {
209 if (graph[index][j] > NO_STATE)
210 ownedLocks.add(locks.get(j));
211 }
212 if (ownedLocks.size() is 0)
213 Assert.isLegal(false, "A thread with no locks is part of a deadlock."); //$NON-NLS-1$
214 return ownedLocks.toArray();
215 }
216
217 /**
218 * Returns an array of threads that form the deadlock (usually 2).
219 */
220 private JThread[] getThreadsInDeadlock(JThread cause) {
221 ArrayList deadlockedThreads = new ArrayList(2);
222 /**
223 * if the thread that caused deadlock doesn't own any locks, then it is not part
224 * of the deadlock (it just caused it because of a rule it tried to acquire)
225 */
226 if (ownsLocks(cause))
227 deadlockedThreads.add(cause);
228 addCycleThreads(deadlockedThreads, cause);
229 return arraycast!(JThread)( deadlockedThreads.toArray());
230 }
231
232 /**
233 * Returns the thread(s) that own the given lock.
234 */
235 private JThread[] getThreadsOwningLock(ISchedulingRule rule) {
236 if (rule is null)
237 return new JThread[0];
238 int lockIndex = indexOf(rule, false);
239 ArrayList blocking = new ArrayList(1);
240 for (int i = 0; i < graph.length; i++) {
241 if (graph[i][lockIndex] > NO_STATE)
242 blocking.add(lockThreads.get(i));
243 }
244 if ((blocking.size() is 0) && (JobManager.DEBUG_LOCKS))
245 Stdout.formatln(Format("Lock {} is involved in deadlock but is not owned by any thread.", rule )); //$NON-NLS-1$ //$NON-NLS-2$
246 if ((blocking.size() > 1) && (cast(ILock)rule ) && (JobManager.DEBUG_LOCKS))
247 Stdout.formatln(Format("Lock {} is owned by more than 1 thread, but it is not a rule.", rule )); //$NON-NLS-1$ //$NON-NLS-2$
248 return arraycast!(JThread)( blocking.toArray());
249 }
250
251 /**
252 * Returns the lock the given thread is waiting for.
253 */
254 private Object getWaitingLock(JThread current) {
255 int index = indexOf(current, false);
256 //find the lock that this thread is waiting for
257 for (int j = 0; j < graph[index].length; j++) {
258 if (graph[index][j] is WAITING_FOR_LOCK)
259 return locks.get(j);
260 }
261 //it can happen that a thread is not waiting for any lock (it is not really part of the deadlock)
262 return null;
263 }
264
265 /**
266 * Returns the index of the given lock in the lock array. If the lock is
267 * not present in the array, it is added to the end.
268 */
269 private int indexOf(ISchedulingRule lock, bool add) {
270 int index = locks.indexOf(cast(Object)lock);
271 if ((index < 0) && add) {
272 locks.add(cast(Object)lock);
273 resize = true;
274 index = locks.size() - 1;
275 }
276 return index;
277 }
278
279 /**
280 * Returns the index of the given thread in the thread array. If the thread
281 * is not present in the array, it is added to the end.
282 */
283 private int indexOf(JThread owner, bool add) {
284 int index = lockThreads.indexOf(owner);
285 if ((index < 0) && add) {
286 lockThreads.add(owner);
287 resize = true;
288 index = lockThreads.size() - 1;
289 }
290 return index;
291 }
292
293 /**
294 * Returns true IFF the adjacency matrix is empty.
295 */
296 bool isEmpty() {
297 return (locks.size() is 0) && (lockThreads.size() is 0) && (graph.length is 0);
298 }
299
300 /**
301 * The given lock was acquired by the given thread.
302 */
303 void lockAcquired(JThread owner, ISchedulingRule lock) {
304 int lockIndex = indexOf(lock, true);
305 int threadIndex = indexOf(owner, true);
306 if (resize)
307 resizeGraph();
308 if (graph[threadIndex][lockIndex] is WAITING_FOR_LOCK)
309 graph[threadIndex][lockIndex] = NO_STATE;
310 /**
311 * acquire all locks that conflict with the given lock
312 * or conflict with a lock the given lock will acquire implicitly
313 * (locks are acquired implicitly when a conflicting lock is acquired)
314 */
315 ArrayList conflicting = new ArrayList(1);
316 //only need two passes through all the locks to pick up all conflicting rules
317 int NUM_PASSES = 2;
318 conflicting.add(cast(Object)lock);
319 graph[threadIndex][lockIndex]++;
320 for (int i = 0; i < NUM_PASSES; i++) {
321 for (int k = 0; k < conflicting.size(); k++) {
322 ISchedulingRule current = cast(ISchedulingRule) conflicting.get(k);
323 for (int j = 0; j < locks.size(); j++) {
324 ISchedulingRule possible = cast(ISchedulingRule) locks.get(j);
325 if (current.isConflicting(possible) && !conflicting.contains(cast(Object)possible)) {
326 conflicting.add(cast(Object)possible);
327 graph[threadIndex][j]++;
328 }
329 }
330 }
331 }
332 }
333
334 /**
335 * The given lock was released by the given thread. Update the graph.
336 */
337 void lockReleased(JThread owner, ISchedulingRule lock) {
338 int lockIndex = indexOf(lock, false);
339 int threadIndex = indexOf(owner, false);
340 //make sure the lock and thread exist in the graph
341 if (threadIndex < 0) {
342 if (JobManager.DEBUG_LOCKS)
343 Stdout.formatln("[lockReleased] Lock {} was already released by thread {}", lock, owner.getName()); //$NON-NLS-1$ //$NON-NLS-2$
344 return;
345 }
346 if (lockIndex < 0) {
347 if (JobManager.DEBUG_LOCKS)
348 Stdout.formatln("[lockReleased] Thread {} already released lock {}", owner.getName(), lock); //$NON-NLS-1$ //$NON-NLS-2$
349 return;
350 }
351 //if this lock was suspended, set it to NO_STATE
352 if ((cast(ILock)lock ) && (graph[threadIndex][lockIndex] is WAITING_FOR_LOCK)) {
353 graph[threadIndex][lockIndex] = NO_STATE;
354 return;
355 }
356 //release all locks that conflict with the given lock
357 //or release all rules that are owned by the given thread, if we are releasing a rule
358 for (int j = 0; j < graph[threadIndex].length; j++) {
359 if ((lock.isConflicting(cast(ISchedulingRule) locks.get(j))) || (!(cast(ILock)lock ) && !(cast(ILock)locks.get(j)) && (graph[threadIndex][j] > NO_STATE))) {
360 if (graph[threadIndex][j] is NO_STATE) {
361 if (JobManager.DEBUG_LOCKS)
362 Stdout.formatln("[lockReleased] More releases than acquires for thread {} and lock {}", owner.getName(), lock); //$NON-NLS-1$ //$NON-NLS-2$
363 } else {
364 graph[threadIndex][j]--;
365 }
366 }
367 }
368 //if this thread just released the given lock, try to simplify the graph
369 if (graph[threadIndex][lockIndex] is NO_STATE)
370 reduceGraph(threadIndex, lock);
371 }
372
373 /**
374 * The given scheduling rule is no longer used because the job that invoked it is done.
375 * Release this rule regardless of how many times it was acquired.
376 */
377 void lockReleasedCompletely(JThread owner, ISchedulingRule rule) {
378 int ruleIndex = indexOf(rule, false);
379 int threadIndex = indexOf(owner, false);
380 //need to make sure that the given thread and rule were not already removed from the graph
381 if (threadIndex < 0) {
382 if (JobManager.DEBUG_LOCKS)
383 Stdout.formatln("[lockReleasedCompletely] Lock {} was already released by thread {}", rule, owner.getName()); //$NON-NLS-1$ //$NON-NLS-2$
384 return;
385 }
386 if (ruleIndex < 0) {
387 if (JobManager.DEBUG_LOCKS)
388 Stdout.formatln("[lockReleasedCompletely] Thread {} already released lock {}", owner.getName(), rule); //$NON-NLS-1$ //$NON-NLS-2$
389 return;
390 }
391 /**
392 * set all rules that are owned by the given thread to NO_STATE
393 * (not just rules that conflict with the rule we are releasing)
394 * if we are releasing a lock, then only update the one entry for the lock
395 */
396 for (int j = 0; j < graph[threadIndex].length; j++) {
397 if (!(cast(ILock)locks.get(j) ) && (graph[threadIndex][j] > NO_STATE))
398 graph[threadIndex][j] = NO_STATE;
399 }
400 reduceGraph(threadIndex, rule);
401 }
402
403 /**
404 * The given thread could not get the given lock and is waiting for it.
405 * Update the graph.
406 */
407 Deadlock lockWaitStart(JThread client, ISchedulingRule lock) {
408 setToWait(client, lock, false);
409 int lockIndex = indexOf(lock, false);
410 int[] temp = new int[lockThreads.size()];
411 //check if the addition of the waiting thread caused deadlock
412 if (!checkWaitCycles(temp, lockIndex))
413 return null;
414 //there is a deadlock in the graph
415 JThread[] threads = getThreadsInDeadlock(client);
416 JThread candidate = resolutionCandidate(threads);
417 ISchedulingRule[] locksToSuspend = realLocksForThread(candidate);
418 Deadlock deadlock = new Deadlock(threads, locksToSuspend, candidate);
419 //find a thread whose locks can be suspended to resolve the deadlock
420 if (JobManager.DEBUG_LOCKS)
421 reportDeadlock(deadlock);
422 if (JobManager.DEBUG_DEADLOCK)
423 throw new IllegalStateException(Format("Deadlock detected. Caused by thread {}.", client.getName())); //$NON-NLS-1$
424 // Update the graph to indicate that the locks will now be suspended.
425 // To indicate that the lock will be suspended, we set the thread to wait for the lock.
426 // When the lock is forced to be released, the entry will be cleared.
427 for (int i = 0; i < locksToSuspend.length; i++)
428 setToWait(deadlock.getCandidate(), locksToSuspend[i], true);
429 return deadlock;
430 }
431
432 /**
433 * The given thread has stopped waiting for the given lock.
434 * Update the graph.
435 */
436 void lockWaitStop(JThread owner, ISchedulingRule lock) {
437 int lockIndex = indexOf(lock, false);
438 int threadIndex = indexOf(owner, false);
439 //make sure the thread and lock exist in the graph
440 if (threadIndex < 0) {
441 if (JobManager.DEBUG_LOCKS)
442 Stdout.formatln("Thread {} was already removed.", owner.getName() ); //$NON-NLS-1$ //$NON-NLS-2$
443 return;
444 }
445 if (lockIndex < 0) {
446 if (JobManager.DEBUG_LOCKS)
447 Stdout.formatln("Lock {} was already removed.", lock ); //$NON-NLS-1$ //$NON-NLS-2$
448 return;
449 }
450 if (graph[threadIndex][lockIndex] !is WAITING_FOR_LOCK)
451 Assert.isTrue(false, Format("Thread {} was not waiting for lock {} so it could not time out.", owner.getName(), (cast(Object)lock).toString())); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
452 graph[threadIndex][lockIndex] = NO_STATE;
453 reduceGraph(threadIndex, lock);
454 }
455
456 /**
457 * Returns true IFF the given thread owns a single lock
458 */
459 private bool ownsLocks(JThread cause) {
460 int threadIndex = indexOf(cause, false);
461 for (int j = 0; j < graph[threadIndex].length; j++) {
462 if (graph[threadIndex][j] > NO_STATE)
463 return true;
464 }
465 return false;
466 }
467
468 /**
469 * Returns true IFF the given thread owns a single real lock.
470 * A real lock is a lock that can be suspended.
471 */
472 private bool ownsRealLocks(JThread owner) {
473 int threadIndex = indexOf(owner, false);
474 for (int j = 0; j < graph[threadIndex].length; j++) {
475 if (graph[threadIndex][j] > NO_STATE) {
476 Object lock = locks.get(j);
477 if (cast(ILock)lock )
478 return true;
479 }
480 }
481 return false;
482 }
483
484 /**
485 * Return true IFF this thread owns rule locks (i.e. implicit locks which
486 * cannot be suspended)
487 */
488 private bool ownsRuleLocks(JThread owner) {
489 int threadIndex = indexOf(owner, false);
490 for (int j = 0; j < graph[threadIndex].length; j++) {
491 if (graph[threadIndex][j] > NO_STATE) {
492 Object lock = locks.get(j);
493 if (!(cast(ILock)lock ))
494 return true;
495 }
496 }
497 return false;
498 }
499
500 /**
501 * Returns an array of real locks that are owned by the given thread.
502 * Real locks are locks that implement the ILock interface and can be suspended.
503 */
504 private ISchedulingRule[] realLocksForThread(JThread owner) {
505 int threadIndex = indexOf(owner, false);
506 ArrayList ownedLocks = new ArrayList(1);
507 for (int j = 0; j < graph[threadIndex].length; j++) {
508 if ((graph[threadIndex][j] > NO_STATE) && (cast(ILock)locks.get(j) ))
509 ownedLocks.add(locks.get(j));
510 }
511 if (ownedLocks.size() is 0)
512 Assert.isLegal(false, "A thread with no real locks was chosen to resolve deadlock."); //$NON-NLS-1$
513 return arraycast!(ISchedulingRule)( ownedLocks.toArray());
514 }
515
516 /**
517 * The matrix has been simplified. Check if any unnecessary rows or columns
518 * can be removed.
519 */
520 private void reduceGraph(int row, ISchedulingRule lock) {
521 int numLocks = locks.size();
522 bool[] emptyColumns = new bool[numLocks];
523
524 /**
525 * find all columns that could possibly be empty
526 * (consist of locks which conflict with the given lock, or of locks which are rules)
527 */
528 for (int j = 0; j < numLocks; j++) {
529 if ((lock.isConflicting(cast(ISchedulingRule) locks.get(j))) || !(cast(ILock)locks.get(j)))
530 emptyColumns[j] = true;
531 }
532
533 bool rowEmpty = true;
534 int numEmpty = 0;
535 //check if the given row is empty
536 for (int j = 0; j < graph[row].length; j++) {
537 if (graph[row][j] !is NO_STATE) {
538 rowEmpty = false;
539 break;
540 }
541 }
542 /**
543 * Check if the possibly empty columns are actually empty.
544 * If a column is actually empty, remove the corresponding lock from the list of locks
545 * Start at the last column so that when locks are removed from the list,
546 * the index of the remaining locks is unchanged. Store the number of empty columns.
547 */
548 for (int j = emptyColumns.length - 1; j >= 0; j--) {
549 for (int i = 0; i < graph.length; i++) {
550 if (emptyColumns[j] && (graph[i][j] !is NO_STATE)) {
551 emptyColumns[j] = false;
552 break;
553 }
554 }
555 if (emptyColumns[j]) {
556 locks.remove(j);
557 numEmpty++;
558 }
559 }
560 //if no columns or rows are empty, return right away
561 if ((numEmpty is 0) && (!rowEmpty))
562 return;
563
564 if (rowEmpty)
565 lockThreads.remove(row);
566
567 //new graph (the list of locks and the list of threads are already updated)
568 final int numThreads = lockThreads.size();
569 numLocks = locks.size();
570 //optimize empty graph case
571 if (numThreads is 0 && numLocks is 0) {
572 graph = EMPTY_MATRIX;
573 return;
574 }
575 int[][] tempGraph = new int[][](numThreads,numLocks);
576
577 //the number of rows we need to skip to get the correct entry from the old graph
578 int numRowsSkipped = 0;
579 for (int i = 0; i < graph.length - numRowsSkipped; i++) {
580 if ((i is row) && rowEmpty) {
581 numRowsSkipped++;
582 //check if we need to skip the last row
583 if (i >= graph.length - numRowsSkipped)
584 break;
585 }
586 //the number of columns we need to skip to get the correct entry from the old graph
587 //needs to be reset for every new row
588 int numColsSkipped = 0;
589 for (int j = 0; j < graph[i].length - numColsSkipped; j++) {
590 while (emptyColumns[j + numColsSkipped]) {
591 numColsSkipped++;
592 //check if we need to skip the last column
593 if (j >= graph[i].length - numColsSkipped)
594 break;
595 }
596 //need to break out of the outer loop
597 if (j >= graph[i].length - numColsSkipped)
598 break;
599 tempGraph[i][j] = graph[i + numRowsSkipped][j + numColsSkipped];
600 }
601 }
602 graph = tempGraph;
603 Assert.isTrue(numThreads is graph.length, "Rows and threads don't match."); //$NON-NLS-1$
604 Assert.isTrue(numLocks is ((graph.length > 0) ? graph[0].length : 0), "Columns and locks don't match."); //$NON-NLS-1$
605 }
606
607 /**
608 * Adds a 'deadlock detected' message to the log with a stack trace.
609 */
610 private void reportDeadlock(Deadlock deadlock) {
611 String msg = "Deadlock detected. All locks owned by thread " ~ deadlock.getCandidate().getName() ~ " will be suspended."; //$NON-NLS-1$ //$NON-NLS-2$
612 MultiStatus main = new MultiStatus(JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, new IllegalStateException());
613 JThread[] threads = deadlock.getThreads();
614 for (int i = 0; i < threads.length; i++) {
615 Object[] ownedLocks = getOwnedLocks(threads[i]);
616 Object waitLock = getWaitingLock(threads[i]);
617 StringBuffer buf = new StringBuffer("Thread "); //$NON-NLS-1$
618 buf.append(threads[i].getName());
619 buf.append(" has locks: "); //$NON-NLS-1$
620 for (int j = 0; j < ownedLocks.length; j++) {
621 buf.append(Format("{}",ownedLocks[j]));
622 buf.append((j < ownedLocks.length - 1) ? ", " : " "); //$NON-NLS-1$ //$NON-NLS-2$
623 }
624 buf.append("and is waiting for lock "); //$NON-NLS-1$
625 buf.append(Format("{}",waitLock));
626 Status child = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, buf.toString(), null);
627 main.add(child);
628 }
629 RuntimeLog.log(main);
630 }
631
632 /**
633 * The number of threads/locks in the graph has changed. Update the
634 * underlying matrix.
635 */
636 private void resizeGraph() {
637 // a new row and/or a new column was added to the graph.
638 // since new rows/columns are always added to the end, just transfer
639 // old entries to the new graph, with the same indices.
640 final int newRows = lockThreads.size();
641 final int newCols = locks.size();
642 //optimize 0x0 and 1x1 matrices
643 if (newRows is 0 && newCols is 0) {
644 graph = EMPTY_MATRIX;
645 return;
646 }
647 int[][] tempGraph = new int[][](newRows,newCols);
648 for (int i = 0; i < graph.length; i++)
649 System.arraycopy(graph[i], 0, tempGraph[i], 0, graph[i].length);
650 graph = tempGraph;
651 resize = false;
652 }
653
654 /**
655 * Get the thread whose locks can be suspended. (i.e. all locks it owns are
656 * actual locks and not rules). Return the first thread in the array by default.
657 */
658 private JThread resolutionCandidate(JThread[] candidates) {
659 //first look for a candidate that has no scheduling rules
660 for (int i = 0; i < candidates.length; i++) {
661 if (!ownsRuleLocks(candidates[i]))
662 return candidates[i];
663 }
664 //next look for any candidate with a real lock (a lock that can be suspended)
665 for (int i = 0; i < candidates.length; i++) {
666 if (ownsRealLocks(candidates[i]))
667 return candidates[i];
668 }
669 //unnecessary, return the first entry in the array by default
670 return candidates[0];
671 }
672
673 /**
674 * The given thread is waiting for the given lock. Update the graph.
675 */
676 private void setToWait(JThread owner, ISchedulingRule lock, bool suspend) {
677 bool needTransfer = false;
678 /**
679 * if we are adding an entry where a thread is waiting on a scheduling rule,
680 * then we need to transfer all positive entries for a conflicting rule to the
681 * newly added rule in order to synchronize the graph.
682 */
683 if (!suspend && !(cast(ILock)lock))
684 needTransfer = true;
685 int lockIndex = indexOf(lock, !suspend);
686 int threadIndex = indexOf(owner, !suspend);
687 if (resize)
688 resizeGraph();
689
690 graph[threadIndex][lockIndex] = WAITING_FOR_LOCK;
691 if (needTransfer)
692 fillPresentEntries(lock, lockIndex);
693 }
694
695 /**
696 * Prints out the current matrix to standard output.
697 * Only used for debugging.
698 */
699 public String toDebugString() {
700 StringBuffer sb = new StringBuffer();
701 sb.append(" :: \n"); //$NON-NLS-1$
702 for (int j = 0; j < locks.size(); j++) {
703 sb.append(" ");
704 sb.append( locks.get(j).toString );
705 sb.append(",");
706 }
707 sb.append("\n");
708 for (int i = 0; i < graph.length; i++) {
709 sb.append(" ");
710 sb.append( (cast(JThread) lockThreads.get(i)).getName() );
711 sb.append(" : ");
712 for (int j = 0; j < graph[i].length; j++) {
713 sb.append(" ");
714 sb.append(Integer.toString(graph[i][j])); //$NON-NLS-1$
715 sb.append(",");
716 }
717 sb.append("\n");
718 }
719 sb.append("-------\n"); //$NON-NLS-1$
720 return sb.toString();
721 }
722 }