package com.webpieces.util.locking; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import org.webpieces.util.logging.Logger; import org.webpieces.util.logging.LoggerFactory; public class PermitQueue<RESP> { private static final Logger log = LoggerFactory.getLogger(PermitQueue.class); private final ConcurrentLinkedQueue<QueuedRequest<RESP>> queue = new ConcurrentLinkedQueue<>(); private final Semaphore permits; private final AtomicInteger toBeRemoved = new AtomicInteger(0); private int permitCount; public PermitQueue(int numPermits) { permitCount = numPermits; permits = new Semaphore(numPermits); } public CompletableFuture<RESP> runRequest(Supplier<CompletableFuture<RESP>> processor) { CompletableFuture<RESP> future = new CompletableFuture<RESP>(); queue.add(new QueuedRequest<>(future, processor)); processItemFromQueue(); return future; } private void processItemFromQueue() { boolean acquired = permits.tryAcquire(); if(!acquired) return; QueuedRequest<RESP> req = queue.poll(); if(req == null) { releaseSinglePermit(); //release acquired permit return; } CompletableFuture<RESP> future = req.getFuture(); try { CompletableFuture<RESP> resp = req.getProcessor().get(); resp.handle((r, t) -> handle(r, t, future)); } catch(Throwable e) { log.warn("Exception", e); handle(null, e, future); } } private void releaseSinglePermit() { int value = toBeRemoved.decrementAndGet(); if(value >= 0) { //we need to NOT release permit back into pool and just return here return; } //oops, it's less than 0, add it back now toBeRemoved.incrementAndGet(); permits.release(); } private Void handle(RESP resp, Throwable t, CompletableFuture<RESP> future) { if(t != null) future.completeExceptionally(t); else future.complete(resp); return null; } public int totalPermits() { return permitCount; } public int availablePermits() { return permits.availablePermits(); } public void releasePermit() { //apply the release now that the function is RUN WHEN the client resolves the release future releaseSinglePermit(); processItemFromQueue(); } public void modifyPermitPoolSize(int permitCnt) { permitCount += permitCnt; if(permitCnt > 0) { log.info("increasing permits in pool by "+permitCnt); //apply the release now that the function is RUN WHEN the client resolves the release future permits.release(permitCnt); for(int i = 0; i < permitCnt; i++) { processItemFromQueue(); } } else { log.info("decreasing permits in pool by "+permitCnt); int positiveToRemove = -permitCnt; //first try to remove them all immediately int countOfRemoved = 0; while(permits.tryAcquire()) { countOfRemoved++; if(countOfRemoved >= positiveToRemove) break; } int toRemoveStill = positiveToRemove - countOfRemoved; //then cache the rest that will get removed on release(ie. when someone is done) toBeRemoved.addAndGet(toRemoveStill); } } }