    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}