Scala Future和TimeoutException:如何知道根本原因?

Scala Future和TimeoutException:如何知道根本原因?,scala,asynchronous,future,Scala,Asynchronous,Future,假设我有以下代码: val futureInt1 = getIntAsync1(); val futureInt2 = getIntAsync2(); val futureSum = for { int1 <- futureInt1 int2 <- futureInt2 } yield (int1 + int2) val sum = Await.result(futureSum, 60 seconds) 我怎么知道getIntAsync1或getIntAsync2中的

假设我有以下代码:

val futureInt1 = getIntAsync1();
val futureInt2 = getIntAsync2();

val futureSum = for {
  int1 <- futureInt1
  int2 <- futureInt2
} yield (int1 + int2) 

val sum = Await.result(futureSum, 60 seconds)
我怎么知道
getIntAsync1
getIntAsync2
中的哪一个仍然挂起并实际导致超时

请注意,这里我将2个期货与zip合并,这是一个简单的问题示例,但在我的应用程序中,我有不同级别的此类代码(即
getIntAsync1
本身可以使用
Future.zip
Future.sequence
,map/flatMap/applicative)

不知何故,我希望能够在主线程出现超时时记录挂起的并发操作stacktraces,这样我就可以知道系统上的bottlenexts在哪里



我有一个现有的遗留API后端,它还没有完全反应,不会很快。我试图通过使用并发来增加响应时间。但自从使用这种代码以来,理解为什么我的应用程序中有些东西需要花费很多时间变得更加痛苦。如果您能提供任何提示来帮助我调试这些问题,我将不胜感激。

您可以通过调用其isComplete方法来检查future是否已完成

if (futureInt1.isComplete) { /*futureInt2 must be the culprit */ } 
if (futureInt2.isComplete) { /*futureInt1 must be the culprit */ } 

作为第一种方法,我建议将您的未来[Int]提升到未来[Try[Int]]。诸如此类:

object impl {

  def checkException[T](in: Future[T]): Future[Try[T]] = 
    in.map(Success(_)).recover {
      case e: Throwable => {   
        Failure(new Exception("Error in future: " + in))
      }
    }

  implicit class FutureCheck(s: Future[Int]) {
   def check = checkException(s)
  }
}
object test {

  import impl._

  val futureInt1 = Future{ 1 }
  val futureInt2 = Future{ 2 }

  def combine(a: Try[Int], b: Try[Int])(f: (Int, Int) => (Int)) : Try[Int] = {
    if(a.isSuccess && b.isSuccess) {
      Success(f(a.get, b.get))
    }
   else 
     Failure(new Exception("Error adding results"))
  }

  val futureSum = for {
    int1 <- futureInt1.check
    int2 <- futureInt2.check
  } yield combine(int1, int2)(_ + _)
}
然后使用一个小函数来组合结果,如下所示:

object impl {

  def checkException[T](in: Future[T]): Future[Try[T]] = 
    in.map(Success(_)).recover {
      case e: Throwable => {   
        Failure(new Exception("Error in future: " + in))
      }
    }

  implicit class FutureCheck(s: Future[Int]) {
   def check = checkException(s)
  }
}
object test {

  import impl._

  val futureInt1 = Future{ 1 }
  val futureInt2 = Future{ 2 }

  def combine(a: Try[Int], b: Try[Int])(f: (Int, Int) => (Int)) : Try[Int] = {
    if(a.isSuccess && b.isSuccess) {
      Success(f(a.get, b.get))
    }
   else 
     Failure(new Exception("Error adding results"))
  }

