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 }