112f608f3d2089439a108788a1908941eea4277b9Puneet Lall/* 263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall * Copyright (C) 2015 The Android Open Source Project 312f608f3d2089439a108788a1908941eea4277b9Puneet Lall * 412f608f3d2089439a108788a1908941eea4277b9Puneet Lall * Licensed under the Apache License, Version 2.0 (the "License"); 512f608f3d2089439a108788a1908941eea4277b9Puneet Lall * you may not use this file except in compliance with the License. 612f608f3d2089439a108788a1908941eea4277b9Puneet Lall * You may obtain a copy of the License at 712f608f3d2089439a108788a1908941eea4277b9Puneet Lall * 812f608f3d2089439a108788a1908941eea4277b9Puneet Lall * http://www.apache.org/licenses/LICENSE-2.0 912f608f3d2089439a108788a1908941eea4277b9Puneet Lall * 1012f608f3d2089439a108788a1908941eea4277b9Puneet Lall * Unless required by applicable law or agreed to in writing, software 1112f608f3d2089439a108788a1908941eea4277b9Puneet Lall * distributed under the License is distributed on an "AS IS" BASIS, 1212f608f3d2089439a108788a1908941eea4277b9Puneet Lall * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1312f608f3d2089439a108788a1908941eea4277b9Puneet Lall * See the License for the specific language governing permissions and 1412f608f3d2089439a108788a1908941eea4277b9Puneet Lall * limitations under the License. 1512f608f3d2089439a108788a1908941eea4277b9Puneet Lall */ 1612f608f3d2089439a108788a1908941eea4277b9Puneet Lall 1712f608f3d2089439a108788a1908941eea4277b9Puneet Lallpackage com.android.camera.one.v2.sharedimagereader.ringbuffer; 1812f608f3d2089439a108788a1908941eea4277b9Puneet Lall 1912f608f3d2089439a108788a1908941eea4277b9Puneet Lallimport com.android.camera.async.BufferQueue; 2012f608f3d2089439a108788a1908941eea4277b9Puneet Lallimport com.android.camera.async.BufferQueueController; 2163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport com.android.camera.async.ConcurrentState; 2263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport com.android.camera.async.CountableBufferQueue; 2363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport com.android.camera.async.Observable; 2412f608f3d2089439a108788a1908941eea4277b9Puneet Lallimport com.android.camera.one.v2.camera2proxy.ImageProxy; 2512f608f3d2089439a108788a1908941eea4277b9Puneet Lallimport com.android.camera.one.v2.sharedimagereader.ticketpool.Ticket; 2663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport com.android.camera.one.v2.sharedimagereader.ticketpool.TicketPool; 2763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport com.android.camera.one.v2.sharedimagereader.util.ImageCloser; 2863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport com.android.camera.one.v2.sharedimagereader.util.TicketImageProxy; 2936a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lallimport com.google.common.base.Preconditions; 3063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall 3163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport java.util.Arrays; 3263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport java.util.Collection; 3363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport java.util.concurrent.TimeUnit; 3463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport java.util.concurrent.TimeoutException; 3563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport java.util.concurrent.atomic.AtomicInteger; 3612f608f3d2089439a108788a1908941eea4277b9Puneet Lall 374961ad31d9a877e3a68566fb5d4b33b7f79ce44ePuneet Lallimport javax.annotation.Nonnull; 3863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport javax.annotation.Nullable; 3963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallimport javax.annotation.ParametersAreNonnullByDefault; 4036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lallimport javax.annotation.concurrent.ThreadSafe; 4136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall 4236a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall/** 4336a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * A dynamically-sized ring-buffer, implementing BufferQueue (output) and 4436a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * BufferQueueController (input). 4536a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * <p> 4636a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * The size of the buffer is implicitly defined by the number of "Tickets" 4736a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * available from the parent {@link TicketPool} at any given time. When the 4836a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * number of available tickets decreases, the buffer shrinks, discarding old 4936a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * elements. When the number of available tickets increases, the buffer expands, 5036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * retaining old elements when new elements are added. 5136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * <p> 5236a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * The ring-buffer is also a TicketPool, which allows higher-priority requests 5336a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * to reserve "Tickets" (representing ImageReader capacity) to evict images from 5436a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * the ring-buffer. 5536a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * <p> 5636a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * See docs for {@link DynamicRingBufferFactory} for more information. 5736a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall */ 5836a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall@ThreadSafe 5963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall@ParametersAreNonnullByDefault 6063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lallfinal class DynamicRingBuffer implements TicketPool, BufferQueue<ImageProxy>, 6163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall BufferQueueController<ImageProxy> { 6263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall private final CountableBufferQueue<ImageProxy> mQueue; 6363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall private final TicketPool mTicketPool; 6463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall private final AtomicInteger mTicketWaiterCount; 6563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall private final AvailableTicketCounter mAvailableTicketCount; 6636a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall private final AtomicInteger mMaxSize; 6736a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall private final ConcurrentState<Integer> mQueueSize; 6863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall 6936a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall /** 7036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * @param parentTickets The parent ticket pool which implicitly determines 7136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall * how much capacity is available at any given time. 7236a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall */ 7363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall DynamicRingBuffer(TicketPool parentTickets) { 7436a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall mQueueSize = new ConcurrentState<>(0); 7536a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall mQueue = new CountableBufferQueue<>(mQueueSize, new ImageCloser()); 7636a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall mAvailableTicketCount = new AvailableTicketCounter(Arrays.asList(mQueueSize, parentTickets 7763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall .getAvailableTicketCount())); 7863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mTicketPool = parentTickets; 7963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mTicketWaiterCount = new AtomicInteger(0); 8036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall mMaxSize = new AtomicInteger(Integer.MAX_VALUE); 8112f608f3d2089439a108788a1908941eea4277b9Puneet Lall } 8212f608f3d2089439a108788a1908941eea4277b9Puneet Lall 8312f608f3d2089439a108788a1908941eea4277b9Puneet Lall @Override 844961ad31d9a877e3a68566fb5d4b33b7f79ce44ePuneet Lall public void update(@Nonnull ImageProxy image) { 8512f608f3d2089439a108788a1908941eea4277b9Puneet Lall // Try to acquire a ticket to expand the ring-buffer and save the image. 8663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall Ticket ticket = null; 8763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall 8863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall // Counting is hard. {@link mAvailableTicketCount} must reflect the sum 8963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall // of mTicketPool.getAvailableTicketCount() and the number of images in 9063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall // mQueue. However, for a brief moment, we acquire a ticket from 9163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall // mTicketPool, but have yet added it to mQueue. During this period, 9263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall // mAvailableTicketCount would appear to be 1 less than it should. 9363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall // To fix this, we must lock it to the current value, perform the 9463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall // transaction, and then unlock it, marking it as "valid" again, which 9563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall // also notifies listeners of the change. 9663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mAvailableTicketCount.freeze(); 9763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall try { 9863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall ticket = tryAcquireLowPriorityTicket(); 9963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall if (ticket == null) { 10063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall // If we cannot expand the ring-buffer, remove the last element 10163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall // (decreasing the size), and then try to increase the size 10263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall // again. 10363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mQueue.discardNext(); 10463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall ticket = tryAcquireLowPriorityTicket(); 10563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 10663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall if (ticket != null) { 10763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mQueue.update(new TicketImageProxy(image, ticket)); 10863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } else { 10963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall image.close(); 11063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 11136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall shrinkToFitMaxSize(); 11263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } finally { 11363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mAvailableTicketCount.unfreeze(); 11412f608f3d2089439a108788a1908941eea4277b9Puneet Lall } 11563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 11663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall 11763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall @Nullable 11863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall private Ticket tryAcquireLowPriorityTicket() { 11963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall if (mTicketWaiterCount.get() != 0) { 12063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall return null; 12112f608f3d2089439a108788a1908941eea4277b9Puneet Lall } 12263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall return mTicketPool.tryAcquire(); 12312f608f3d2089439a108788a1908941eea4277b9Puneet Lall } 12412f608f3d2089439a108788a1908941eea4277b9Puneet Lall 12512f608f3d2089439a108788a1908941eea4277b9Puneet Lall @Override 12612f608f3d2089439a108788a1908941eea4277b9Puneet Lall public void close() { 12763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mQueue.close(); 12863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 12963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall 13063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall @Override 13163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall public ImageProxy getNext() throws InterruptedException, BufferQueueClosedException { 13263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall return mQueue.getNext(); 13363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 13463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall 13563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall @Override 13663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall public ImageProxy getNext(long timeout, TimeUnit unit) throws InterruptedException, 13763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall TimeoutException, BufferQueueClosedException { 13863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall return mQueue.getNext(timeout, unit); 13963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 14063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall 14163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall @Override 14263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall public ImageProxy peekNext() { 14363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall return mQueue.peekNext(); 14463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 14563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall 14663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall @Override 14763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall public void discardNext() { 14863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mQueue.discardNext(); 14912f608f3d2089439a108788a1908941eea4277b9Puneet Lall } 15012f608f3d2089439a108788a1908941eea4277b9Puneet Lall 15112f608f3d2089439a108788a1908941eea4277b9Puneet Lall @Override 15212f608f3d2089439a108788a1908941eea4277b9Puneet Lall public boolean isClosed() { 15363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall return mQueue.isClosed(); 15463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 15563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall 15663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall @Nonnull 15763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall @Override 15863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall public Collection<Ticket> acquire(int tickets) throws InterruptedException, 15963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall NoCapacityAvailableException { 16063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mTicketWaiterCount.incrementAndGet(); 16163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall try { 16263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall while (mQueue.peekNext() != null) { 16363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mQueue.discardNext(); 16463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 16563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall return mTicketPool.acquire(tickets); 16663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } finally { 16763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mTicketWaiterCount.decrementAndGet(); 16863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 16963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 17063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall 17163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall @Nonnull 17263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall @Override 17363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall public Observable<Integer> getAvailableTicketCount() { 17463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall return mAvailableTicketCount; 17563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 17663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall 17763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall @Nullable 17863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall @Override 17963204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall public Ticket tryAcquire() { 18063204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mTicketWaiterCount.incrementAndGet(); 18163204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall try { 18263204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall while (mQueue.peekNext() != null) { 18363204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mQueue.discardNext(); 18463204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 18563204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall return mTicketPool.tryAcquire(); 18663204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } finally { 18763204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall mTicketWaiterCount.decrementAndGet(); 18863204dc989dbd0eba56f65086fde0ebe29ed6bdbPuneet Lall } 18912f608f3d2089439a108788a1908941eea4277b9Puneet Lall } 19036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall 19136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall public void setMaxSize(int newMaxSize) { 19236a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall Preconditions.checkArgument(newMaxSize >= 0); 19336a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall mMaxSize.set(newMaxSize); 19436a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall // Shrink the queue to meet this new constraint. 19536a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall shrinkToFitMaxSize(); 19636a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall } 19736a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall 19836a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall private void shrinkToFitMaxSize() { 19936a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall // To ensure that the available ticket count never "flickers" when we 20036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall // logically move the ticket from the queue into the parent ticket pool, 20136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall // lock the available ticket count. 20236a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall mAvailableTicketCount.freeze(); 20336a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall try { 20436a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall // Note that to maintain the invariant of eventual-consistency 20536a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall // (since this class is inherently shared between multiple threads), 20636a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall // we must repeatedly poll these values each time. 20736a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall while (mQueueSize.get() > mMaxSize.get()) { 20836a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall mQueue.discardNext(); 20936a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall } 21036a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall } finally { 21136a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall mAvailableTicketCount.unfreeze(); 21236a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall } 21336a1ad23bead41193e22442d3196e93a01ec7fe6Puneet Lall } 21412f608f3d2089439a108788a1908941eea4277b9Puneet Lall} 215