package edu.uw.zookeeper.client;
import java.util.concurrent.Executor;
import com.google.common.base.Function;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.Monitor;
import com.google.common.util.concurrent.MoreExecutors;
import com.typesafe.config.ConfigValueType;
import edu.uw.zookeeper.common.Configurable;
import edu.uw.zookeeper.common.Configuration;
import edu.uw.zookeeper.common.Promise;
import edu.uw.zookeeper.common.SettableFuturePromise;
import edu.uw.zookeeper.protocol.Operation;
import edu.uw.zookeeper.protocol.SessionListener;
public class LimitOutstandingClient<I extends Operation.Request, O extends Operation.ProtocolResponse<?>, T extends SessionListener> implements ClientExecutor<I,O,T> {
public static <I extends Operation.Request, O extends Operation.ProtocolResponse<?>, T extends SessionListener> ClientExecutor<? super I, O, T> create(
Configuration configuration,
ClientExecutor<? super I, O, T> client) {
return create(ConfigurableLimit.get(configuration), client);
}
public static <I extends Operation.Request, O extends Operation.ProtocolResponse<?>, T extends SessionListener> ClientExecutor<? super I, O, T> create(
int limit,
ClientExecutor<? super I, O, T> client) {
if (limit == NO_LIMIT) {
return client;
} else if (limit < 0) {
throw new IllegalStateException(String.valueOf(limit));
} else {
return new LimitOutstandingClient<I,O,T>(limit, client);
}
}
@Configurable(arg="outstanding", value="1000", type=ConfigValueType.NUMBER)
public static class ConfigurableLimit implements Function<Configuration, Integer> {
public static Integer get(Configuration configuration) {
return new ConfigurableLimit().apply(configuration);
}
@Override
public Integer apply(Configuration configuration) {
Configurable configurable = getClass().getAnnotation(Configurable.class);
return configuration.withConfigurable(configurable)
.getConfigOrEmpty(configurable.path())
.getInt(configurable.arg());
}
}
public static int NO_LIMIT = 0;
private final Monitor monitor = new Monitor();
private final Monitor.Guard notThrottled = new Monitor.Guard(monitor) {
public boolean isSatisfied() {
return outstanding < limit;
}
};
private volatile int outstanding = 0;
private final int limit;
private final ClientExecutor<? super I, O, T> delegate;
private final Listener listener;
private final Executor executor;
protected LimitOutstandingClient(
int limit,
ClientExecutor<? super I, O, T> delegate) {
this.limit = limit;
this.delegate = delegate;
this.executor = MoreExecutors.directExecutor();
this.listener = new Listener();
}
public int getLimit() {
return limit;
}
public int getOutstanding() {
return outstanding;
}
@Override
public ListenableFuture<O> submit(I request) {
return submit(request, SettableFuturePromise.<O>create());
}
@Override
public ListenableFuture<O> submit(I request, Promise<O> promise) {
try {
monitor.enterWhen(notThrottled);
} catch (InterruptedException e) {
promise.setException(e);
return promise;
}
try {
ListenableFuture<O> future = delegate.submit(request, promise);
outstanding += 1;
future.addListener(listener, executor);
return future;
} finally {
monitor.leave();
}
}
@Override
public void subscribe(T handler) {
delegate.subscribe(handler);
}
@Override
public boolean unsubscribe(T handler) {
return delegate.unsubscribe(handler);
}
private class Listener implements Runnable {
@Override
public void run() {
monitor.enter();
try {
assert (outstanding > 0);
outstanding -= 1;
} finally {
monitor.leave();
}
}
}
}