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