  val futureSum = for {
    int1 <- futureInt1.check
    int2 <- futureInt2.check
  } yield combine(int1, int2)(_ + _)
}
对象测试{
导入impl_
val futureInt1=未来{1}
val futureInt2=未来{2}
def combine(a:Try[Int],b:Try[Int])(f:(Int,Int)=>(Int)):Try[Int]={
如果(a.isSuccess&&b.isSuccess){
成功(f(a.get,b.get))
}
其他的
失败(新异常(“添加结果时出错”))
}
val futureSum=用于{
int1
val futureInt1=getIntAsync1();
val futureInt2=getIntAsync2();
val futureSum=用于{
int1 println(“我们得到了超时。未完成的未来是:”+列表(futureInt1,futureInt2)。过滤器(!\u0.isCompleted)
}

建议的解决方案将for块中的每个未来包装成需要超时和名称的TimelyFuture。在内部,它使用WAIT来检测单个超时。 请记住,这种使用未来的方式不适用于生产代码,因为它使用阻塞。它仅用于诊断,以确定哪些未来需要时间才能完成

package xxx

import java.util.concurrent.TimeoutException

import scala.concurrent.{Future, _}
import scala.concurrent.duration.Duration
import scala.util._
import scala.concurrent.duration._

class TimelyFuture[T](f: Future[T], name: String, duration: Duration) extends Future[T] {

  override def onComplete[U](ff: (Try[T]) => U)(implicit executor: ExecutionContext): Unit = f.onComplete(x => ff(x))

  override def isCompleted: Boolean = f.isCompleted

  override def value: Option[Try[T]] = f.value

  @scala.throws[InterruptedException](classOf[InterruptedException])
  @scala.throws[TimeoutException](classOf[TimeoutException])
  override def ready(atMost: Duration)(implicit permit: CanAwait): TimelyFuture.this.type = {
    Try(f.ready(atMost)(permit)) match {
      case Success(v) => this
      case Failure(e) => this
    }
  }

  @scala.throws[Exception](classOf[Exception])
  override def result(atMost: Duration)(implicit permit: CanAwait): T = {
    f.result(atMost)
  }

  override def transform[S](ff: (Try[T]) => Try[S])(implicit executor: ExecutionContext): Future[S] = {
    val p = Promise[S]()
    Try(Await.result(f, duration)) match {
      case s@Success(_) => ff(s) match {
        case Success(v) => p.success(v)
        case Failure(e) => p.failure(e)
      }
      case Failure(e) => e match {
        case e: TimeoutException => p.failure(new RuntimeException(s"future ${name} has timed out after ${duration}"))
        case _ => p.failure(e)
      }
    }
    p.future
  }

  override def transformWith[S](ff: (Try[T]) => Future[S])(implicit executor: ExecutionContext): Future[S] = {
    val p = Promise[S]()
    Try(Await.result(f, duration)) match {
      case s@Success(_) => ff(s).onComplete({
        case Success(v) => p.success(v)
        case Failure(e) => p.failure(e)
      })
      case Failure(e) => e match {
        case e: TimeoutException => p.failure(new RuntimeException(s"future ${name} has timed out after ${duration}"))
        case _ => p.failure(e)
      }
    }
    p.future
  }
}

object Main {

  import scala.concurrent.ExecutionContext.Implicits.global

  def main(args: Array[String]): Unit = {
    val f = Future {
      Thread.sleep(5);
      1
    }

    val g = Future {
      Thread.sleep(2000);
      2
    }

    val result: Future[(Int, Int)] = for {
      v1 <- new TimelyFuture(f, "f", 10 milliseconds)
      v2 <- new TimelyFuture(g, "g", 10 milliseconds)
    } yield (v1, v2)


    val sum = Await.result(result, 1 seconds) // as expected, this throws exception : "RuntimeException: future g has timed out after 10 milliseconds"
  }
}
xxx包
导入java.util.concurrent.TimeoutException
导入scala.concurrent.{Future,}
导入scala.concurrent.duration.duration
导入scala.util_
导入scala.concurrent.duration_
类TimelyFuture[T](f:Future[T],名称:String,持续时间:duration)扩展Future[T]{
覆盖def onComplete[U](ff:(Try[T])=>U)(隐式执行器:ExecutionContext):Unit=f.onComplete(x=>ff(x))
覆盖def isCompleted:Boolean=f.isCompleted
覆盖def值:选项[Try[T]]=f.value
@抛出[InterruptedException](类[InterruptedException])
@scala.throws[TimeoutException](类[TimeoutException])
覆盖def就绪(大气:持续时间)(隐式许可:CanAwait):TimelyFuture.this.type={
尝试(f.ready(atMost)(许可))匹配{
案例成功(v)=>此
案例失败(e)=>此
}
}
@抛出[Exception](classOf[Exception])
覆盖def结果(大气:持续时间)(隐式许可:CanAwait):T={
f、 结果(atMost)
}
重写def转换(ff:(Try[T])=>Try[S])(隐式执行器:ExecutionContext):未来[S]={
val p=承诺[S]()
尝试(等待结果(f,持续时间))匹配{
案例s@Success())=>ff(s)匹配{
案例成功率(v)=>p.Success(v)
案例失败(e)=>p.失败(e)
}
案例失败(e)=>e匹配{
案例e:TimeoutException=>p.failure(新的RuntimeException(s“future${name}在${duration}之后超时”))
案例=>p.failure(e)
}
}
p、 未来
}
重写def transformWith[S](ff:(Try[T])=>Future[S])(隐式执行器:ExecutionContext):Future[S]={
val p=承诺[S]()
尝试(等待结果(f,持续时间))匹配{
案例s@Success()=>ff(s).未完成({
案例成功率(v)=>p.Success(v)
案例失败(e)=>p.失败(e)
})
案例失败(e)=>e匹配{
案例e:TimeoutException=>p.failure(新的RuntimeException(s“future${name}在${duration}之后超时”))
案例=>p.failure(e)
}
}
p、 未来
}
}
对象主体{
导入scala.concurrent.ExecutionContext.Implicits.global
def main(参数:数组[字符串]):单位={
val f=未来{
睡眠(5);
1.
}
val g=未来{
《睡眠》(2000年);
2.
}
val结果:未来[(Int,Int)]=for{

v1关键是要认识到,在您的示例中,未来不会超时,是您的调用线程最多暂停X次

因此,如果你想在期货中建立时间模型,你应该在每个分支上使用zipWith,并在一定时间内使用包含值的期货。如果你使用Akka,那么你可以使用
Akka.pattern.after
以及Future.firstCompletedOf

现在,即使你这样做了,你如何找出你的任何未来没有及时完成的原因,也许它们依赖于其他没有完成的未来

package xxx

import java.util.concurrent.TimeoutException

import scala.concurrent.{Future, _}
import scala.concurrent.duration.Duration
import scala.util._
import scala.concurrent.duration._

class TimelyFuture[T](f: Future[T], name: String, duration: Duration) extends Future[T] {

  override def onComplete[U](ff: (Try[T]) => U)(implicit executor: ExecutionContext): Unit = f.onComplete(x => ff(x))

  override def isCompleted: Boolean = f.isCompleted

  override def value: Option[Try[T]] = f.value

  @scala.throws[InterruptedException](classOf[InterruptedException])
  @scala.throws[TimeoutException](classOf[TimeoutException])
  override def ready(atMost: Duration)(implicit permit: CanAwait): TimelyFuture.this.type = {
    Try(f.ready(atMost)(permit)) match {
      case Success(v) => this
      case Failure(e) => this
    }
  }

  @scala.throws[Exception](classOf[Exception])
  override def result(atMost: Duration)(implicit permit: CanAwait): T = {
    f.result(atMost)
  }

  override def transform[S](ff: (Try[T]) => Try[S])(implicit executor: ExecutionContext): Future[S] = {
    val p = Promise[S]()
    Try(Await.result(f, duration)) match {
      case s@Success(_) => ff(s) match {
        case Success(v) => p.success(v)
        case Failure(e) => p.failure(e)
      }
      case Failure(e) => e match {
        case e: TimeoutException => p.failure(new RuntimeException(s"future ${name} has timed out after ${duration}"))
        case _ => p.failure(e)
      }
    }
    p.future
  }

  override def transformWith[S](ff: (Try[T]) => Future[S])(implicit executor: ExecutionContext): Future[S] = {
    val p = Promise[S]()
    Try(Await.result(f, duration)) match {
      case s@Success(_) => ff(s).onComplete({
        case Success(v) => p.success(v)
        case Failure(e) => p.failure(e)
      })
      case Failure(e) => e match {
        case e: TimeoutException => p.failure(new RuntimeException(s"future ${name} has timed out after ${duration}"))
        case _ => p.failure(e)
      }
    }
    p.future
  }
}

object Main {

  import scala.concurrent.ExecutionContext.Implicits.global

  def main(args: Array[String]): Unit = {
    val f = Future {
      Thread.sleep(5);
      1
    }

    val g = Future {
      Thread.sleep(2000);
      2
    }

    val result: Future[(Int, Int)] = for {
      v1 <- new TimelyFuture(f, "f", 10 milliseconds)
      v2 <- new TimelyFuture(g, "g", 10 milliseconds)
    } yield (v1, v2)


    val sum = Await.result(result, 1 seconds) // as expected, this throws exception : "RuntimeException: future g has timed out after 10 milliseconds"
  }
}

问题归结为:您是否正在尝试对吞吐量进行一些根本原因分析?然后您应该监控您的执行上下文,而不是您的未来。未来只是价值。

如果您只是寻找单个未来需要很长时间(或与其他相结合)的信息指标,您最好在创建期货时使用包装器来记录指标:

    object InstrumentedFuture {
        def now() = System.currentTimeMillis()
        def apply[T](name: String)(code: => T): Future[T] = {
           val start = now()
           val f = Future { 
            code 
           }
           f.onComplete {
              case _ => println(s"Future ${name} took ${now() - start} ms") 
           }
           f
        }
    }


    val future1 = InstrumentedFuture("Calculator") { /*...code...*/ }
    val future2 = InstrumentedFuture("Differentiator") { /*...code...*/ }

什么是
结果
?我想应该是
futureSum
超时的不是int1或int2,而是Yield创造的新未来我希望我知道答案:)让我们这样考虑。假设我们在for块中有更多的功能。当等待超时时,多个未来可能不完整。您想收集所有这些未来吗?如果是,您可以