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 17package android.hardware.camera2.utils; 18 19import android.util.Log; 20 21import java.util.concurrent.locks.Condition; 22import 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 */ 39public 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