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