001 // Copyright 2006, 2007, 2009 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry5.ioc.internal.util;
016
017 import org.apache.tapestry5.ioc.Invokable;
018
019 import java.util.concurrent.TimeUnit;
020 import java.util.concurrent.locks.ReadWriteLock;
021 import java.util.concurrent.locks.ReentrantReadWriteLock;
022
023 /**
024 * A barrier used to execute code in a context where it is guarded by read/write locks. In addition, handles upgrading
025 * read locks to write locks (and vice versa). Execution of code within a lock is in terms of a {@link Runnable} object
026 * (that returns no value), or a {@link Invokable} object (which does return a value).
027 */
028 public class ConcurrentBarrier
029 {
030 private final ReadWriteLock lock = new ReentrantReadWriteLock();
031
032 /**
033 * This is, of course, a bit of a problem. We don't have an avenue for ensuring that this ThreadLocal is destroyed
034 * at the end of the request, and that means a thread can hold a reference to the class and the class loader which
035 * loaded it. This may cause redeployment problems (leaked classes and class loaders). Apparently JDK 1.6 provides
036 * the APIs to check to see if the current thread has a read lock. So, we tend to remove the TL, rather than set its
037 * value to false.
038 */
039 private static class ThreadBoolean extends ThreadLocal<Boolean>
040 {
041 @Override
042 protected Boolean initialValue()
043 {
044 return false;
045 }
046 }
047
048 private final ThreadBoolean threadHasReadLock = new ThreadBoolean();
049
050 /**
051 * Invokes the object after acquiring the read lock (if necessary). If invoked when the read lock has not yet been
052 * acquired, then the lock is acquired for the duration of the call. If the lock has already been acquired, then the
053 * status of the lock is not changed.
054 * <p/>
055 * TODO: Check to see if the write lock is acquired and <em>not</em> acquire the read lock in that situation.
056 * Currently this code is not re-entrant. If a write lock is already acquired and the thread attempts to get the
057 * read lock, then the thread will hang. For the moment, all the uses of ConcurrentBarrier are coded in such a way
058 * that reentrant locks are not a problem.
059 *
060 * @param <T>
061 * @param invokable
062 * @return the result of invoking the invokable
063 */
064 public <T> T withRead(Invokable<T> invokable)
065 {
066 boolean readLockedAtEntry;
067
068 synchronized (threadHasReadLock)
069 {
070 readLockedAtEntry = threadHasReadLock.get();
071 }
072
073 if (!readLockedAtEntry)
074 {
075 lock.readLock().lock();
076
077 synchronized (threadHasReadLock)
078 {
079 threadHasReadLock.set(true);
080 }
081 }
082
083 try
084 {
085 return invokable.invoke();
086 }
087 finally
088 {
089 if (!readLockedAtEntry)
090 {
091 lock.readLock().unlock();
092
093 synchronized (threadHasReadLock)
094 {
095 threadHasReadLock.remove();
096 }
097 }
098 }
099 }
100
101 /**
102 * As with {@link #withRead(Invokable)}, creating an {@link Invokable} wrapper around the runnable object.
103 */
104 public void withRead(final Runnable runnable)
105 {
106 Invokable<Void> invokable = new Invokable<Void>()
107 {
108 public Void invoke()
109 {
110 runnable.run();
111
112 return null;
113 }
114 };
115
116 withRead(invokable);
117 }
118
119 /**
120 * Acquires the exclusive write lock before invoking the Invokable. The code will be executed exclusively, no other
121 * reader or writer threads will exist (they will be blocked waiting for the lock). If the current thread has a read
122 * lock, it is released before attempting to acquire the write lock, and re-acquired after the write lock is
123 * released. Note that in that short window, between releasing the read lock and acquiring the write lock, it is
124 * entirely possible that some other thread will sneak in and do some work, so the {@link Invokable} object should
125 * be prepared for cases where the state has changed slightly, despite holding the read lock. This usually manifests
126 * as race conditions where either a) some parallel unrelated bit of work has occured or b) duplicate work has
127 * occured. The latter is only problematic if the operation is very expensive.
128 *
129 * @param <T>
130 * @param invokable
131 */
132 public <T> T withWrite(Invokable<T> invokable)
133 {
134 boolean readLockedAtEntry = releaseReadLock();
135
136 lock.writeLock().lock();
137
138 try
139 {
140 return invokable.invoke();
141 }
142 finally
143 {
144 lock.writeLock().unlock();
145 restoreReadLock(readLockedAtEntry);
146 }
147 }
148
149 private boolean releaseReadLock()
150 {
151 boolean readLockedAtEntry;
152
153 synchronized (threadHasReadLock)
154 {
155 readLockedAtEntry = threadHasReadLock.get();
156 }
157
158 if (readLockedAtEntry)
159 {
160 lock.readLock().unlock();
161
162 synchronized (threadHasReadLock)
163 {
164 threadHasReadLock.set(false);
165 }
166 }
167
168 return readLockedAtEntry;
169 }
170
171 private void restoreReadLock(boolean readLockedAtEntry)
172 {
173 if (readLockedAtEntry)
174 {
175 lock.readLock().lock();
176
177 synchronized (threadHasReadLock)
178 {
179 threadHasReadLock.set(true);
180 }
181 }
182 else
183 {
184 synchronized (threadHasReadLock)
185 {
186 threadHasReadLock.remove();
187 }
188 }
189 }
190
191 /**
192 * As with {@link #withWrite(Invokable)}, creating an {@link Invokable} wrapper around the runnable object.
193 */
194 public void withWrite(final Runnable runnable)
195 {
196 Invokable<Void> invokable = new Invokable<Void>()
197 {
198 public Void invoke()
199 {
200 runnable.run();
201
202 return null;
203 }
204 };
205
206 withWrite(invokable);
207 }
208
209 /**
210 * Try to aquire the exclusive write lock and invoke the Runnable. If the write lock is obtained within the specfied
211 * timeout, then this method behaves as {@link #withWrite(Runnable)} and will return true. If the write lock is not
212 * obtained within the timeout then the runnable is never invoked and the method will return false.
213 *
214 * @param runnable Runnable object to execute inside the write lock.
215 * @param timeout Time to wait for write lock.
216 * @param timeoutUnit Units of timeout.
217 * @return true if lock was obtained & runnabled executed. False otherwise.
218 */
219 public boolean tryWithWrite(final Runnable runnable, long timeout, TimeUnit timeoutUnit)
220 {
221 boolean readLockedAtEntry = releaseReadLock();
222
223 boolean obtainedLock = false;
224
225 try
226 {
227 try
228 {
229 obtainedLock = lock.writeLock().tryLock(timeout, timeoutUnit);
230
231 if (obtainedLock) runnable.run();
232
233 }
234 catch (InterruptedException e)
235 {
236 obtainedLock = false;
237 }
238 finally
239 {
240 if (obtainedLock) lock.writeLock().unlock();
241 }
242 }
243 finally
244 {
245 restoreReadLock(readLockedAtEntry);
246 }
247
248 return obtainedLock;
249 }
250
251 }