com.browserup.bup.proxy.ActivityMonitor Maven / Gradle / Ivy
/*
* Modifications Copyright (c) 2019 BrowserUp, Inc.
*/
package com.browserup.bup.proxy;
import com.google.common.util.concurrent.Monitor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* Tracks active and total requests on a proxy, and provides a mechanism to wait for active requests to finish.
* See {@link com.browserup.bup.proxy.ActivityMonitor#waitForQuiescence(long, long, java.util.concurrent.TimeUnit)}.
*/
public class ActivityMonitor {
private final AtomicInteger activeRequests = new AtomicInteger(0);
private final AtomicInteger totalRequests = new AtomicInteger(0);
private final AtomicLong lastRequestFinishedNanos = new AtomicLong(System.nanoTime());
private final Monitor monitor = new Monitor();
private final Monitor.Guard requestNotActive = new Monitor.Guard(monitor) {
@Override
public boolean isSatisfied() {
return activeRequests.get() == 0;
}
};
private final Monitor.Guard requestActive = new Monitor.Guard(monitor) {
@Override
public boolean isSatisfied() {
return activeRequests.get() > 0;
}
};
public void requestStarted() {
int previousCount = activeRequests.getAndIncrement();
totalRequests.incrementAndGet();
if (previousCount == 0) {
// previously there were no active requests, but now there are -- signal to any waitForQuiescence threads that they need to
// begin waiting again
monitor.enter();
monitor.leave();
}
}
public void requestFinished() {
int newCount = activeRequests.decrementAndGet();
lastRequestFinishedNanos.set(System.nanoTime());
if (newCount == 0) {
// there are no active requests, so signal to any waitForQuiescence threads that they can begin waiting for their quietPeriod
monitor.enter();
monitor.leave();
}
}
public int getActiveRequests() {
return activeRequests.get();
}
public int getTotalRequests() {
return totalRequests.get();
}
public boolean waitForQuiescence(long quietPeriod, long timeout, TimeUnit timeUnit) {
// the minRequestFinishTime is the earliest possible time the current or last request "could" finish. if there is no active
// request, this is simply the lastRequestFinishedNanos time. if there is an active request, it is "now". this helps avoid waiting
// for quiescence if there is an active request and the timeout is less than the quietPeriod.
long minRequestFinishTime;
if (activeRequests.get() == 0) {
if (timeUnit.convert(System.nanoTime() - lastRequestFinishedNanos.get(), TimeUnit.NANOSECONDS) >= quietPeriod) {
return true;
} else {
minRequestFinishTime = lastRequestFinishedNanos.get();
}
} else {
minRequestFinishTime = System.nanoTime();
}
// record the maximum time we can wait until (the current time + the timeout), which will allow us to avoid waiting for
// quiescence if it is not possible to satisfy the quietPeriod before the waitUntil time elapses
long waitUntil = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeout, timeUnit);
while (minRequestFinishTime + TimeUnit.NANOSECONDS.convert(quietPeriod, timeUnit) <= waitUntil) {
// the maximum amount of time we can wait for active requests to finish that will still allow us to wait for quiescence
// for the quietPeriod.
long maxWaitTimeForActiveRequests = waitUntil - System.nanoTime() - TimeUnit.NANOSECONDS.convert(quietPeriod, timeUnit);
// wait for active requests to finish
boolean success = monitor.enterWhenUninterruptibly(requestNotActive, maxWaitTimeForActiveRequests, TimeUnit.NANOSECONDS);
if (!success) {
// timed out waiting for active requests to finish
return false;
}
monitor.leave();
// the time needed to monitor for new active requests is whenever the last request finished + the quiet period. this may be less
// than the actual quiet period if no requests were active when entering waitForQuiescence, but the quietPeriod has not yet elapsed
// since the last request.
long waitForNewRequests = lastRequestFinishedNanos.get() - System.nanoTime() + TimeUnit.NANOSECONDS.convert(quietPeriod, timeUnit);
// if the quietPeriod has already elapsed since the last request, no need to wait any longer
if (waitForNewRequests < 0) {
return true;
}
// wait for new requests to come in. if a new request comes in, the loop will restart, waiting for active requests to complete.
boolean requestsActive = monitor.enterWhenUninterruptibly(requestActive, waitForNewRequests, TimeUnit.NANOSECONDS);
if (requestsActive) {
// a request became active, so we need to wait for all requests to finish again
monitor.leave();
continue;
} else {
return true;
}
}
return false;
}
}