/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.hardware.camera2.utils; import android.util.Log; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * Implement a shared/exclusive lock that can be closed. * *

A shared lock can be acquired if any other shared locks are also acquired. An * exclusive lock acquire will block until all shared locks have been released.

* *

Locks are re-entrant; trying to acquire another lock (of the same type) * while a lock is already held will immediately succeed.

* *

Acquiring to acquire a shared lock while holding an exclusive lock or vice versa is not * supported; attempting it will throw an {@link IllegalStateException}.

* *

If the lock is closed, all future and current acquires will immediately return {@code null}. *

*/ public class CloseableLock implements AutoCloseable { private static final boolean VERBOSE = false; private final String TAG = "CloseableLock"; private final String mName; private volatile boolean mClosed = false; /** If an exclusive lock is acquired by some thread. */ private boolean mExclusive = false; /** * How many shared locks are acquired by any thread: * *

Reentrant locking increments this. If an exclusive lock is held, * this value will stay at 0.

*/ private int mSharedLocks = 0; private final ReentrantLock mLock = new ReentrantLock(); /** This condition automatically releases mLock when waiting; re-acquiring it after notify */ private final Condition mCondition = mLock.newCondition(); /** How many times the current thread is holding the lock */ private final ThreadLocal mLockCount = new ThreadLocal() { @Override protected Integer initialValue() { return 0; } }; /** * Helper class to release a lock at the end of a try-with-resources statement. */ public class ScopedLock implements AutoCloseable { private ScopedLock() {} /** Release the lock with {@link CloseableLock#releaseLock}. */ @Override public void close() { releaseLock(); } } /** * Create a new instance; starts out with 0 locks acquired. */ public CloseableLock() { mName = ""; } /** * Create a new instance; starts out with 0 locks acquired. * * @param name set an optional name for logging functionality */ public CloseableLock(String name) { mName = name; } /** * Acquires the lock exclusively (blocking), marks it as closed, then releases the lock. * *

Marking a lock as closed will fail all further acquisition attempts; * it will also immediately unblock all other threads currently trying to acquire a lock.

* *

This operation is idempotent; calling it more than once has no effect.

* * @throws IllegalStateException * if an attempt is made to {@code close} while this thread has a lock acquired */ @Override public void close() { if (mClosed) { if (VERBOSE) { log("close - already closed; ignoring"); } return; } ScopedLock scoper = acquireExclusiveLock(); // Already closed by another thread? if (scoper == null) { return; } else if (mLockCount.get() != 1) { // Future: may want to add a #releaseAndClose to allow this. throw new IllegalStateException( "Cannot close while one or more acquired locks are being held by this " + "thread; release all other locks first"); } try { mLock.lock(); mClosed = true; mExclusive = false; mSharedLocks = 0; mLockCount.remove(); // Notify all threads that are waiting to unblock and return immediately mCondition.signalAll(); } finally { mLock.unlock(); } if (VERBOSE) { log("close - completed"); } } /** * Try to acquire the lock non-exclusively, blocking until the operation completes. * *

If the lock has already been closed, or being closed before this operation returns, * the call will immediately return {@code false}.

* *

If other threads hold a non-exclusive lock (and the lock is not yet closed), * this operation will return immediately. If another thread holds an exclusive lock, * this thread will block until the exclusive lock has been released.

* *

This lock is re-entrant; acquiring more than one non-exclusive lock per thread is * supported, and must be matched by an equal number of {@link #releaseLock} calls.

* * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock * was already closed. * * @throws IllegalStateException if this thread is already holding an exclusive lock */ public ScopedLock acquireLock() { int ownedLocks; try { mLock.lock(); // Lock is already closed, all further acquisitions will fail if (mClosed) { if (VERBOSE) { log("acquire lock early aborted (already closed)"); } return null; } ownedLocks = mLockCount.get(); // This thread is already holding an exclusive lock if (mExclusive && ownedLocks > 0) { throw new IllegalStateException( "Cannot acquire shared lock while holding exclusive lock"); } // Is another thread holding the exclusive lock? Block until we can get in. while (mExclusive) { mCondition.awaitUninterruptibly(); // Did another thread #close while we were waiting? Unblock immediately. if (mClosed) { if (VERBOSE) { log("acquire lock unblocked aborted (already closed)"); } return null; } } mSharedLocks++; ownedLocks = mLockCount.get() + 1; mLockCount.set(ownedLocks); } finally { mLock.unlock(); } if (VERBOSE) { log("acquired lock (local own count = " + ownedLocks + ")"); } return new ScopedLock(); } /** * Try to acquire the lock exclusively, blocking until all other threads release their locks. * *

If the lock has already been closed, or being closed before this operation returns, * the call will immediately return {@code false}.

* *

If any other threads are holding a lock, this thread will block until all * other locks are released.

* *

This lock is re-entrant; acquiring more than one exclusive lock per thread is supported, * and must be matched by an equal number of {@link #releaseLock} calls.

* * @return {@code ScopedLock} instance if the lock was acquired, or {@code null} if the lock * was already closed. * * @throws IllegalStateException * if an attempt is made to acquire an exclusive lock while already holding a lock */ public ScopedLock acquireExclusiveLock() { int ownedLocks; try { mLock.lock(); // Lock is already closed, all further acquisitions will fail if (mClosed) { if (VERBOSE) { log("acquire exclusive lock early aborted (already closed)"); } return null; } ownedLocks = mLockCount.get(); // This thread is already holding a shared lock if (!mExclusive && ownedLocks > 0) { throw new IllegalStateException( "Cannot acquire exclusive lock while holding shared lock"); } /* * Is another thread holding the lock? Block until we can get in. * * If we are already holding the lock, always let it through since * we are just reentering the exclusive lock. */ while (ownedLocks == 0 && (mExclusive || mSharedLocks > 0)) { mCondition.awaitUninterruptibly(); // Did another thread #close while we were waiting? Unblock immediately. if (mClosed) { if (VERBOSE) { log("acquire exclusive lock unblocked aborted (already closed)"); } return null; } } mExclusive = true; ownedLocks = mLockCount.get() + 1; mLockCount.set(ownedLocks); } finally { mLock.unlock(); } if (VERBOSE) { log("acquired exclusive lock (local own count = " + ownedLocks + ")"); } return new ScopedLock(); } /** * Release a single lock that was acquired. * *

Any other other that is blocked and trying to acquire a lock will get a chance * to acquire the lock.

* * @throws IllegalStateException if no locks were acquired, or if the lock was already closed */ public void releaseLock() { if (mLockCount.get() <= 0) { throw new IllegalStateException( "Cannot release lock that was not acquired by this thread"); } int ownedLocks; try { mLock.lock(); // Lock is already closed, it couldn't have been acquired in the first place if (mClosed) { throw new IllegalStateException("Do not release after the lock has been closed"); } if (!mExclusive) { mSharedLocks--; } else { if (mSharedLocks != 0) { throw new AssertionError("Too many shared locks " + mSharedLocks); } } ownedLocks = mLockCount.get() - 1; mLockCount.set(ownedLocks); if (ownedLocks == 0 && mExclusive) { // Wake up any threads that might be waiting for the exclusive lock to be released mExclusive = false; mCondition.signalAll(); } else if (ownedLocks == 0 && mSharedLocks == 0) { // Wake up any threads that might be trying to get the exclusive lock mCondition.signalAll(); } } finally { mLock.unlock(); } if (VERBOSE) { log("released lock (local lock count " + ownedLocks + ")"); } } private void log(String what) { Log.v(TAG + "[" + mName + "]", what); } }