electronicarts / ea-async

EA Async implements async-await methods in the JVM.
https://go.ea.com/ea-async
Other
1.38k stars 129 forks source link

Await in loops cause memory leak #56

Open zekronium opened 3 years ago

zekronium commented 3 years ago

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.

dwing4g commented 3 years ago

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

zekronium commented 3 years ago

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?

silence-coding commented 3 years ago

How to solve the problem

silence-coding commented 3 years ago

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 commented 3 years ago

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