Home | History | Annotate | Download | only in ticketpool
      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.one.v2.sharedimagereader.ticketpool;
     18 
     19 import com.android.camera.async.ConcurrentState;
     20 import com.android.camera.async.Observable;
     21 import com.android.camera.async.SafeCloseable;
     22 
     23 import java.util.ArrayList;
     24 import java.util.Collection;
     25 import java.util.LinkedList;
     26 import java.util.List;
     27 import java.util.concurrent.atomic.AtomicBoolean;
     28 import java.util.concurrent.locks.Condition;
     29 import java.util.concurrent.locks.ReentrantLock;
     30 
     31 import javax.annotation.Nonnull;
     32 import javax.annotation.concurrent.GuardedBy;
     33 
     34 /**
     35  * A ticket pool with a finite number of tickets.
     36  */
     37 public final class FiniteTicketPool implements TicketPool, SafeCloseable {
     38     private class TicketImpl implements Ticket {
     39         private final AtomicBoolean mClosed;
     40 
     41         public TicketImpl() {
     42             mClosed = new AtomicBoolean(false);
     43         }
     44 
     45         @Override
     46         public void close() {
     47             boolean alreadyClosed = mClosed.getAndSet(true);
     48             if (!alreadyClosed) {
     49                 synchronized (mLock) {
     50                     releaseTicket();
     51                     updateAvailableTicketCount();
     52                 }
     53             }
     54         }
     55     }
     56 
     57     private class Waiter {
     58         private final int mTicketsRequested;
     59         private final Condition mCondition;
     60 
     61         private Waiter(int ticketsRequested, Condition condition) {
     62             mTicketsRequested = ticketsRequested;
     63             mCondition = condition;
     64         }
     65 
     66         public Condition getCondition() {
     67             return mCondition;
     68         }
     69 
     70         public int getTicketsRequested() {
     71             return mTicketsRequested;
     72         }
     73     }
     74 
     75     private final int mMaxCapacity;
     76     private final ReentrantLock mLock;
     77     @GuardedBy("mLock")
     78     private final LinkedList<Waiter> mWaiters;
     79     private final ConcurrentState<Integer> mAvailableTicketCount;
     80     @GuardedBy("mLock")
     81     private int mTickets;
     82     @GuardedBy("mLock")
     83     private boolean mClosed;
     84 
     85     public FiniteTicketPool(int capacity) {
     86         mMaxCapacity = capacity;
     87         mLock = new ReentrantLock(true);
     88         mTickets = capacity;
     89         mWaiters = new LinkedList<>();
     90         mClosed = false;
     91         mAvailableTicketCount = new ConcurrentState<>(capacity);
     92     }
     93 
     94     @GuardedBy("mLock")
     95     private void releaseTicket() {
     96         mLock.lock();
     97         try {
     98             mTickets++;
     99 
    100             // Wake up waiters in order, so long as their requested number of
    101             // tickets can be satisfied.
    102             int ticketsRemaining = mTickets;
    103             Waiter nextWaiter = mWaiters.peekFirst();
    104             while (nextWaiter != null && nextWaiter.getTicketsRequested() <= ticketsRemaining) {
    105                 ticketsRemaining -= nextWaiter.getTicketsRequested();
    106                 nextWaiter.getCondition().signal();
    107 
    108                 mWaiters.removeFirst();
    109                 nextWaiter = mWaiters.peekFirst();
    110             }
    111         } finally {
    112             mLock.unlock();
    113         }
    114     }
    115 
    116     @Nonnull
    117     @Override
    118     public Collection<Ticket> acquire(int tickets) throws InterruptedException,
    119             NoCapacityAvailableException {
    120         mLock.lock();
    121         try {
    122             if (tickets > mMaxCapacity || tickets < 0) {
    123                 throw new NoCapacityAvailableException();
    124             }
    125             Waiter thisWaiter = new Waiter(tickets, mLock.newCondition());
    126             mWaiters.addLast(thisWaiter);
    127             updateAvailableTicketCount();
    128             try {
    129                 while (mTickets < tickets && !mClosed) {
    130                     thisWaiter.getCondition().await();
    131                 }
    132                 if (mClosed) {
    133                     throw new NoCapacityAvailableException();
    134                 }
    135 
    136                 mTickets -= tickets;
    137 
    138                 updateAvailableTicketCount();
    139 
    140                 List<Ticket> ticketList = new ArrayList<>();
    141                 for (int i = 0; i < tickets; i++) {
    142                     ticketList.add(new TicketImpl());
    143                 }
    144                 return ticketList;
    145             } finally {
    146                 mWaiters.remove(thisWaiter);
    147                 updateAvailableTicketCount();
    148             }
    149         } finally {
    150             mLock.unlock();
    151         }
    152     }
    153 
    154     @GuardedBy("mLock")
    155     private void updateAvailableTicketCount() {
    156         if (mClosed || !mWaiters.isEmpty()) {
    157             mAvailableTicketCount.update(0);
    158         } else {
    159             mAvailableTicketCount.update(mTickets);
    160         }
    161     }
    162 
    163     @Nonnull
    164     @Override
    165     public Observable<Integer> getAvailableTicketCount() {
    166         return mAvailableTicketCount;
    167     }
    168 
    169     @Override
    170     public Ticket tryAcquire() {
    171         mLock.lock();
    172         try {
    173             if (!mClosed && mWaiters.isEmpty() && mTickets >= 1) {
    174                 mTickets--;
    175                 updateAvailableTicketCount();
    176                 return new TicketImpl();
    177             } else {
    178                 return null;
    179             }
    180         } finally {
    181             mLock.unlock();
    182         }
    183     }
    184 
    185     @Override
    186     public void close() {
    187         mLock.lock();
    188         try {
    189             if (mClosed) {
    190                 return;
    191             }
    192 
    193             mClosed = true;
    194 
    195             for (Waiter waiter : mWaiters) {
    196                 waiter.getCondition().signal();
    197             }
    198 
    199             updateAvailableTicketCount();
    200         } finally {
    201             mLock.unlock();
    202         }
    203     }
    204 }
    205