1 /******************************************************************************* 2 * Copyright (c) 2003, 2014 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM - Initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.core.internal.jobs; 15 16 import java.util.ArrayDeque; 17 import java.util.HashMap; 18 import org.eclipse.core.internal.runtime.RuntimeLog; 19 import org.eclipse.core.runtime.*; 20 import org.eclipse.core.runtime.jobs.ISchedulingRule; 21 import org.eclipse.core.runtime.jobs.LockListener; 22 23 /** 24 * Stores the only reference to the graph that contains all the known 25 * relationships between locks, rules, and the threads that own them. 26 * Synchronizes all access to the graph on the only instance that exists in this class. 27 * 28 * Also stores the state of suspended locks so that they can be re-acquired with 29 * the proper lock depth. 30 */ 31 public class LockManager { 32 /** 33 * This class captures the state of suspended locks. 34 * Locks are suspended if deadlock is detected. 35 */ 36 private static class LockState { 37 private int depth; 38 private OrderedLock lock; 39 40 /** 41 * Suspends ownership of the given lock, and returns the saved state. 42 */ suspend(OrderedLock lock)43 protected static LockState suspend(OrderedLock lock) { 44 LockState state = new LockState(); 45 state.lock = lock; 46 state.depth = lock.forceRelease(); 47 return state; 48 } 49 50 /** 51 * Re-acquires a suspended lock and reverts to the correct lock depth. 52 */ resume()53 public void resume() { 54 //spin until the lock is successfully acquired 55 //NOTE: spinning here allows the UI thread to service pending syncExecs 56 //if the UI thread is waiting to acquire a lock. 57 while (true) { 58 try { 59 if (lock.acquire(Long.MAX_VALUE)) 60 break; 61 } catch (InterruptedException e) { 62 //ignore and loop 63 } 64 } 65 lock.setDepth(depth); 66 } 67 } 68 69 //the lock listener for this lock manager 70 protected LockListener lockListener; 71 /* 72 * The internal data structure that stores all the relationships 73 * between the locks (or rules) and the threads that own them. 74 */ 75 private DeadlockDetector locks = new DeadlockDetector(); 76 /* 77 * Stores thread - stack pairs where every entry in the stack is an array 78 * of locks that were suspended while the thread was acquiring more locks 79 * (a stack is needed because when a thread tries to re-acquire suspended locks, 80 * it can cause deadlock, and some locks it owns can be suspended again) 81 */ 82 private final HashMap<Thread, ArrayDeque<LockState[]>> suspendedLocks = new HashMap<>(); 83 aboutToRelease()84 public void aboutToRelease() { 85 if (lockListener == null) 86 return; 87 try { 88 lockListener.aboutToRelease(); 89 } catch (Exception | LinkageError e) { 90 handleException(e); 91 } 92 } 93 canBlock()94 public boolean canBlock() { 95 if (lockListener == null) 96 return true; 97 try { 98 return lockListener.canBlock(); 99 } catch (Exception | LinkageError e) { 100 handleException(e); 101 } 102 return false; 103 } 104 aboutToWait(Thread lockOwner)105 public boolean aboutToWait(Thread lockOwner) { 106 if (lockListener == null) 107 return false; 108 try { 109 return lockListener.aboutToWait(lockOwner); 110 } catch (Exception | LinkageError e) { 111 handleException(e); 112 } 113 return false; 114 } 115 116 /** 117 * This thread has just acquired a lock. Update graph. 118 */ addLockThread(Thread thread, ISchedulingRule lock)119 void addLockThread(Thread thread, ISchedulingRule lock) { 120 DeadlockDetector tempLocks = locks; 121 if (tempLocks == null) 122 return; 123 try { 124 synchronized (tempLocks) { 125 try { 126 tempLocks.lockAcquired(thread, lock); 127 } catch (Exception e) { 128 throw createDebugException(tempLocks, e); 129 } 130 } 131 } catch (Exception e) { 132 handleInternalError(e); 133 } 134 } 135 136 /** 137 * This thread has just been refused a lock. Update graph and check for deadlock. 138 */ addLockWaitThread(Thread thread, ISchedulingRule lock)139 void addLockWaitThread(Thread thread, ISchedulingRule lock) { 140 DeadlockDetector tempLocks = locks; 141 if (tempLocks == null) 142 return; 143 try { 144 Deadlock found = null; 145 synchronized (tempLocks) { 146 try { 147 found = tempLocks.lockWaitStart(thread, lock); 148 } catch (Exception e) { 149 throw createDebugException(tempLocks, e); 150 } 151 } 152 if (found == null) 153 return; 154 // if deadlock was detected, the found variable will contain all the information about it, 155 // including which locks to suspend for which thread to resolve the deadlock. 156 ISchedulingRule[] toSuspend = found.getLocks(); 157 LockState[] suspended = new LockState[toSuspend.length]; 158 for (int i = 0; i < toSuspend.length; i++) 159 suspended[i] = LockState.suspend((OrderedLock) toSuspend[i]); 160 synchronized (suspendedLocks) { 161 suspendedLocks.computeIfAbsent(found.getCandidate(), key -> new ArrayDeque<>()).push(suspended); 162 } 163 } catch (Exception e) { 164 handleInternalError(e); 165 } 166 } 167 createDebugException(DeadlockDetector tempLocks, Exception rootException)168 private Exception createDebugException(DeadlockDetector tempLocks, Exception rootException) { 169 String debugString = null; 170 try { 171 debugString = tempLocks.toDebugString(); 172 } catch (Exception e) { 173 //ignore failure to create the debug string 174 } 175 return new Exception(debugString, rootException); 176 } 177 178 /** 179 * Handles exceptions that occur while calling third party code from within the 180 * LockManager. This is essentially an in-lined version of Platform.run(ISafeRunnable) 181 */ handleException(Throwable e)182 private static void handleException(Throwable e) { 183 IStatus status; 184 if (e instanceof CoreException) { 185 //logged message should not be translated 186 status = new MultiStatus(JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "LockManager.handleException", e); //$NON-NLS-1$ 187 ((MultiStatus) status).merge(((CoreException) e).getStatus()); 188 } else { 189 status = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "LockManager.handleException", e); //$NON-NLS-1$ 190 } 191 RuntimeLog.log(status); 192 } 193 194 /** 195 * There was an internal error in the deadlock detection code. Shut the entire 196 * thing down to prevent further errors. Recovery is too complex as it 197 * requires freezing all threads and inferring the present lock state. 198 */ handleInternalError(Throwable t)199 private void handleInternalError(Throwable t) { 200 try { 201 handleException(t); 202 } catch (Exception e) { 203 //ignore failure to log 204 } 205 //discard the deadlock detector for good 206 locks = null; 207 } 208 209 /** 210 * Returns true IFF the underlying graph is empty. 211 * For debugging purposes only. 212 */ isEmpty()213 public boolean isEmpty() { 214 return locks.isEmpty(); 215 } 216 217 /** 218 * Returns true IFF this thread either owns, or is waiting for, any locks or rules. 219 */ isLockOwner()220 public boolean isLockOwner() { 221 //all job threads have to be treated as lock owners because UI thread 222 //may try to join a job 223 Thread current = Thread.currentThread(); 224 if (current instanceof Worker) 225 return true; 226 DeadlockDetector tempLocks = locks; 227 if (tempLocks == null) 228 return false; 229 synchronized (tempLocks) { 230 return tempLocks.contains(Thread.currentThread()); 231 } 232 } 233 234 /** 235 * Creates and returns a new lock. 236 */ newLock()237 public synchronized OrderedLock newLock() { 238 return new OrderedLock(this); 239 } 240 241 /** 242 * Releases all the acquires that were called on the given rule. Needs to be called only once. 243 */ removeLockCompletely(Thread thread, ISchedulingRule rule)244 void removeLockCompletely(Thread thread, ISchedulingRule rule) { 245 DeadlockDetector tempLocks = locks; 246 if (tempLocks == null) 247 return; 248 try { 249 synchronized (tempLocks) { 250 try { 251 tempLocks.lockReleasedCompletely(thread, rule); 252 } catch (Exception e) { 253 throw createDebugException(tempLocks, e); 254 } 255 } 256 } catch (Exception e) { 257 handleInternalError(e); 258 } 259 } 260 261 /** 262 * This thread has just released a lock. Update graph. 263 */ removeLockThread(Thread thread, ISchedulingRule lock)264 void removeLockThread(Thread thread, ISchedulingRule lock) { 265 DeadlockDetector tempLocks = locks; 266 if (tempLocks == null) 267 return; 268 try { 269 synchronized (tempLocks) { 270 try { 271 tempLocks.lockReleased(thread, lock); 272 } catch (Exception e) { 273 throw createDebugException(tempLocks, e); 274 } 275 } 276 } catch (Exception e) { 277 handleInternalError(e); 278 } 279 } 280 281 /** 282 * This thread has just stopped waiting for a lock. Update graph. 283 * If the thread has already been granted the lock (or wasn't waiting 284 * for the lock) then the graph remains unchanged. 285 */ removeLockWaitThread(Thread thread, ISchedulingRule lock)286 void removeLockWaitThread(Thread thread, ISchedulingRule lock) { 287 DeadlockDetector tempLocks = locks; 288 if (tempLocks == null) 289 return; 290 try { 291 synchronized (tempLocks) { 292 try { 293 tempLocks.lockWaitStop(thread, lock); 294 } catch (Exception e) { 295 throw createDebugException(tempLocks, e); 296 } 297 } 298 } catch (Exception e) { 299 handleInternalError(e); 300 } 301 } 302 303 /** 304 * Resumes all the locks that were suspended while this thread was waiting to acquire another lock. 305 */ resumeSuspendedLocks(Thread owner)306 void resumeSuspendedLocks(Thread owner) { 307 LockState[] toResume; 308 synchronized (suspendedLocks) { 309 ArrayDeque prevLocks = suspendedLocks.get(owner); 310 if (prevLocks == null) 311 return; 312 toResume = (LockState[]) prevLocks.pop(); 313 if (prevLocks.isEmpty()) 314 suspendedLocks.remove(owner); 315 } 316 for (LockState element : toResume) 317 element.resume(); 318 } 319 setLockListener(LockListener listener)320 public void setLockListener(LockListener listener) { 321 this.lockListener = listener; 322 } 323 } 324