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 015package org.apache.tapestry5.ioc.internal.util; 016 017import org.apache.tapestry5.ioc.Invokable; 018 019import java.util.concurrent.TimeUnit; 020import java.util.concurrent.locks.ReadWriteLock; 021import 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 */ 028public 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 @Override 109 public Void invoke() 110 { 111 runnable.run(); 112 113 return null; 114 } 115 }; 116 117 withRead(invokable); 118 } 119 120 /** 121 * Acquires the exclusive write lock before invoking the Invokable. The code will be executed exclusively, no other 122 * reader or writer threads will exist (they will be blocked waiting for the lock). If the current thread has a read 123 * lock, it is released before attempting to acquire the write lock, and re-acquired after the write lock is 124 * released. Note that in that short window, between releasing the read lock and acquiring the write lock, it is 125 * entirely possible that some other thread will sneak in and do some work, so the {@link Invokable} object should 126 * be prepared for cases where the state has changed slightly, despite holding the read lock. This usually manifests 127 * as race conditions where either a) some parallel unrelated bit of work has occured or b) duplicate work has 128 * occured. The latter is only problematic if the operation is very expensive. 129 * 130 * @param <T> 131 * @param invokable 132 */ 133 public <T> T withWrite(Invokable<T> invokable) 134 { 135 boolean readLockedAtEntry = releaseReadLock(); 136 137 lock.writeLock().lock(); 138 139 try 140 { 141 return invokable.invoke(); 142 } 143 finally 144 { 145 lock.writeLock().unlock(); 146 restoreReadLock(readLockedAtEntry); 147 } 148 } 149 150 private boolean releaseReadLock() 151 { 152 boolean readLockedAtEntry; 153 154 synchronized (threadHasReadLock) 155 { 156 readLockedAtEntry = threadHasReadLock.get(); 157 } 158 159 if (readLockedAtEntry) 160 { 161 lock.readLock().unlock(); 162 163 synchronized (threadHasReadLock) 164 { 165 threadHasReadLock.set(false); 166 } 167 } 168 169 return readLockedAtEntry; 170 } 171 172 private void restoreReadLock(boolean readLockedAtEntry) 173 { 174 if (readLockedAtEntry) 175 { 176 lock.readLock().lock(); 177 178 synchronized (threadHasReadLock) 179 { 180 threadHasReadLock.set(true); 181 } 182 } 183 else 184 { 185 synchronized (threadHasReadLock) 186 { 187 threadHasReadLock.remove(); 188 } 189 } 190 } 191 192 /** 193 * As with {@link #withWrite(Invokable)}, creating an {@link Invokable} wrapper around the runnable object. 194 */ 195 public void withWrite(final Runnable runnable) 196 { 197 Invokable<Void> invokable = new Invokable<Void>() 198 { 199 @Override 200 public Void invoke() 201 { 202 runnable.run(); 203 204 return null; 205 } 206 }; 207 208 withWrite(invokable); 209 } 210 211 /** 212 * Try to aquire the exclusive write lock and invoke the Runnable. If the write lock is obtained within the specfied 213 * timeout, then this method behaves as {@link #withWrite(Runnable)} and will return true. If the write lock is not 214 * obtained within the timeout then the runnable is never invoked and the method will return false. 215 * 216 * @param runnable Runnable object to execute inside the write lock. 217 * @param timeout Time to wait for write lock. 218 * @param timeoutUnit Units of timeout. 219 * @return true if lock was obtained & runnabled executed. False otherwise. 220 */ 221 public boolean tryWithWrite(final Runnable runnable, long timeout, TimeUnit timeoutUnit) 222 { 223 boolean readLockedAtEntry = releaseReadLock(); 224 225 boolean obtainedLock = false; 226 227 try 228 { 229 try 230 { 231 obtainedLock = lock.writeLock().tryLock(timeout, timeoutUnit); 232 233 if (obtainedLock) runnable.run(); 234 235 } 236 catch (InterruptedException e) 237 { 238 obtainedLock = false; 239 } 240 finally 241 { 242 if (obtainedLock) lock.writeLock().unlock(); 243 } 244 } 245 finally 246 { 247 restoreReadLock(readLockedAtEntry); 248 } 249 250 return obtainedLock; 251 } 252 253}