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.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 class SafetyMutex {
20 private static final LogHelper log = Application.getLogger();
22 // Package-private for unit testing
23 static volatile boolean errorReported = false;
25 // lockingThread is set when this mutex is locked.
26 Thread lockingThread = null;
27 // Stack of places that have locked this mutex
28 final LinkedList<String> locations = new LinkedList<String>();
32 * Verify that this mutex is unlocked, but don't lock it. This has the same effect
33 * as <code>mutex.lock(); mutex.unlock();</code> and is useful for methods that return
34 * immediately (e.g. getters).
36 * @throws ConcurrencyException if this mutex is already locked.
38 public synchronized void verify() {
40 if (lockingThread != null && lockingThread != Thread.currentThread()) {
41 error("Mutex is already locked", true);
47 * Lock this mutex. If this mutex is already locked an error is raised and
48 * a ConcurrencyException is thrown. The location parameter is used to distinguish
49 * the locking location, and it should be e.g. the method name.
51 * @param location a string describing the location where this mutex was locked (cannot be null).
53 * @throws ConcurrencyException if this mutex is already locked.
55 public synchronized void lock(String location) {
56 if (location == null) {
57 throw new IllegalArgumentException("location is null");
61 Thread currentThread = Thread.currentThread();
62 if (lockingThread != null && lockingThread != currentThread) {
63 error("Mutex is already locked", true);
66 lockingThread = currentThread;
67 locations.push(location);
73 * Unlock this mutex. If this mutex is not locked at the position of the parameter
74 * or was locked by another thread than the current thread an error is raised,
75 * but an exception is not thrown.
77 * This method is guaranteed never to throw an exception, so it can safely be used in finally blocks.
79 * @param location a location string matching that which locked the mutex
80 * @return whether the unlocking was successful (this normally doesn't need to be checked)
82 public synchronized boolean unlock(String location) {
85 if (location == null) {
86 ExceptionHandler.handleErrorCondition("location is null");
92 // Check that the mutex is locked
93 if (lockingThread == null) {
94 error("Mutex was not locked", false);
98 // Check that the mutex is locked by the current thread
99 if (lockingThread != Thread.currentThread()) {
100 error("Mutex is being unlocked from differerent thread than where it was locked", false);
104 // Check that the unlock location is correct
105 String lastLocation = locations.pop();
106 if (!location.equals(lastLocation)) {
107 locations.push(lastLocation);
108 error("Mutex unlocking location does not match locking location, location=" + location, false);
112 // Unlock the mutex if the last one
113 if (locations.isEmpty()) {
114 lockingThread = null;
117 } catch (Exception e) {
118 ExceptionHandler.handleErrorCondition("An exception occurred while unlocking a mutex, " +
119 "locking thread=" + lockingThread + " locations=" + locations, e);
127 * Check that the internal state of the mutex (lockingThread vs. locations) is correct.
129 private void checkState(boolean throwException) {
132 * lockingThread == null && !locations.isEmpty()
133 * lockingThread != null && locations.isEmpty()
135 if ((lockingThread == null) ^ (locations.isEmpty())) {
136 // Clear the mutex only after error() has executed (and possibly thrown an exception)
138 error("Mutex data inconsistency occurred - unlocking mutex", throwException);
140 lockingThread = null;
148 * Raise an error. The first occurrence is passed directly to the exception handler,
149 * later errors are simply logged.
151 private void error(String message, boolean throwException) {
153 ", current thread = " + Thread.currentThread() +
154 ", locking thread=" + lockingThread +
155 ", locking locations=" + locations;
157 ConcurrencyException ex = new ConcurrencyException(message);
159 if (!errorReported) {
160 errorReported = true;
161 ExceptionHandler.handleErrorCondition(ex);
163 log.error(message, ex);
166 if (throwException) {