Home | History | Annotate | Download | only in utils
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.hardware.camera2.utils;
     18 
     19 import android.util.Log;
     20 
     21 import java.util.concurrent.locks.Condition;
     22 import java.util.concurrent.locks.ReentrantLock;
     23 
     24 /**
     25  * Implement a shared/exclusive lock that can be closed.
     26  *
     27  * <p>A shared lock can be acquired if any other shared locks are also acquired. An
     28  * exclusive lock acquire will block until all shared locks have been released.</p>
     29  *
     30  * <p>Locks are re-entrant; trying to acquire another lock (of the same type)
     31  * while a lock is already held will immediately succeed.</p>
     32  *
     33  * <p>Acquiring to acquire a shared lock while holding an exclusive lock or vice versa is not
     34  * supported; attempting it will throw an {@link IllegalStateException}.</p>
     35  *
     36  * <p>If the lock is closed, all future and current acquires will immediately return {@code null}.
     37  * </p>
     38  */
     39 public class CloseableLock implements AutoCloseable {
     40 
     41     private static final boolean VERBOSE = false;
     42 
     43     private final String TAG = "CloseableLock";
     44     private final String mName;
     45 
     46     private volatile boolean mClosed = false;
     47 
     48     /** If an exclusive lock is acquired by some thread. */
     49     private boolean mExclusive = false;
     50     /**
     51      * How many shared locks are acquired by any thread:
     52      *
     53      * <p>Reentrant locking increments this. If an exclusive lock is held,
     54      * this value will stay at 0.</p>
     55      */
     56     private int mSharedLocks = 0;
     57 
     58     private final ReentrantLock mLock = new ReentrantLock();
     59     /** This condition automatically releases mLock when waiting; re-acquiring it after notify */
     60     private final Condition mCondition = mLock.newCondition();
     61 
     62     /** How many times the current thread is holding the lock */
     63     private final ThreadLocal<Integer> mLockCount =
     64         new ThreadLocal<Integer>() {
     65             @Override protected Integer initialValue() {
     66                 return 0;
     67             }
     68         };
     69 
     70     /**
     71      * Helper class to release a lock at the end of a try-with-resources statement.
     72      */
     73     public class ScopedLock implements AutoCloseable {
     74         private ScopedLock() {}
     75 
     76         /** Release the lock with {@link CloseableLock#releaseLock}. */
     77         @Override
     78         public void close() {
     79             releaseLock();
     80         }
     81     }
     82 
     83     /**
     84      * Create a new instance; starts out with 0 locks acquired.
     85      */
     86     public CloseableLock() {
     87         mName = "";
     88     }
     89 
     90     /**
     91      * Create a new instance; starts out with 0 locks acquired.
     92      *
     93      * @param name set an optional name for logging functionality
     94      */
     95     public CloseableLock(String name) {
     96         mName = name;
     97     }
     98 
     99     /**
    100      * Acquires the lock exclusively (blocking), marks it as closed, then releases the lock.
    101      *
    102      * <p>Marking a lock as closed will fail all further acquisition attempts;
    103      * it will also immediately unblock all other threads currently trying to acquire a lock.</p>
    104      *
    105      * <p>This operation is idempotent; calling it more than once has no effect.</p>
    106      *
    107      * @throws IllegalStateException
    108      *          if an attempt is made to {@code close} while this thread has a lock acquired
    109      */
    110     @Override
    111     public void close() {
    112         if (mClosed) {
    113             if (VERBOSE) {
    114                 log("close - already closed; ignoring");
    115             }
    116             return;
    117         }
    118 
    119         ScopedLock scoper = acquireExclusiveLock();
    120         // Already closed by another thread?
    121         if (scoper == null) {
    122             return;
    123         } else if (mLockCount.get() != 1) {
    124             // Future: may want to add a #releaseAndClose to allow this.
    125             throw new IllegalStateException(
    126                     "Cannot close while one or more acquired locks are being held by this " +
    127                      "thread; release all other locks first");
    128         }
    129 
    130         try {
    131             mLock.lock();
    132 
    133             mClosed = true;
    134             mExclusive = false;
    135             mSharedLocks = 0;
    136             mLockCount.remove();
    137 
    138             // Notify all threads that are waiting to unblock and return immediately
    139             mCondition.signalAll();
    140         } finally {
    141             mLock.unlock();
    142         }
    143 
    144         if (VERBOSE) {
    145             log("close - completed");
    146         }
    147     }
    148 
    149     /**
    150      * Try to acquire the lock non-exclusively, blocking until the operation completes.
    151      *
    152      * <p>If the lock has already been closed, or being closed before this operation returns,
    153      * the call will immediately return {@code false}.</p>
    154      *
    155      * <p>If other threads hold a non-exclusive lock (and the lock is not yet closed),
    156      * this operation will return immediately. If another thread holds an exclusive lock,
    157      * this thread will block until the exclusive lock has been released.</p>
    158      *
    159      * <p>This lock is re-entrant; acquiring more than one non-exclusive lock per thread is
    160      * supported, and must be matched by an equal number of {@link #releaseLock} calls.</p>
    161      *
    162      * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock
    163      *         was already closed.
    164      *
    165      * @throws IllegalStateException if this thread is already holding an exclusive lock
    166      */
    167     public ScopedLock acquireLock() {
    168 
    169         int ownedLocks;
    170 
    171         try {
    172             mLock.lock();
    173 
    174             // Lock is already closed, all further acquisitions will fail
    175             if (mClosed) {
    176                 if (VERBOSE) {
    177                     log("acquire lock early aborted (already closed)");
    178                 }
    179                 return null;
    180             }
    181 
    182             ownedLocks = mLockCount.get();
    183 
    184             // This thread is already holding an exclusive lock
    185             if (mExclusive && ownedLocks > 0) {
    186                 throw new IllegalStateException(
    187                         "Cannot acquire shared lock while holding exclusive lock");
    188             }
    189 
    190             // Is another thread holding the exclusive lock? Block until we can get in.
    191             while (mExclusive) {
    192                 mCondition.awaitUninterruptibly();
    193 
    194                 // Did another thread #close while we were waiting? Unblock immediately.
    195                 if (mClosed) {
    196                     if (VERBOSE) {
    197                         log("acquire lock unblocked aborted (already closed)");
    198                     }
    199                     return null;
    200                 }
    201             }
    202 
    203             mSharedLocks++;
    204 
    205             ownedLocks = mLockCount.get() + 1;
    206             mLockCount.set(ownedLocks);
    207         } finally {
    208             mLock.unlock();
    209         }
    210 
    211         if (VERBOSE) {
    212             log("acquired lock (local own count = " + ownedLocks + ")");
    213         }
    214         return new ScopedLock();
    215     }
    216 
    217     /**
    218      * Try to acquire the lock exclusively, blocking until all other threads release their locks.
    219      *
    220      * <p>If the lock has already been closed, or being closed before this operation returns,
    221      * the call will immediately return {@code false}.</p>
    222      *
    223      * <p>If any other threads are holding a lock, this thread will block until all
    224      * other locks are released.</p>
    225      *
    226      * <p>This lock is re-entrant; acquiring more than one exclusive lock per thread is supported,
    227      * and must be matched by an equal number of {@link #releaseLock} calls.</p>
    228      *
    229      * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock
    230      *         was already closed.
    231      *
    232      * @throws IllegalStateException
    233      *          if an attempt is made to acquire an exclusive lock while already holding a lock
    234      */
    235     public ScopedLock acquireExclusiveLock() {
    236 
    237         int ownedLocks;
    238 
    239         try {
    240             mLock.lock();
    241 
    242             // Lock is already closed, all further acquisitions will fail
    243             if (mClosed) {
    244                 if (VERBOSE) {
    245                     log("acquire exclusive lock early aborted (already closed)");
    246                 }
    247                 return null;
    248             }
    249 
    250             ownedLocks = mLockCount.get();
    251 
    252             // This thread is already holding a shared lock
    253             if (!mExclusive && ownedLocks > 0) {
    254                 throw new IllegalStateException(
    255                         "Cannot acquire exclusive lock while holding shared lock");
    256             }
    257 
    258             /*
    259              * Is another thread holding the lock? Block until we can get in.
    260              *
    261              * If we are already holding the lock, always let it through since
    262              * we are just reentering the exclusive lock.
    263              */
    264             while (ownedLocks == 0 && (mExclusive || mSharedLocks > 0)) {
    265                 mCondition.awaitUninterruptibly();
    266 
    267              // Did another thread #close while we were waiting? Unblock immediately.
    268                 if (mClosed) {
    269                     if (VERBOSE) {
    270                         log("acquire exclusive lock unblocked aborted (already closed)");
    271                     }
    272                     return null;
    273                 }
    274             }
    275 
    276             mExclusive = true;
    277 
    278             ownedLocks = mLockCount.get() + 1;
    279             mLockCount.set(ownedLocks);
    280         } finally {
    281             mLock.unlock();
    282         }
    283 
    284         if (VERBOSE) {
    285             log("acquired exclusive lock (local own count = " + ownedLocks + ")");
    286         }
    287         return new ScopedLock();
    288     }
    289 
    290     /**
    291      * Release a single lock that was acquired.
    292      *
    293      * <p>Any other other that is blocked and trying to acquire a lock will get a chance
    294      * to acquire the lock.</p>
    295      *
    296      * @throws IllegalStateException if no locks were acquired, or if the lock was already closed
    297      */
    298     public void releaseLock() {
    299         if (mLockCount.get() <= 0) {
    300             throw new IllegalStateException(
    301                     "Cannot release lock that was not acquired by this thread");
    302         }
    303 
    304         int ownedLocks;
    305 
    306         try {
    307             mLock.lock();
    308 
    309             // Lock is already closed, it couldn't have been acquired in the first place
    310             if (mClosed) {
    311                 throw new IllegalStateException("Do not release after the lock has been closed");
    312             }
    313 
    314             if (!mExclusive) {
    315                 mSharedLocks--;
    316             } else {
    317                 if (mSharedLocks != 0) {
    318                     throw new AssertionError("Too many shared locks " + mSharedLocks);
    319                 }
    320             }
    321 
    322             ownedLocks = mLockCount.get() - 1;
    323             mLockCount.set(ownedLocks);
    324 
    325             if (ownedLocks == 0 && mExclusive) {
    326                 // Wake up any threads that might be waiting for the exclusive lock to be released
    327                 mExclusive = false;
    328                 mCondition.signalAll();
    329             } else if (ownedLocks == 0 && mSharedLocks == 0) {
    330                 // Wake up any threads that might be trying to get the exclusive lock
    331                 mCondition.signalAll();
    332             }
    333         } finally {
    334             mLock.unlock();
    335         }
    336 
    337         if (VERBOSE) {
    338              log("released lock (local lock count " + ownedLocks + ")");
    339         }
    340     }
    341 
    342     private void log(String what) {
    343         Log.v(TAG + "[" + mName + "]", what);
    344     }
    345 
    346 }
    347