Skip to content

Commit 01bb51b

Browse files
authored
perf: Memory optimization for RangeCache (#65)
Motivation Currently, RangeCache will implicitly retain a reference to the RangeResponse KeyValue arrays used to initially populate/refresh the cache via the promise held in its startFuture field. This ref is there only to propagate cancellation to the etcd range request but can take up significant memory once completed if the cache is large. Note this is mostly the size of the object array itself since the contained objects will usually be referenced from the cache's map. However over time unnecessary references could remain if/when the corresponding KeyValues are deleted. The array's "shallow" footprint might also be non-negligible for very large caches. Modifications - Change the promise used for the start future to hold an explicit reference to the range request future, so that it can be nulled as soon as that other future completes. - Remove an incorrect assertion - Clean up a weird @nullable import
1 parent 6ab55c0 commit 01bb51b

1 file changed

Lines changed: 31 additions & 10 deletions

File tree

src/main/java/com/ibm/etcd/client/utils/RangeCache.java

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,13 @@
4444

4545
import javax.annotation.concurrent.GuardedBy;
4646

47-
import org.checkerframework.checker.nullness.qual.Nullable;
4847
import org.slf4j.Logger;
4948
import org.slf4j.LoggerFactory;
5049

5150
import com.google.common.collect.Iterators;
5251
import com.google.common.util.concurrent.AbstractFuture;
5352
import com.google.common.util.concurrent.Futures;
5453
import com.google.common.util.concurrent.ListenableFuture;
55-
import com.google.common.util.concurrent.MoreExecutors;
5654
import com.google.protobuf.ByteString;
5755
import com.ibm.etcd.client.EtcdClient;
5856
import com.ibm.etcd.client.FutureListener;
@@ -210,22 +208,46 @@ protected ListenableFuture<Boolean> fullRefreshCache() {
210208
.map(ResponseOp::getResponseRange)
211209
.collect(Collectors.toList()), directExecutor());
212210
}
213-
214-
final SettableFuture<Boolean> promise = new SettableFuture<Boolean>() {
211+
212+
final class StartPromise extends SettableFuture<Boolean> implements Runnable {
213+
// Reference to parent future (etcd range request) held here just
214+
// to be able to propagate cancellation if that happens before it completes.
215+
volatile ListenableFuture<?> parent;
216+
StartPromise(ListenableFuture<?> parent) {
217+
this.parent = parent;
218+
parent.addListener(this, directExecutor());
219+
}
215220
@Override
216-
protected void interruptTask() {
217-
if (rrfut.cancel(true)) {
221+
public void run() {
222+
// Ensure that we remove our reference to the "parent" future as soon
223+
// as it completes, since RangeCache stores this promise in the
224+
// startFuture field, and we want to allow the original future and its
225+
// possibly-large value to be garbage collected.
226+
parent = null;
227+
}
228+
@Override
229+
protected void afterDone() {
230+
if (!wasInterrupted()) {
231+
return;
232+
}
233+
// propagate cancellation
234+
final ListenableFuture<?> p = parent;
235+
if (p != null && p.cancel(true)) {
218236
return;
219237
}
220238
Watch theWatch;
221239
synchronized(RangeCache.this) {
222240
theWatch = watch;
241+
closed = true;
223242
}
224243
if (theWatch != null) {
225244
theWatch.close();
226245
}
227246
}
228-
};
247+
}
248+
249+
final SettableFuture<Boolean> promise = new StartPromise(rrfut);
250+
229251
Futures.addCallback(rrfut, (FutureListener<List<RangeResponse>>) (rrs, err) -> {
230252
if (rrs != null) try {
231253
setupWatch(rrs, firstTime, promise);
@@ -323,7 +345,6 @@ public void onError(Throwable t) {
323345
promise.setException(new CancellationException());
324346
return;
325347
}
326-
assert startFuture == promise;
327348
boolean isDone = promise.isDone();
328349
if (isDone && promise.isCancelled()) {
329350
return;
@@ -651,7 +672,7 @@ public boolean keyExistsRemote(ByteString key) {
651672
/**
652673
* Stores result of put operations
653674
*/
654-
public static class PutResult {
675+
public static final class PutResult {
655676
private final boolean succ;
656677
private final KeyValue kv;
657678
public PutResult(boolean success, KeyValue kv) {
@@ -1020,7 +1041,7 @@ public boolean isClosed() {
10201041

10211042
static class SettableFuture<V> extends AbstractFuture<V> {
10221043
@Override
1023-
public boolean set(@Nullable V value) {
1044+
public boolean set(V value) {
10241045
return super.set(value);
10251046
}
10261047
@Override

0 commit comments

Comments
 (0)