SafetyMutex and rocket optimization updates
[debian/openrocket] / src / net / sf / openrocket / util / SafetyMutex.java
1 package net.sf.openrocket.util;
2
3 import java.util.LinkedList;
4
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;
9
10 /**
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.
15  * <p>
16  * This mutex is not reentrant even for the same thread that has locked it.
17  * 
18  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
19  */
20 public abstract class SafetyMutex {
21         private static final LogHelper log = Application.getLogger();
22         
23         
24         /**
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.
27          * 
28          * @return      a new instance of a safety mutex
29          */
30         public static SafetyMutex newInstance() {
31                 if (Prefs.useSafetyChecks()) {
32                         return new ConcreteSafetyMutex();
33                 } else {
34                         return new BogusSafetyMutex();
35                 }
36         }
37         
38         
39
40         /**
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).
44          * 
45          * @throws ConcurrencyException if this mutex is already locked.
46          */
47         public abstract void verify();
48         
49         
50         /**
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.
54          * 
55          * @param location      a string describing the location where this mutex was locked (cannot be null).
56          * 
57          * @throws ConcurrencyException         if this mutex is already locked.
58          */
59         public abstract void lock(String location);
60         
61         
62         /**
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.
66          * <p>
67          * This method is guaranteed never to throw an exception, so it can safely be used in finally blocks.
68          * 
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)
71          */
72         public abstract boolean unlock(String location);
73         
74         
75
76         /**
77          * Bogus implementation of a safety mutex (used when safety checking is not performed).
78          */
79         static class BogusSafetyMutex extends SafetyMutex {
80                 
81                 @Override
82                 public void verify() {
83                 }
84                 
85                 @Override
86                 public void lock(String location) {
87                 }
88                 
89                 @Override
90                 public boolean unlock(String location) {
91                         return true;
92                 }
93                 
94         }
95         
96         /**
97          * A concrete, working implementation of a safety mutex.
98          */
99         static class ConcreteSafetyMutex extends SafetyMutex {
100                 private static final boolean STORE_LOCKING_LOCATION = (System.getProperty("openrocket.debug.mutexlocation") != null);
101                 
102                 // Package-private for unit testing
103                 static volatile boolean errorReported = false;
104                 
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>();
111                 
112                 
113
114                 @Override
115                 public synchronized void verify() {
116                         checkState(true);
117                         if (lockingThread != null && lockingThread != Thread.currentThread()) {
118                                 error("Mutex is already locked", true);
119                         }
120                 }
121                 
122                 
123
124                 @Override
125                 public synchronized void lock(String location) {
126                         if (location == null) {
127                                 throw new IllegalArgumentException("location is null");
128                         }
129                         checkState(true);
130                         
131                         Thread currentThread = Thread.currentThread();
132                         if (lockingThread != null && lockingThread != currentThread) {
133                                 error("Mutex is already locked", true);
134                         }
135                         
136                         lockingThread = currentThread;
137                         if (STORE_LOCKING_LOCATION) {
138                                 lockingLocation = new TraceException("Location where mutex was locked '" + location + "'");
139                         }
140                         locations.push(location);
141                 }
142                 
143                 
144
145
146                 @Override
147                 public synchronized boolean unlock(String location) {
148                         try {
149                                 
150                                 if (location == null) {
151                                         ExceptionHandler.handleErrorCondition("location is null");
152                                         location = "";
153                                 }
154                                 checkState(false);
155                                 
156
157                                 // Check that the mutex is locked
158                                 if (lockingThread == null) {
159                                         error("Mutex was not locked", false);
160                                         return false;
161                                 }
162                                 
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);
166                                         return false;
167                                 }
168                                 
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);
174                                         return false;
175                                 }
176                                 
177                                 // Unlock the mutex if the last one
178                                 if (locations.isEmpty()) {
179                                         lockingThread = null;
180                                         lockingLocation = null;
181                                 }
182                                 return true;
183                         } catch (Exception e) {
184                                 ExceptionHandler.handleErrorCondition("An exception occurred while unlocking a mutex, " +
185                                                 "locking thread=" + lockingThread + " locations=" + locations, e);
186                                 return false;
187                         }
188                 }
189                 
190                 
191
192                 /**
193                  * Check that the internal state of the mutex (lockingThread vs. locations) is correct.
194                  */
195                 private void checkState(boolean throwException) {
196                         /*
197                          * Disallowed states:
198                          *   lockingThread == null  &&  !locations.isEmpty()
199                          *   lockingThread != null  &&  locations.isEmpty()
200                          */
201                         if ((lockingThread == null) ^ (locations.isEmpty())) {
202                                 // Clear the mutex only after error() has executed (and possibly thrown an exception)
203                                 try {
204                                         error("Mutex data inconsistency occurred - unlocking mutex", throwException);
205                                 } finally {
206                                         lockingThread = null;
207                                         lockingLocation = null;
208                                         locations.clear();
209                                 }
210                         }
211                 }
212                 
213                 
214                 /**
215                  * Raise an error.  The first occurrence is passed directly to the exception handler,
216                  * later errors are simply logged.
217                  */
218                 private void error(String message, boolean throwException) {
219                         message = message +
220                                         ", current thread = " + Thread.currentThread() +
221                                         ", locking thread=" + lockingThread +
222                                         ", locking locations=" + locations;
223                         
224                         ConcurrencyException ex = new ConcurrencyException(message, lockingLocation);
225                         
226                         if (!errorReported) {
227                                 errorReported = true;
228                                 ExceptionHandler.handleErrorCondition(ex);
229                         } else {
230                                 log.error(message, ex);
231                         }
232                         
233                         if (throwException) {
234                                 throw ex;
235                         }
236                 }
237         }
238 }