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    }