content: /*
   * 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.
   *
   * <p>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.</p>
   *
   * <p>Locks are re-entrant; trying to acquire another lock (of the same type)
   * while a lock is already held will immediately succeed.</p>
   *
   * <p>Acquiring to acquire a shared lock while holding an exclusive lock or vice versa is not
   * supported; attempting it will throw an {@link IllegalStateException}.</p>
   *
   * <p>If the lock is closed, all future and current acquires will immediately return {@code null}.
   * </p>
   */
  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:
       *
       * <p>Reentrant locking increments this. If an exclusive lock is held,
       * this value will stay at 0.</p>
       */
      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<Integer> mLockCount =
          new ThreadLocal<Integer>() {
              @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.
       *
       * <p>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.</p>
       *
       * <p>This operation is idempotent; calling it more than once has no effect.</p>
       *
       * @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.
       *
       * <p>If the lock has already been closed, or being closed before this operation returns,
       * the call will immediately return {@code false}.</p>
       *
       * <p>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.</p>
       *
       * <p>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.</p>
       *
       * @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.
       *
       * <p>If the lock has already been closed, or being closed before this operation returns,
       * the call will immediately return {@code false}.</p>
       *
       * <p>If any other threads are holding a lock, this thread will block until all
       * other locks are released.</p>
       *
       * <p>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.</p>
       *
       * @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.
       *
       * <p>Any other thread that is blocked and trying to acquire a lock will get a chance
       * to acquire the lock.</p>
       *
       * @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);
      }
  
  }
  