encore
encore copied to clipboard
Memory leak with futures
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
@moy @amaurymaille @lhenrio
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