しかし、通信処理や重い計算などでどうしても、一時的にブロックしてしまうことはあり得ます。そんなときはFutureを使えばよいですが、使い方にちょっと注意が必要らしいです。
次の例は5分ごとに"Hello"を返す WebSocket サーバーの実装です。
package controllers import play.api._ import play.api.mvc._ import play.api.libs.iteratee.Iteratee import play.api.libs.iteratee.Enumerator import scala.concurrent.Future import play.api.libs.concurrent.Execution.Implicits._ import scala.language.reflectiveCalls object Application extends Controller { def index = WebSocket.using { req => val in = Iteratee.consume[String]() val out = Enumerator.repeatM { Future { create() } } (in, out) } def create () : String = { Thread.sleep(5 * 60 * 1000) //重い処理を表現 "Hello" } }
Future { } に 重い処理を渡していますが、このときに暗黙の引数としてExecutionContextが必要です。ググると「play.api.libs.concurrent.Execution.Implicits._」をimportするとよいみたいな記事が見つかったので、その通りにしたというところです。
しかし、このサーバーを実行した上で、このwebsocketサービスを利用するページをブラウザでたくさん(5~10)のタブでどんどん開くと、Playサーバーが応答しなくなってしまいます。他のサービスや、静的コンテンツなどもplayのものはすべてbusyになってしまいます。
問題の原因はおそらく、Futureでくるんだ処理が実行される環境(ExecutionContext)です。Playのドキュメント「Understanding Play thread pools」を読むとだいたい次のような事が書いてあります。
- Playでブロックする処理や重い処理を書くときは、それがどのThread Poolで実行されるか意識する必要がある
- Playには目的に応じていくつかのThread Poolがある
- Thread Poolの設定はapplication.confでする
package controllers import play.api._ import play.api.mvc._ import play.api.libs.iteratee.Iteratee import play.api.libs.iteratee.Enumerator import scala.concurrent.Future import scala.language.reflectiveCalls import play.api.libs.concurrent.Akka import play.api.Play.current object Application extends Controller { def index = WebSocket.using { req => val in = Iteratee.consume[String]() val executionContext = Akka.system.dispatcher val out = Enumerator.repeatM { Future { create() } (executionContext) } (in, out) } def create () : String = { Thread.sleep(5 * 60 * 1000) "Hello" } }
環境
- Play 2.1.1
- Scala 2.10.1