How do I mitigate memory leaks when recursively ca

2019-08-12 07:57发布

问题:

This question is inspired by some comments in an earlier Stackoverflow article on the same topic and also motivated by some code I'm writing as well. Given the example contained therein, I'm somewhat convinced that this pattern is tail recursive. If this is the case, how do I mitigate the memory leak posed by accumulating futures whose underlying threads never join the ForkJoinPool from which they were spawned?

import com.ning.http.client.AsyncHttpClientConfig.Builder
import play.api.libs.iteratee.Iteratee
import play.api.libs.iteratee.Execution.Implicits.defaultExecutionContext
import play.api.libs.ws.ning.NingWSClient
import scala.util.{Success,Failure}

object Client {
 val client = new NingWSClient(new Builder().build())
 def print = Iteratee.foreach { chunk: Array[Byte] => println(new String(chunk)) }

 def main(args: Array[String]) {
   connect()
   def connect(): Unit = {
     val consumer = client.url("http://streaming.resource.com")
     consumer.get(_ => print).onComplete {
       case Success(s) => println("Success")
       case Failure(f) => println("Recursive retry"); connect()
     }
   }
 }
}

In the example I've shared, the get[A](...) method returns a Future[Iteratee[Array[Byte],A]]. The author of the above article I've included remarks that "scala.concurrent Futures don't get merged" once they return but that Twitter's futures some how manage this. I'm using the PlayFramework implementation, however, which uses futures provided by the standard Scala 2.1X library.

Do any of you have evidence that support or dismiss these claims? Does my code pose a memory leak?

回答1:

The issue you linked (was in a comment, not the question or answer) refers to a memory leak that occurred with flatMap. Your code doesn't use flatMap, so I don't think you wouldn't run into any problems, as you're just conditionally executing another Future via a callback, and not caring at all about the previous.

Either way, it's kind of irrelevant because this issue was fixed in 2.10.3. See issue SI-7336 and the pull request that fixes it for more info.



回答2:

While your call does not suffer from the stack overflow dangers of regular recursive calls, it is not tail recursive either. The exit statement of connect() is onComplete which simply lets connect() exit and release its stack. The "recursive" call to connect() actually happens as a continuation when the future completes and therefore is not an actual recursion.

Caveat: if get actually always returned a Future.successful (as it might in a unit test), chances are that get could suffer from a stack overflow, since the Success case would like trigger immediately and before onComplete exited. I haven't tested this and it might be something that may or may not happen depending on the ExecutionContext's properties.