1 package net.sf.openrocket.util;
3 import java.util.LinkedList;
5 import net.sf.openrocket.logging.LogHelper;
6 import net.sf.openrocket.logging.TraceException;
7 import net.sf.openrocket.startup.Application;
10 * A mutex that can be used for verifying thread safety. This class cannot be
11 * used to perform synchronization, only to detect concurrency issues. This
12 * class can be used by the main methods of non-thread-safe classes to ensure
13 * the class is not wrongly used from multiple threads concurrently.
15 * This mutex is not reentrant even for the same thread that has locked it.
17 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
19 public abstract class SafetyMutex {
20 private static final boolean USE_CHECKS = Application.useSafetyChecks();
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() {
32 return new ConcreteSafetyMutex();
34 return new BogusSafetyMutex();
40 * Verify that this mutex is unlocked, but don't lock it. This has the same effect
41 * as <code>mutex.lock(); mutex.unlock();</code> and is useful for methods that return
42 * immediately (e.g. getters).
44 * @throws ConcurrencyException if this mutex is already locked.
46 public abstract void verify();
50 * Lock this mutex. If this mutex is already locked an error is raised and
51 * a ConcurrencyException is thrown. The location parameter is used to distinguish
52 * the locking location, and it should be e.g. the method name.
54 * @param location a string describing the location where this mutex was locked (cannot be null).
56 * @throws ConcurrencyException if this mutex is already locked.
58 public abstract void lock(String location);
62 * Unlock this mutex. If this mutex is not locked at the position of the parameter
63 * or was locked by another thread than the current thread an error is raised,
64 * but an exception is not thrown.
66 * This method is guaranteed never to throw an exception, so it can safely be used in finally blocks.
68 * @param location a location string matching that which locked the mutex
69 * @return whether the unlocking was successful (this normally doesn't need to be checked)
71 public abstract boolean unlock(String location);
76 * Bogus implementation of a safety mutex (used when safety checking is not performed).
78 static class BogusSafetyMutex extends SafetyMutex {
81 public void verify() {
85 public void lock(String location) {
89 public boolean unlock(String location) {
96 * A concrete, working implementation of a safety mutex.
98 static class ConcreteSafetyMutex extends SafetyMutex {
99 private static final boolean STORE_LOCKING_LOCATION = (System.getProperty("openrocket.debug.mutexlocation") != null);
101 // Package-private for unit testing
102 static volatile boolean errorReported = false;
104 // lockingThread is set when this mutex is locked.
105 Thread lockingThread = null;
106 // longingLocation is set when lockingThread is, if STORE_LOCKING_LOCATION is true
107 TraceException lockingLocation = null;
108 // Stack of places that have locked this mutex
109 final LinkedList<String> locations = new LinkedList<String>();
114 public synchronized void verify() {
116 if (lockingThread != null && lockingThread != Thread.currentThread()) {
117 error("Mutex is already locked", true);
124 public synchronized void lock(String location) {
125 if (location == null) {
126 throw new IllegalArgumentException("location is null");
130 Thread currentThread = Thread.currentThread();
131 if (lockingThread != null && lockingThread != currentThread) {
132 error("Mutex is already locked", true);
135 lockingThread = currentThread;
136 if (STORE_LOCKING_LOCATION) {
137 lockingLocation = new TraceException("Location where mutex was locked '" + location + "'");
139 locations.push(location);
146 public synchronized boolean unlock(String location) {
149 if (location == null) {
150 Application.getExceptionHandler().handleErrorCondition("location is null");
156 // Check that the mutex is locked
157 if (lockingThread == null) {
158 error("Mutex was not locked", false);
162 // Check that the mutex is locked by the current thread
163 if (lockingThread != Thread.currentThread()) {
164 error("Mutex is being unlocked from differerent thread than where it was locked", false);
168 // Check that the unlock location is correct
169 String lastLocation = locations.pop();
170 if (!location.equals(lastLocation)) {
171 locations.push(lastLocation);
172 error("Mutex unlocking location does not match locking location, location=" + location, false);
176 // Unlock the mutex if the last one
177 if (locations.isEmpty()) {
178 lockingThread = null;
179 lockingLocation = null;
182 } catch (Exception e) {
183 Application.getExceptionHandler().handleErrorCondition("An exception occurred while unlocking a mutex, " +
184 "locking thread=" + lockingThread + " locations=" + locations, e);
192 * Check that the internal state of the mutex (lockingThread vs. locations) is correct.
194 private void checkState(boolean throwException) {
197 * lockingThread == null && !locations.isEmpty()
198 * lockingThread != null && locations.isEmpty()
200 if ((lockingThread == null) ^ (locations.isEmpty())) {
201 // Clear the mutex only after error() has executed (and possibly thrown an exception)
203 error("Mutex data inconsistency occurred - unlocking mutex", throwException);
205 lockingThread = null;
206 lockingLocation = null;
214 * Raise an error. The first occurrence is passed directly to the exception handler,
215 * later errors are simply logged.
217 private void error(String message, boolean throwException) {
219 ", current thread = " + Thread.currentThread() +
220 ", locking thread=" + lockingThread +
221 ", locking locations=" + locations;
223 ConcurrencyException ex = new ConcurrencyException(message, lockingLocation);
225 if (!errorReported) {
226 errorReported = true;
227 Application.getExceptionHandler().handleErrorCondition(ex);
229 log.error(message, ex);
232 if (throwException) {