1 package net.sf.openrocket.util;
3 import java.util.LinkedList;
5 import net.sf.openrocket.gui.main.ExceptionHandler;
6 import net.sf.openrocket.logging.LogHelper;
7 import net.sf.openrocket.logging.TraceException;
8 import net.sf.openrocket.startup.Application;
11 * A mutex that can be used for verifying thread safety. This class cannot be
12 * used to perform synchronization, only to detect concurrency issues. This
13 * class can be used by the main methods of non-thread-safe classes to ensure
14 * the class is not wrongly used from multiple threads concurrently.
16 * This mutex is not reentrant even for the same thread that has locked it.
18 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
20 public abstract class SafetyMutex {
21 private static final LogHelper log = Application.getLogger();
25 * Return a new instance of a safety mutex. This returns an actual implementation
26 * or a bogus implementation depending on whether safety checks are enabled or disabled.
28 * @return a new instance of a safety mutex
30 public static SafetyMutex newInstance() {
31 if (Prefs.useSafetyChecks()) {
32 return new ConcreteSafetyMutex();
34 return new BogusSafetyMutex();
41 * Verify that this mutex is unlocked, but don't lock it. This has the same effect
42 * as <code>mutex.lock(); mutex.unlock();</code> and is useful for methods that return
43 * immediately (e.g. getters).
45 * @throws ConcurrencyException if this mutex is already locked.
47 public abstract void verify();
51 * Lock this mutex. If this mutex is already locked an error is raised and
52 * a ConcurrencyException is thrown. The location parameter is used to distinguish
53 * the locking location, and it should be e.g. the method name.
55 * @param location a string describing the location where this mutex was locked (cannot be null).
57 * @throws ConcurrencyException if this mutex is already locked.
59 public abstract void lock(String location);
63 * Unlock this mutex. If this mutex is not locked at the position of the parameter
64 * or was locked by another thread than the current thread an error is raised,
65 * but an exception is not thrown.
67 * This method is guaranteed never to throw an exception, so it can safely be used in finally blocks.
69 * @param location a location string matching that which locked the mutex
70 * @return whether the unlocking was successful (this normally doesn't need to be checked)
72 public abstract boolean unlock(String location);
77 * Bogus implementation of a safety mutex (used when safety checking is not performed).
79 static class BogusSafetyMutex extends SafetyMutex {
82 public void verify() {
86 public void lock(String location) {
90 public boolean unlock(String location) {
97 * A concrete, working implementation of a safety mutex.
99 static class ConcreteSafetyMutex extends SafetyMutex {
100 private static final boolean STORE_LOCKING_LOCATION = (System.getProperty("openrocket.debug.mutexlocation") != null);
102 // Package-private for unit testing
103 static volatile boolean errorReported = false;
105 // lockingThread is set when this mutex is locked.
106 Thread lockingThread = null;
107 // longingLocation is set when lockingThread is, if STORE_LOCKING_LOCATION is true
108 TraceException lockingLocation = null;
109 // Stack of places that have locked this mutex
110 final LinkedList<String> locations = new LinkedList<String>();
115 public synchronized void verify() {
117 if (lockingThread != null && lockingThread != Thread.currentThread()) {
118 error("Mutex is already locked", true);
125 public synchronized void lock(String location) {
126 if (location == null) {
127 throw new IllegalArgumentException("location is null");
131 Thread currentThread = Thread.currentThread();
132 if (lockingThread != null && lockingThread != currentThread) {
133 error("Mutex is already locked", true);
136 lockingThread = currentThread;
137 if (STORE_LOCKING_LOCATION) {
138 lockingLocation = new TraceException("Location where mutex was locked '" + location + "'");
140 locations.push(location);
147 public synchronized boolean unlock(String location) {
150 if (location == null) {
151 ExceptionHandler.handleErrorCondition("location is null");
157 // Check that the mutex is locked
158 if (lockingThread == null) {
159 error("Mutex was not locked", false);
163 // Check that the mutex is locked by the current thread
164 if (lockingThread != Thread.currentThread()) {
165 error("Mutex is being unlocked from differerent thread than where it was locked", false);
169 // Check that the unlock location is correct
170 String lastLocation = locations.pop();
171 if (!location.equals(lastLocation)) {
172 locations.push(lastLocation);
173 error("Mutex unlocking location does not match locking location, location=" + location, false);
177 // Unlock the mutex if the last one
178 if (locations.isEmpty()) {
179 lockingThread = null;
180 lockingLocation = null;
183 } catch (Exception e) {
184 ExceptionHandler.handleErrorCondition("An exception occurred while unlocking a mutex, " +
185 "locking thread=" + lockingThread + " locations=" + locations, e);
193 * Check that the internal state of the mutex (lockingThread vs. locations) is correct.
195 private void checkState(boolean throwException) {
198 * lockingThread == null && !locations.isEmpty()
199 * lockingThread != null && locations.isEmpty()
201 if ((lockingThread == null) ^ (locations.isEmpty())) {
202 // Clear the mutex only after error() has executed (and possibly thrown an exception)
204 error("Mutex data inconsistency occurred - unlocking mutex", throwException);
206 lockingThread = null;
207 lockingLocation = null;
215 * Raise an error. The first occurrence is passed directly to the exception handler,
216 * later errors are simply logged.
218 private void error(String message, boolean throwException) {
220 ", current thread = " + Thread.currentThread() +
221 ", locking thread=" + lockingThread +
222 ", locking locations=" + locations;
224 ConcurrencyException ex = new ConcurrencyException(message, lockingLocation);
226 if (!errorReported) {
227 errorReported = true;
228 ExceptionHandler.handleErrorCondition(ex);
230 log.error(message, ex);
233 if (throwException) {