--
You received this message because you are subscribed to the Google Groups "Play framework dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework-...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
--
import scala.concurrent._
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
import scala.util._
object FutureLeak extends App {
val step = 100000
val upper = 1000000 //1500000000
// code that leaks
def loop(future: Future[Int]): Future[Int] = {
future.flatMap { i =>
if (i % step == 0) println(i)
if (i < upper)
loop(Future(i + 1))
else Future(i)
}
}
// code that does not leak
def loop2(future: Future[Int]): Future[Int] = {
val promise = Promise[Int]
def inloop(future: Future[Int]): Unit = {
future.onComplete {
case Success(i) if i < upper =>
if (i % step == 0) println(i)
inloop(Future(i + 1))
case Success(i) =>
println("done with " + i)
promise.success(i)
}
}
inloop(future)
promise.future
}
Await.result(loop(Future(0)), 100 seconds) // leads to java.lang.OutOfMemoryError: Java heap space with -Xms32m -Xmx32m
Await.result(loop2(Future(0)), 100 seconds) // runs fine with -Xms32m -Xmx32m
}
I ran into this problem with ReactiveMongo cursors (which use Enumerators - and so Scala Futures).
Hope that it helps,
Your creating a looooong chain of futures that depend on eachother and of course they cant be collected until they start resolving.
What would a solution look like?
Cheers,
V
Your creating a looooong chain of futures that depend on eachother
and of course they cant be collected until they start resolving.
On Fri, Apr 5, 2013 at 5:40 PM, Viktor Klang <viktor...@typesafe.com> wrote:
Your creating a looooong chain of futures that depend on eachother
Actually not, the next future is NOT created until the first is fulfilled.
and of course they cant be collected until they start resolving.
They are resolved one at a time.
One thought is whether Scala closures play in here since they always seem to have an outer-pointer. Will have a look next week if this is the case.
So what is the solution?We could improve garbage collection in some cases by using weak references. But that's quite expensive and still wouldn't fix the problem in this example program.We really need a way to avoid creating an unlimited length f-future to flatMap-future chain. Could we somehow propagate the initial flatMap-future's promise through the chain? It seems wasteful to create a chain of promises and onComplete handlers which will all eventually contain the same value.Here's how a single promise can be used if it is coded explicitly. Maybe there's an automatic way to do the same thing somehow?def loop(future: Future[Int], p: Promise[Int]) {future.foreach { i =>if (i % step == 0) println(i)if (i < upper)loop(Future(i + 1), p)else p.success(i)}}
CheersRich
--
You received this message because you are subscribed to the Google Groups "Play framework dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework-...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
I profiled the memory leak yesterday. Here's the chain in more detail, beginning with the promise that is returned by flatMap.p$4: Promise$DefaultPromise ← promise n, created by flatMap_ref: $colon$colon ← promise n's internal statehd: CallbackRunnable ← the list contained in the promise n's internal stateonComplete: Future$$anonfun$flatMap$1$$anonfun$apply$3 ← runs on completion of promise n, attached by flatMap$outer: Future$$anonfun$flatMap$1 ← outer pointer, referencing promise n-1 created by flatMap…There's an outer pointer here, referencing promise n-1. But (as far as I can tell) we need to reference promise n-1 so we can complete it when promise n is complete. Restructuring the closure won't help, because we somehow need to complete promise n-1 on completion of promise n, and we presumably need some sort of reference from pormise n to promise n-1 to achieve that.More concretely, consider the following snippet.Future(1).flatMap(a => Future(2).flatMap(b => Future(3)))There are five futures here. Three futures are explicitly created, and two are created when flatMap is called. Let's call these objects f1, f2, f3, fm1 and fm2.Working backwards, here is the chain that causes the leak.1. The promise fm2 is created by the second call to flatMap. It can only complete when f3 is complete. So there is a pointer from f3's completion handler to fm2.
2. The promise fm1 is created by the first call to flatMap. It can only complete when fm2 is complete. So there is a pointer from fm2's completion handler back to fm1.3. This chain remains in place until f3 eventually completes.
Now consider Stephane's snippet.if (i < upper) loop(Future(i + 1)) else Future(i)
def loop(future: Future[Int]): Future[Int] = {
future.flatMap { i =>
}
}Let's call the futures returned by each invocation of loop fm1, fm2, fm3, etc.So, upon calling loop, fm1 is returned immediately. Eventually loop will be called again and fm2 will be returned. The eventual value of fm1 depends on that of fm2, so a completion handler is attached to fm2 pointing back to fm1. This is the first link of the chain, a reference from fm2 back to fm1, via fm2's completion handler.Each call to loop adds another link to the chain, from fm(n) back to fm(n-1). The chain remains in place until the future produced by the final iteration of loop completes. Make n big enough and an OOME is the result. :(
On Sun, Apr 7, 2013 at 1:50 AM, Viktor Klang <viktor...@typesafe.com> wrote:
Lets just first check what is leaking what, it might be that the closure leaks through the outer pointer and hten moving the function creation out to a companion object might alleviate that as thisFuture wouldn't be captured in the closure attached to thatFuture.
On Sat, Apr 6, 2013 at 10:16 PM, Rich Dougherty <ri...@rd.gen.nz> wrote:
Each call to loop adds another link to the chain, from fm(n) back to fm(n-1). The chain remains in place until the future produced by the final iteration of loop completes. Make n big enough and an OOME is the result. :(Thanks for the hard work Rich!Alright, so if I interpret this correctly: We're being killed by the chain of Promises created by the loop.The question is if this is fixable without breaking any semantics currently provided, and what such a fix would look like.Ideas?
A few considerations here:1) In practice, we control nearly all the types of futures used by iteratees. The only type we don't is things like calls returned by Enumerator.generateM, but those futures should be short lived anyway. When you flatMap an iteratee, we control the future. This means we can create our own future implementations that do this. We've already got one of our own, play.core.server.netty.NettyPromise. We could also create our own Future.successful, and Promise.2) Again in practice, the only time when this memory leak really matters is for infinite streams. Since the stream is infinite, the result of the iteratee is irrelevant (and is often Unit anyway). In these cases, it is not necessary to carry to the result of the futures through the chain, therefore I think our implementations here (eg the enumerators in PlayDefaultUpstreamHandler) can probably be modified to not strictly implement their specs, because they don't need to.
A few considerations here:1) In practice, we control nearly all the types of futures used by iteratees. The only type we don't is things like calls returned by Enumerator.generateM, but those futures should be short lived anyway. When you flatMap an iteratee, we control the future. This means we can create our own future implementations that do this. We've already got one of our own, play.core.server.netty.NettyPromise. We could also create our own Future.successful, and Promise.
2) Again in practice, the only time when this memory leak really matters is for infinite streams. Since the stream is infinite, the result of the iteratee is irrelevant (and is often Unit anyway). In these cases, it is not necessary to carry to the result of the futures through the chain, therefore I think our implementations here (eg the enumerators in PlayDefaultUpstreamHandler) can probably be modified to not strictly implement their specs, because they don't need to.
On Sun, Apr 7, 2013 at 4:33 AM, James Roper <james...@typesafe.com> wrote:
A few considerations here:1) In practice, we control nearly all the types of futures used by iteratees. The only type we don't is things like calls returned by Enumerator.generateM, but those futures should be short lived anyway. When you flatMap an iteratee, we control the future. This means we can create our own future implementations that do this. We've already got one of our own, play.core.server.netty.NettyPromise. We could also create our own Future.successful, and Promise.Possible, but that will make it extremely hard to maintain. It is very easy to get this memory leak without knowing.
2) Again in practice, the only time when this memory leak really matters is for infinite streams. Since the stream is infinite, the result of the iteratee is irrelevant (and is often Unit anyway). In these cases, it is not necessary to carry to the result of the futures through the chain, therefore I think our implementations here (eg the enumerators in PlayDefaultUpstreamHandler) can probably be modified to not strictly implement their specs, because they don't need to.Not really. consider Enumerator.repeat(...) &> Enumeratee.take(1000)Here the result is not unit.Solving the problem only for Play is a good alternative. But it should be clear how to avoid it. Or otherwise we get into a maintenance hell.