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 
17 package com.android.camera.util;
18 
19 import android.os.Handler;
20 import android.util.Pair;
21 
22 import com.android.camera.debug.Log.Tag;
23 
24 import java.security.InvalidParameterException;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.Map;
28 import java.util.TreeMap;
29 import java.util.concurrent.Semaphore;
30 
31 /**
32  * Implements a thread-safe fixed-size pool map of integers to objects such that
33  * the least element may be swapped out for a new element at any time. Elements
34  * may be temporarily "pinned" for processing in separate threads, during which
35  * they will not be swapped out. <br>
36  * This class enforces the invariant that a new element can always be swapped
37  * in. Thus, requests to pin an element for a particular task may be denied if
38  * there are not enough unpinned elements which can be removed. <br>
39  */
40 public class ConcurrentSharedRingBuffer<E> {
41     private static final Tag TAG = new Tag("CncrrntShrdRingBuf");
42 
43     /**
44      * Callback interface for swapping elements at the head of the buffer.
45      */
46     public static interface SwapTask<E> {
47         /**
48          * Called if the buffer is under-capacity and a new element is being
49          * added.
50          *
51          * @return the new element to add.
52          */
create()53         public E create();
54 
55         /**
56          * Called if the buffer is full and an old element must be swapped out
57          * to make room for the new element.
58          *
59          * @param oldElement the element being removed from the buffer.
60          * @return the new element to add.
61          */
swap(E oldElement)62         public E swap(E oldElement);
63 
64         /**
65          * Called if the buffer already has an element with the specified key.
66          * Note that the element may currently be pinned for processing by other
67          * elements. Therefore, implementations must be thread safe with respect
68          * to any other operations which may be applied to pinned tasks.
69          *
70          * @param existingElement the element to be updated.
71          */
update(E existingElement)72         public void update(E existingElement);
73     }
74 
75     /**
76      * Callback for selecting an element to pin. See
77      * {@link tryPinGreatestSelected}.
78      */
79     public static interface Selector<E> {
80         /**
81          * @param element The element to select or not select.
82          * @return true if the element should be selected, false otherwise.
83          */
select(E element)84         public boolean select(E element);
85     }
86 
87     public static interface PinStateListener {
88         /**
89          * Invoked whenever the ability to pin an element for processing
90          * changes.
91          *
92          * @param pinsAvailable If true, requests to pin elements (e.g. calls to
93          *            pinGreatest()) are less-likely to fail. If false, they are
94          *            more-likely to fail.
95          */
onPinStateChange(boolean pinsAvailable)96         public void onPinStateChange(boolean pinsAvailable);
97     }
98 
99     /**
100      * Wraps E with reference counting.
101      */
102     private static class Pinnable<E> {
103         private E mElement;
104 
105         /** Reference-counting for the number of tasks holding this element. */
106         private int mPins;
107 
Pinnable(E element)108         public Pinnable(E element) {
109             mElement = element;
110             mPins = 0;
111         }
112 
getElement()113         public E getElement() {
114             return mElement;
115         }
116 
isPinned()117         private boolean isPinned() {
118             return mPins > 0;
119         }
120     }
121 
122     /** Allow only one swapping operation at a time. */
123     private final Object mSwapLock = new Object();
124     /**
125      * Lock all transactions involving mElements, mUnpinnedElements,
126      * mCapacitySemaphore, mPinSemaphore, mClosed, mPinStateHandler, and
127      * mPinStateListener and the state of Pinnable instances. <br>
128      * TODO Replace this with a priority semaphore and allow swapLeast()
129      * operations to run faster at the expense of slower tryPin()/release()
130      * calls.
131      */
132     private final Object mLock = new Object();
133     /** Stores all elements. */
134     private TreeMap<Long, Pinnable<E>> mElements;
135     /** Stores the subset of mElements which is not pinned. */
136     private TreeMap<Long, Pinnable<E>> mUnpinnedElements;
137     /** Used to acquire space in mElements. */
138     private final Semaphore mCapacitySemaphore;
139     /** This must be acquired while an element is pinned. */
140     private final Semaphore mPinSemaphore;
141     private boolean mClosed = false;
142 
143     private Handler mPinStateHandler = null;
144     private PinStateListener mPinStateListener = null;
145 
146     /**
147      * Constructs a new ring buffer with the specified capacity.
148      *
149      * @param capacity the maximum number of elements to store.
150      */
ConcurrentSharedRingBuffer(int capacity)151     public ConcurrentSharedRingBuffer(int capacity) {
152         if (capacity <= 0) {
153             throw new IllegalArgumentException("Capacity must be positive.");
154         }
155 
156         mElements = new TreeMap<Long, Pinnable<E>>();
157         mUnpinnedElements = new TreeMap<Long, Pinnable<E>>();
158         mCapacitySemaphore = new Semaphore(capacity);
159         // Start with -1 permits to pin elements since we must always have at
160         // least one unpinned
161         // element available to swap out as the head of the buffer.
162         mPinSemaphore = new Semaphore(-1);
163     }
164 
165     /**
166      * Sets or replaces the listener.
167      *
168      * @param handler The handler on which to invoke the listener.
169      * @param listener The listener to be called whenever the ability to pin an
170      *            element changes.
171      */
setListener(Handler handler, PinStateListener listener)172     public void setListener(Handler handler, PinStateListener listener) {
173         synchronized (mLock) {
174             mPinStateHandler = handler;
175             mPinStateListener = listener;
176         }
177     }
178 
179     /**
180      * Places a new element in the ring buffer, removing the least (by key)
181      * non-pinned element if necessary. The existing element (or {@code null} if
182      * the buffer is under-capacity) is passed to {@code swapper.swap()} and the
183      * result is saved to the buffer. If an entry with {@code newKey} already
184      * exists in the ring-buffer, then {@code swapper.update()} is called and
185      * may modify the element in-place. See {@link SwapTask}. <br>
186      * Note that this method is the only way to add new elements to the buffer
187      * and will never be blocked on pinned tasks.
188      *
189      * @param newKey the key with which to store the swapped-in element.
190      * @param swapper the callback used to perform the swap.
191      * @return true if the swap was successful and the new element was saved to
192      *         the buffer, false if the swap was not possible and the element
193      *         was not saved to the buffer. Note that if the swap failed,
194      *         {@code swapper.create()} may or may not have been invoked.
195      */
swapLeast(long newKey, SwapTask<E> swapper)196     public boolean swapLeast(long newKey, SwapTask<E> swapper) {
197         synchronized (mSwapLock) {
198             Pinnable<E> existingElement = null;
199 
200             synchronized (mLock) {
201                 if (mClosed) {
202                     return false;
203                 }
204                 existingElement = mElements.get(newKey);
205             }
206 
207             if (existingElement != null) {
208                 swapper.update(existingElement.getElement());
209                 return true;
210             }
211 
212             if (mCapacitySemaphore.tryAcquire()) {
213                 // If we are under capacity, insert the new element and return.
214                 Pinnable<E> p = new Pinnable<E>(swapper.create());
215 
216                 synchronized (mLock) {
217                     if (mClosed) {
218                         return false;
219                     }
220 
221                     // Add the new element and release another permit to pin
222                     // allow pinning another element.
223                     mElements.put(newKey, p);
224                     mUnpinnedElements.put(newKey, p);
225                     mPinSemaphore.release();
226                     if (mPinSemaphore.availablePermits() == 1) {
227                         notifyPinStateChange(true);
228                     }
229                 }
230 
231                 return true;
232             } else {
233                 Pinnable<E> toSwap;
234 
235                 // Note that this method must be synchronized to avoid
236                 // attempting to remove more than one unpinned element at a
237                 // time.
238                 synchronized (mLock) {
239                     if (mClosed) {
240                         return false;
241                     }
242 
243                     Map.Entry<Long, Pinnable<E>> toSwapEntry = mUnpinnedElements.pollFirstEntry();
244 
245                     if (toSwapEntry == null) {
246                         // We should never get here.
247                         throw new RuntimeException("No unpinned element available.");
248                     }
249 
250                     toSwap = toSwapEntry.getValue();
251 
252                     // We must remove the element from both mElements and
253                     // mUnpinnedElements because it must be re-added after the
254                     // swap to be placed in the correct order with newKey.
255                     mElements.remove(toSwapEntry.getKey());
256                 }
257 
258                 try {
259                     toSwap.mElement = swapper.swap(toSwap.mElement);
260                 } finally {
261                     synchronized (mLock) {
262                         if (mClosed) {
263                             return false;
264                         }
265 
266                         mElements.put(newKey, toSwap);
267                         mUnpinnedElements.put(newKey, toSwap);
268                     }
269                 }
270                 return true;
271             }
272         }
273     }
274 
275     /**
276      * Attempts to pin the element with the given key and return it. <br>
277      * Note that, if a non-null pair is returned, the caller <em>must</em> call
278      * {@link #release} with the key.
279      *
280      * @return the key and object of the pinned element, if one could be pinned,
281      *         or null.
282      */
tryPin(long key)283     public Pair<Long, E> tryPin(long key) {
284 
285         boolean acquiredLastPin = false;
286         Pinnable<E> entry = null;
287 
288         synchronized (mLock) {
289             if (mClosed) {
290                 return null;
291             }
292 
293             if (mElements.isEmpty()) {
294                 return null;
295             }
296 
297             entry = mElements.get(key);
298 
299             if (entry == null) {
300                 return null;
301             }
302 
303             if (entry.isPinned()) {
304                 // If the element is already pinned by another task, simply
305                 // increment the pin count.
306                 entry.mPins++;
307             } else {
308                 // We must ensure that there will still be an unpinned element
309                 // after we pin this one.
310                 if (mPinSemaphore.tryAcquire()) {
311                     mUnpinnedElements.remove(key);
312                     entry.mPins++;
313 
314                     acquiredLastPin = mPinSemaphore.availablePermits() <= 0;
315                 } else {
316                     return null;
317                 }
318             }
319         }
320 
321         // If we just grabbed the last permit, we must notify listeners of the
322         // pin
323         // state change.
324         if (acquiredLastPin) {
325             notifyPinStateChange(false);
326         }
327 
328         return Pair.create(key, entry.getElement());
329     }
330 
release(long key)331     public void release(long key) {
332         synchronized (mLock) {
333             // Note that this must proceed even if the buffer has been closed.
334 
335             Pinnable<E> element = mElements.get(key);
336 
337             if (element == null) {
338                 throw new InvalidParameterException("No entry found for the given key.");
339             }
340 
341             if (!element.isPinned()) {
342                 throw new IllegalArgumentException("Calling release() with unpinned element.");
343             }
344 
345             // Unpin the element
346             element.mPins--;
347 
348             if (!element.isPinned()) {
349                 // If there are now 0 tasks pinning this element...
350                 mUnpinnedElements.put(key, element);
351 
352                 // Allow pinning another element.
353                 mPinSemaphore.release();
354 
355                 if (mPinSemaphore.availablePermits() == 1) {
356                     notifyPinStateChange(true);
357                 }
358             }
359         }
360     }
361 
362     /**
363      * Attempts to pin the greatest element and return it. <br>
364      * Note that, if a non-null element is returned, the caller <em>must</em>
365      * call {@link #release} with the element. Furthermore, behavior is
366      * undefined if the element's {@code compareTo} behavior changes between
367      * these calls.
368      *
369      * @return the key and object of the pinned element, if one could be pinned,
370      *         or null.
371      */
tryPinGreatest()372     public Pair<Long, E> tryPinGreatest() {
373         synchronized (mLock) {
374             if (mClosed) {
375                 return null;
376             }
377 
378             if (mElements.isEmpty()) {
379                 return null;
380             }
381 
382             return tryPin(mElements.lastKey());
383         }
384     }
385 
386     /**
387      * Attempts to pin the greatest element for which {@code selector} returns
388      * true. <br>
389      *
390      * @see #pinGreatest
391      */
tryPinGreatestSelected(Selector<E> selector)392     public Pair<Long, E> tryPinGreatestSelected(Selector<E> selector) {
393         // (Quickly) get the list of elements to search through.
394         ArrayList<Long> keys = new ArrayList<Long>();
395         synchronized (mLock) {
396             if (mClosed) {
397                 return null;
398             }
399 
400             if (mElements.isEmpty()) {
401                 return null;
402             }
403 
404             keys.addAll(mElements.keySet());
405         }
406 
407         Collections.sort(keys);
408 
409         // Pin each element, from greatest key to least, until we find the one
410         // we want (the element with the greatest key for which
411         // selector.selected() returns true).
412         for (int i = keys.size() - 1; i >= 0; i--) {
413             Pair<Long, E> pinnedCandidate = tryPin(keys.get(i));
414             if (pinnedCandidate != null) {
415                 boolean selected = false;
416 
417                 try {
418                     selected = selector.select(pinnedCandidate.second);
419                 } finally {
420                     // Don't leak pinnedCandidate if the above select() threw an
421                     // exception.
422                     if (selected) {
423                         return pinnedCandidate;
424                     } else {
425                         release(pinnedCandidate.first);
426                     }
427                 }
428             }
429         }
430 
431         return null;
432     }
433 
434     /**
435      * Removes all elements from the buffer, running {@code task} on each one,
436      * and waiting, if necessary, for all pins to be released.
437      *
438      * @param task
439      * @throws InterruptedException
440      */
close(Task<E> task)441     public void close(Task<E> task) throws InterruptedException {
442         int numPinnedElements;
443 
444         // Ensure that any pending swap tasks complete before closing.
445         synchronized (mSwapLock) {
446             synchronized (mLock) {
447                 mClosed = true;
448                 numPinnedElements = mElements.size() - mUnpinnedElements.size();
449             }
450         }
451 
452         notifyPinStateChange(false);
453 
454         // Wait for all pinned tasks to complete.
455         if (numPinnedElements > 0) {
456             mPinSemaphore.acquire(numPinnedElements);
457         }
458 
459         for (Pinnable<E> element : mElements.values()) {
460             task.run(element.mElement);
461         }
462 
463         mUnpinnedElements.clear();
464 
465         mElements.clear();
466     }
467 
notifyPinStateChange(final boolean pinsAvailable)468     private void notifyPinStateChange(final boolean pinsAvailable) {
469         synchronized (mLock) {
470             // We must synchronize on mPinStateHandler and mPinStateListener.
471             if (mPinStateHandler != null) {
472                 final PinStateListener listener = mPinStateListener;
473                 mPinStateHandler.post(new Runnable() {
474                         @Override
475                     public void run() {
476                         listener.onPinStateChange(pinsAvailable);
477                     }
478                 });
479             }
480         }
481     }
482 }
483