encore icon indicating copy to clipboard operation
encore copied to clipboard

Memory leak with futures

Open HadrienRenaud opened this issue 4 years ago • 2 comments

It seems that creating and accessing a future create some non reachable objects.

Minimal example

Tested on branch development at a98629fc40bae8e420a73559bf9a22a40d6352ce.

With the following example, the usage of memory explodes, and quickly fills all the memory available (more than 16GB).

active class A
  def foo(): int
    1
  end
end

active class Main
  def main() : unit
    val a = new A
    val n = 10000000
    repeat i <- n do
      get(a!foo())
    end
  end
end

For an example with memory usage display, you can use this gist. This behavior does not happen with local objects (ie without futures).

First analysis

We suspect that this behaviour is due to some poor garbage collection while doing the get. Precisely, at runtime, the encore.c function actor_save_context that is used by get while blocking the actor, creates a context with pop_context (which internally calls malloc) and this context does not seem to be properly handled.

Valgrind summary:

==32075== LEAK SUMMARY:                                                         
==32075==    definitely lost: 3,776 bytes in 4 blocks                           
==32075==    indirectly lost: 102,400 bytes in 1 blocks                         
==32075==      possibly lost: 307,200 bytes in 3 blocks                         
==32075==    still reachable: 9,827 bytes in 9 blocks                           
==32075==         suppressed: 0 bytes in 0 blocks 

HadrienRenaud avatar Jul 02 '20 09:07 HadrienRenaud

@moy @amaurymaille @lhenrio

HadrienRenaud avatar Jul 02 '20 09:07 HadrienRenaud

The unbounded mem growth is not related to get. val fut = a!foo() still exposes the same behavior. The underlying reason is that Encore only does GC between msg processing. IOW, the mem growth is caused by all the futures generated by a!foo().

Rewriting it in tail-call fashion will give you more or less constant mem usage, because those futures can be collected between msgs.

active class Main
  var a : A
  def main() : unit
    this.a = new A
    val n = 10000000
    this!f(n)
  end

  def f(n:int) : unit
    if n == 0 then
      ()
    else
      get(this.a!foo())
      this!f(n-1)
    end
  end
end

albertnetymk avatar Jul 02 '20 20:07 albertnetymk