ea-async icon indicating copy to clipboard operation
ea-async copied to clipboard

Await in loops cause memory leak

Open zekronium opened this issue 3 years ago • 5 comments

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.

zekronium avatar Feb 22 '21 21:02 zekronium

I also encountered this problem before. In order to fix it, I had to extend CompletableFuture and reimplement thenCompose() and complete().

dwing4g avatar Feb 24 '21 11:02 dwing4g

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?

zekronium avatar Feb 24 '21 13:02 zekronium

How to solve the problem

silence-coding avatar May 25 '21 12:05 silence-coding

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");
    }

silence-coding avatar May 25 '21 12:05 silence-coding

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");
  }

silence-coding avatar May 25 '21 12:05 silence-coding