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