blob: 3b8beaccdcd92736fc03a01e83878e68103031d6 [file] [log] [blame]
Igor Murashkin49b2b132014-06-18 19:03:00 -07001/*
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) {
Ruben Brunk2cdfa262014-07-11 11:46:20 -0700113 if (VERBOSE) {
114 log("close - already closed; ignoring");
115 }
Igor Murashkin49b2b132014-06-18 19:03:00 -0700116 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
Ruben Brunk2cdfa262014-07-11 11:46:20 -0700144 if (VERBOSE) {
145 log("close - completed");
146 }
Igor Murashkin49b2b132014-06-18 19:03:00 -0700147 }
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) {
Ruben Brunk2cdfa262014-07-11 11:46:20 -0700176 if (VERBOSE) {
177 log("acquire lock early aborted (already closed)");
178 }
Igor Murashkin49b2b132014-06-18 19:03:00 -0700179 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) {
Ruben Brunk2cdfa262014-07-11 11:46:20 -0700196 if (VERBOSE) {
197 log("acquire lock unblocked aborted (already closed)");
198 }
Igor Murashkin49b2b132014-06-18 19:03:00 -0700199 return null;
200 }
201 }
202
203 mSharedLocks++;
204
205 ownedLocks = mLockCount.get() + 1;
206 mLockCount.set(ownedLocks);
207 } finally {
208 mLock.unlock();
209 }
210
Ruben Brunk2cdfa262014-07-11 11:46:20 -0700211 if (VERBOSE) {
212 log("acquired lock (local own count = " + ownedLocks + ")");
213 }
Igor Murashkin49b2b132014-06-18 19:03:00 -0700214 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) {
Ruben Brunk2cdfa262014-07-11 11:46:20 -0700244 if (VERBOSE) {
245 log("acquire exclusive lock early aborted (already closed)");
246 }
Igor Murashkin49b2b132014-06-18 19:03:00 -0700247 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) {
Ruben Brunk2cdfa262014-07-11 11:46:20 -0700269 if (VERBOSE) {
270 log("acquire exclusive lock unblocked aborted (already closed)");
271 }
Igor Murashkin49b2b132014-06-18 19:03:00 -0700272 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
Ruben Brunk2cdfa262014-07-11 11:46:20 -0700284 if (VERBOSE) {
285 log("acquired exclusive lock (local own count = " + ownedLocks + ")");
286 }
Igor Murashkin49b2b132014-06-18 19:03:00 -0700287 return new ScopedLock();
288 }
289
290 /**
291 * Release a single lock that was acquired.
292 *
koprivaf07a4602018-09-20 11:20:27 -0700293 * <p>Any other thread that is blocked and trying to acquire a lock will get a chance
Igor Murashkin49b2b132014-06-18 19:03:00 -0700294 * 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
Ruben Brunk2cdfa262014-07-11 11:46:20 -0700337 if (VERBOSE) {
338 log("released lock (local lock count " + ownedLocks + ")");
339 }
Igor Murashkin49b2b132014-06-18 19:03:00 -0700340 }
341
342 private void log(String what) {
Ruben Brunk2cdfa262014-07-11 11:46:20 -0700343 Log.v(TAG + "[" + mName + "]", what);
Igor Murashkin49b2b132014-06-18 19:03:00 -0700344 }
345
346}