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.startup.Application;
8
9 /**
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.
14  * <p>
15  * This mutex is not reentrant even for the same thread that has locked it.
16  * 
17  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
18  */
19 public class SafetyMutex {
20         private static final LogHelper log = Application.getLogger();
21         
22         // Package-private for unit testing
23         static volatile boolean errorReported = false;
24         
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>();
29         
30         
31         /**
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).
35          * 
36          * @throws ConcurrencyException if this mutex is already locked.
37          */
38         public synchronized void verify() {
39                 checkState(true);
40                 if (lockingThread != null && lockingThread != Thread.currentThread()) {
41                         error("Mutex is already locked", true);
42                 }
43         }
44         
45         
46         /**
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.
50          * 
51          * @param location      a string describing the location where this mutex was locked (cannot be null).
52          * 
53          * @throws ConcurrencyException         if this mutex is already locked.
54          */
55         public synchronized void lock(String location) {
56                 if (location == null) {
57                         throw new IllegalArgumentException("location is null");
58                 }
59                 checkState(true);
60                 
61                 Thread currentThread = Thread.currentThread();
62                 if (lockingThread != null && lockingThread != currentThread) {
63                         error("Mutex is already locked", true);
64                 }
65                 
66                 lockingThread = currentThread;
67                 locations.push(location);
68         }
69         
70         
71
72         /**
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.
76          * <p>
77          * This method is guaranteed never to throw an exception, so it can safely be used in finally blocks.
78          * 
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)
81          */
82         public synchronized boolean unlock(String location) {
83                 try {
84                         
85                         if (location == null) {
86                                 ExceptionHandler.handleErrorCondition("location is null");
87                                 location = "";
88                         }
89                         checkState(false);
90                         
91
92                         // Check that the mutex is locked
93                         if (lockingThread == null) {
94                                 error("Mutex was not locked", false);
95                                 return false;
96                         }
97                         
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);
101                                 return false;
102                         }
103                         
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);
109                                 return false;
110                         }
111                         
112                         // Unlock the mutex if the last one
113                         if (locations.isEmpty()) {
114                                 lockingThread = null;
115                         }
116                         return true;
117                 } catch (Exception e) {
118                         ExceptionHandler.handleErrorCondition("An exception occurred while unlocking a mutex, " +
119                                         "locking thread=" + lockingThread + " locations=" + locations, e);
120                         return false;
121                 }
122         }
123         
124         
125
126         /**
127          * Check that the internal state of the mutex (lockingThread vs. locations) is correct.
128          */
129         private void checkState(boolean throwException) {
130                 /*
131                  * Disallowed states:
132                  *   lockingThread == null  &&  !locations.isEmpty()
133                  *   lockingThread != null  &&  locations.isEmpty()
134                  */
135                 if ((lockingThread == null) ^ (locations.isEmpty())) {
136                         // Clear the mutex only after error() has executed (and possibly thrown an exception)
137                         try {
138                                 error("Mutex data inconsistency occurred - unlocking mutex", throwException);
139                         } finally {
140                                 lockingThread = null;
141                                 locations.clear();
142                         }
143                 }
144         }
145         
146         
147         /**
148          * Raise an error.  The first occurrence is passed directly to the exception handler,
149          * later errors are simply logged.
150          */
151         private void error(String message, boolean throwException) {
152                 message = message +
153                                 ", current thread = " + Thread.currentThread() +
154                                 ", locking thread=" + lockingThread +
155                                 ", locking locations=" + locations;
156                 
157                 ConcurrencyException ex = new ConcurrencyException(message);
158                 
159                 if (!errorReported) {
160                         errorReported = true;
161                         ExceptionHandler.handleErrorCondition(ex);
162                 } else {
163                         log.error(message, ex);
164                 }
165                 
166                 if (throwException) {
167                         throw ex;
168                 }
169         }
170         
171 }