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