ea-async
ea-async copied to clipboard
Await in loops cause memory leak
Hi,
I am reporting this observation that came up when we noticed our instances getting OOM killed.
This happens in any sort of loop, if the future completes instantly, no issue, but if it enters the synthetic method that the instrumentation creates, all the completable futures will not be GC'd till that method exits.
If you have this loop logic in one method and another method awaits on it, the problem persists, but now in the "awaiting" method.
If you lets say change infinite loops to limited for's, now the same "build-up" of futures will happen on the method that awaits on the looping method to complete (if that awaiting method is looping/retrying itself).
Recursive calls, separate thread completion and so on all retain the same issue.
Is there a way to attempt fixing this, I am willing to contribute but I lack experience on this specific instrumentation topic.
Thank you.
I also encountered this problem before. In order to fix it, I had to extend CompletableFuture and reimplement thenCompose() and complete().
I also encountered this problem before. In order to fix it, I had to extend CompletableFuture and reimplement thenCompose() and complete().
What changes did you have to make?
How to solve the problem
Original Code:
static CompletableFuture<String> convert() {
for (int i = 0; i < 2; i++) {
CompletableFuture<Integer> e = CompletableFuture.completedFuture(1);
Async.await(e);
}
return CompletableFuture.completedFuture("ok");
}
Generated after EA:
static CompletableFuture<String> convert() {
for (int i = 0; i < 2; i++) {
CompletableFuture<Integer> e = CompletableFuture.completedFuture(Integer.valueOf(1));
if (!e.isDone()) { CompletableFuture<Integer> completableFuture = e; return completableFuture.exceptionally((Function)Function.identity()).thenCompose(paramObject -> { // Byte code:
// 0: iload_3
// 1: tableswitch default -> 90, 0 -> 24, 1 -> 86
// 24: iconst_0
// 25: istore_0
// 26: iload_0
// 27: iconst_2
// 28: if_icmpge -> 80
// 31: iconst_1
// 32: invokestatic valueOf : (I)Ljava/lang/Integer;
// 35: invokestatic completedFuture : (Ljava/lang/Object;)Ljava/util/concurrent/CompletableFuture;
// 38: astore_1
// 39: aload_1
// 40: dup
// 41: invokevirtual isDone : ()Z
// 44: ifne -> 70
// 47: astore_2
// 48: aload_2
// 49: invokestatic identity : ()Ljava/util/function/Function;
// 52: invokevirtual exceptionally : (Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;
// 55: iload_0
// 56: aload_1
// 57: aload_2
// 58: sipush #1
// 61: <illegal opcode> apply : (ILjava/util/concurrent/CompletableFuture;Ljava/util/concurrent/CompletableFuture;I)Ljava/util/function/Function;
// 66: invokevirtual thenCompose : (Ljava/util/function/Function;)Ljava/util/concurrent/CompletableFuture;
// 69: areturn
// 70: invokevirtual join : ()Ljava/lang/Object;
// 73: pop
// 74: iinc #0, 1
// 77: goto -> 26
// 80: ldc 'ok'
// 82: invokestatic completedFuture : (Ljava/lang/Object;)Ljava/util/concurrent/CompletableFuture;
// 85: areturn
// 86: aload_2
// 87: goto -> 70
// 90: new java/lang/IllegalArgumentException
// 93: dup
// 94: invokespecial <init> : ()V
// 97: athrow
// Line number table:
// Java source line number -> byte code offset
// #17 -> 24
// #18 -> 31
// #19 -> 39
// #17 -> 74
// #21 -> 80
// Local variable table:
// start length slot name descriptor
// 39 35 1 e Ljava/util/concurrent/CompletableFuture;
// 26 54 0 i I
// Local variable type table:
// start length slot name signature
// 39 35 1 e Ljava/util/concurrent/CompletableFuture<Ljava/lang/Integer;>; }); } e.join();
}
return CompletableFuture.completedFuture("ok");
}