在 Future[T] 中递归调用函数时如何缓解内存泄漏?

How do I mitigate memory leaks when recursively calling a function inside a Future[T]?

这个问题的灵感来自早期 Whosebug article 中关于同一主题的一些评论,也受到我正在编写的一些代码的启发。鉴于其中包含的示例,我有点相信这种模式是尾递归的。如果是这种情况,我如何减轻累积期货所造成的内存泄漏,这些期货的底层线程从不加入它们从中产生的 ForkJoinPool?

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()
     }
   }
 }
}

在我分享的示例中,get[A](...) 方法 return 是 Future[Iteratee[Array[Byte],A]]。上面文章的作者我已经发表评论说 "scala.concurrent Futures don't get merged" 一旦他们 return 但是 Twitter 的未来有些如何管理这个。但是,我使用的是 PlayFramework 实现,它使用标准 Scala 2.1X 库提供的期货。

你们中有人有证据支持或驳回这些说法吗?我的代码会造成内存泄漏吗?

您链接的问题(在评论中,而不是在问题或答案中)指的是 flatMap 发生的内存泄漏。您的代码不使用 flatMap,所以我认为您不会 运行 遇到任何问题,因为您只是通过回调有条件地执行另一个 Future,而不是完全不在乎以前的。

无论哪种方式,这都无关紧要,因为此问题已在 2.10.3 中修复。有关详细信息,请参阅修复它的问题 SI-7336 and the pull request

虽然您的调用不会遭受常规递归调用的堆栈溢出危险,但它也不是尾递归。 connect() 的退出语句是 onComplete ,它只是让 connect() 退出并释放其堆栈。 "recursive" 对 connect() 的调用实际上是未来完成时的延续,因此不是实际的递归。

警告:如果 get 实际上总是返回 Future.successful(就像在单元测试中一样),很可能 get可能会发生堆栈溢出,因为 Success 案例希望在 onComplete 退出之前立即触发。我还没有对此进行测试,这可能会发生也可能不会发生,具体取决于 ExecutionContext 的属性。