    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    }