package com.netflix.astyanax.contrib.valve;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import com.netflix.astyanax.contrib.valve.TimeWindowValve.RequestStatus;
public class RollingTimeWindowValve {
private final AtomicReference<InnerState> currentRef = new AtomicReference<InnerState>(null);
private final AtomicLong ratePerSecond = new AtomicLong(0L);
private final AtomicInteger numBuckets = new AtomicInteger(0);
private final AtomicBoolean valveCheckDisabled = new AtomicBoolean(false);
public RollingTimeWindowValve(long rps, int nBuckets) {
ratePerSecond.set(rps);
numBuckets.set(nBuckets);
currentRef.set(new InnerState(System.currentTimeMillis()));
}
public void setRatePerSecond(Long newRps) {
ratePerSecond.set(newRps);
}
public void setNumBuckets(int newBuckets) {
numBuckets.set(newBuckets);
}
public void disableValveCheck() {
valveCheckDisabled.set(true);
}
public void enableValveCheck() {
valveCheckDisabled.set(false);
}
public boolean decrementAndCheckQuota() {
if (valveCheckDisabled.get()) {
return true;
}
InnerState currentState = currentRef.get();
TimeWindowValve currentWindow = currentState.window;
RequestStatus status = currentWindow.decrementAndCheckQuota();
if (status == RequestStatus.Permitted) {
return true;
}
if (status == RequestStatus.OverQuota) {
return false;
}
if (status == RequestStatus.PastWindow) {
InnerState nextState = new InnerState(System.currentTimeMillis());
boolean success = currentRef.compareAndSet(currentState, nextState);
if (success) {
//System.out.println("FLIP");
}
// Try one more time before giving up
return (currentRef.get().window.decrementAndCheckQuota() == RequestStatus.Permitted);
}
return false;
}
private class InnerState {
private final String id = UUID.randomUUID().toString();
private final Long startTime;
private final TimeWindowValve window;
private InnerState(Long startWindowMillis) {
startTime = startWindowMillis;
int nBuckets = numBuckets.get();
long rateForWindow = ratePerSecond.get()/nBuckets;
long windowMillis = 1000/nBuckets;
window = new TimeWindowValve(rateForWindow, startWindowMillis, windowMillis);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((startTime == null) ? 0 : startTime.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
InnerState other = (InnerState) obj;
boolean equals = true;
equals &= (id == null) ? other.id == null : id.equals(other.id);
equals &= (startTime == null) ? other.startTime == null : startTime.equals(other.startTime);
return equals;
}
@Override
public String toString() {
return id.toString();
}
}
}