Finagle で時間のかかる処理をタイムアウトさせる


Finagle で時間のかかる処理を一定時間でタイムアウトさせる方法を調べたのでメモ。

## 環境

試した環境は以下。

```sbt:build.sbt
name := "finagle-example"
scalaVersion := "2.11.7"

resolvers += "twitter-repo" at "https://maven.twttr.com"

libraryDependencies ++= Seq(
  "com.twitter" %% "finagle-core" % "6.31.+",
  "com.twitter" %% "finagle-http" % "6.31.+"
)
```

## TimeoutFilter

よく使う処理という事だからか標準で `com.twitter.finagle.service.TimeoutFilter` が提供されている。

但し、以下のように単純に使ってみたら期待通りにタイムアウトしてくれない。

```scala
import com.twitter.conversions.time._
import com.twitter.finagle.Service
import com.twitter.finagle.service.TimeoutFilter
import com.twitter.finagle.util.DefaultTimer
import com.twitter.util.{Await, Future}
import com.twitter.logging.Logger

class HelloService extends Service[String, String] {
  override def apply(name: String): Future[String] = {
    Thread.sleep(3000)
    Future.value(s"Hello, $name.")
  }
}

object Example {

  private val log = Logger.get(getClass)

  def main(args: Array[String]):Unit = {
    val hello = new HelloService
    val timeoutFilter = new TimeoutFilter[String, String](1.seconds, DefaultTimer.twitter)

    val client = timeoutFilter andThen hello
    val f = client("Finagle")

    f.onSuccess { message =>
      log.info(s"result: ${message}")
    }.onFailure { err: Throwable =>
      log.error(err.toString)
    }
    Await.ready(f)
  }
}
```

実行してみる。

```shell-session
% sbt run
[info] Set current project to finagle-example (in build file:/home/akishin/src/scala/finagle_ex/)
[info] Updating {file:/home/akishin/src/scala/finagle_ex/}finagle_ex...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Compiling 1 Scala source to /home/akishin/src/scala/finagle_ex/target/scala-2.11/classes...
[info] Running Example 
2 22, 2016 10:41:27 午後 Example$$anonfun$main$1 apply
INFO: result: Hello, Finagle.
[success] Total time: 24 s, completed 2016/02/22 22:41:27
```

指定した時間を過ぎているのに `onSuccess` に入って来ている事が判る。

これは以下のように `Service` で `FuturePool.unboundedPool` を使って処理を行うようにすると動作する。

```scala
import com.twitter.conversions.time._
import com.twitter.finagle.Service
import com.twitter.finagle.service.TimeoutFilter
import com.twitter.finagle.util.DefaultTimer
import com.twitter.util.{Await, Future, FuturePool}
import com.twitter.logging.Logger

class HelloService extends Service[String, String] {
  override def apply(name: String): Future[String] = FuturePool.unboundedPool {
    Thread.sleep(3000)
    "Hello, $name."
  }
}

object Example {

  private val log = Logger.get(getClass)

  def main(args: Array[String]):Unit = {
    val hello = new HelloService
    val timeoutFilter = new TimeoutFilter[String, String](1.seconds, DefaultTimer.twitter)

    val client = timeoutFilter andThen hello
    val f = client("Finagle")

    f.onSuccess { message =>
      log.info(s"result: ${message}")
    }.onFailure { err: Throwable =>
      log.error(err.toString)
    }
    Await.ready(f)
  }
}
```

実行してみる。

```shell-session
% sbt run
[info] Set current project to finagle-example (in build file:/home/akishin/src/scala/finagle_ex/)
[info] Compiling 1 Scala source to /home/akishin/src/scala/finagle_ex/target/scala-2.11/classes...
[info] Running Example 
2 22, 2016 10:45:28 午後 Example$$anonfun$main$2 apply
ERROR: com.twitter.finagle.IndividualRequestTimeoutException: exceeded 1.seconds to unspecified while waiting for a response for an individual request, excluding retries
[success] Total time: 6 s, completed 2016/02/22 22:45:29
```

