Finagle で時間のかかる処理を一定時間でタイムアウトさせる方法を調べたのでメモ。
環境
試した環境は以下。
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
が提供されている。
但し、以下のように単純に使ってみたら期待通りにタイムアウトしてくれない。
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)
}
}
実行してみる。
% 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
を使って処理を行うようにすると動作する。
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)
}
}
実行してみる。
% 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
と同じく以下では期待通りには動かない。
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)
}
}
実行してみる。
% 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
を使うように書き換えてみる。
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)
}
}
実行してみる。
% 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