今度は期待通りタイムアウトし、`onFailure` に入ってきてくれた。

## Future#raiseWithin

`TimeoutFilter` を使わずに `Future#raiseWithin` でタイムアウトさせる事もできるっぽい。

この場合も `TimeoutFilter` と同じく以下では期待通りには動かない。

```scala
import com.twitter.conversions.time._
import com.twitter.finagle.Service
import com.twitter.finagle.util.DefaultTimer
import com.twitter.util.{Await, Future}
import com.twitter.logging.Logger

class HelloService extends Service[String, String] {
  override def apply(name: String): Future[String] = {
    Thread.sleep(3000)
    Future.value(s"Hello, $name.")
  }
}

object Example {

  private val log = Logger.get(getClass)

  def main(args: Array[String]):Unit = {
    val hello = new HelloService

    val f = hello("Finagle").raiseWithin(1.seconds)(DefaultTimer.twitter)

    f.onSuccess { message =>
      log.info(s"result: ${message}")
    }.onFailure { err: Throwable =>
      log.error(err.toString)
    }
    Await.ready(f)
  }
}
```

実行してみる。

```shell-session
% sbt run
[info] Set current project to finagle-example (in build file:/home/akishin/src/scala/finagle_ex/)
[info] Compiling 1 Scala source to /home/akishin/src/scala/finagle_ex/target/scala-2.11/classes...
[info] Running Example 
2 22, 2016 11:00:24 午後 Example$$anonfun$main$1 apply
INFO: result: Hello, Finagle.
[success] Total time: 8 s, completed 2016/02/22 23:00:24
```

やはり指定した時間を過ぎているのに `onSuccess` に入ってしまう。

先ほどと同じく `FuturePool.unboundedPool` を使うように書き換えてみる。

```scala
import com.twitter.conversions.time._
import com.twitter.finagle.Service
import com.twitter.finagle.util.DefaultTimer
import com.twitter.util.{Await, Future, FuturePool}
import com.twitter.logging.Logger

class HelloService extends Service[String, String] {
  override def apply(name: String): Future[String] = FuturePool.unboundedPool {
    Thread.sleep(3000)
    "Hello, $name."
  }
}

object Example {

  private val log = Logger.get(getClass)

  def main(args: Array[String]):Unit = {
    val hello = new HelloService

    val f = hello("Finagle").raiseWithin(1.seconds)(DefaultTimer.twitter)

    f.onSuccess { message =>
      log.info(s"result: ${message}")
    }.onFailure { err: Throwable =>
      log.error(err.toString)
    }
    Await.ready(f)
  }
}
```

実行してみる。

```shell-session
% sbt run
[info] Set current project to finagle-example (in build file:/home/akishin/src/scala/finagle_ex/)
[info] Compiling 1 Scala source to /home/akishin/src/scala/finagle_ex/target/scala-2.11/classes...
[info] Running Example 
2 22, 2016 11:01:56 午後 Example$$anonfun$main$2 apply
ERROR: com.twitter.util.TimeoutException: 1.seconds
[success] Total time: 6 s, completed 2016/02/22 23:01:56
```

例外の種類は先ほどと異なるが、こちらも期待通りタイムアウトし、`onFailure` に入ってきてくれた。

## 参考

TimeoutFilter - com.twitter.finagle.service.TimeoutFilter 
https://twitter.github.io/finagle/docs/#com.twitter.finagle.service.TimeoutFilter

Introduction to Finagle 
https://meta.plasm.us/slides/finagle/base/#17

Future - com.twitter.util.Future 
https://twitter.github.io/util/docs/#com.twitter.util.Future

com.twitter.util.Futureのメモ - Qiita 
http://qiita.com/mather314/items/6873a54e856c440afd1d