2013年6月4日火曜日

PlayFramework2.1.1環境で、ScalaからJNAへセットしたコールバックが戻ってこなくなる件

先日の投稿で、scalaからJNAのコールバックを設定するところを
object Tekito {
 lib.setFunction(idx, new JnaCallback {
  def onMessage(msg: String) = println("onMessage " + idx + ": " + msg)
 })
}
こんな感じで書いていたんだけど、実際の環境で上の書き方でコールバックをセットした後、しばらくすると不規則なタイミングでコールバックが返ってこなくなるケースが多発して困っていた。
で、なんとなく次のように変えてみる。
object Tekito {
 def callback = new JnaCallback {
  def onMessage(msg: String) = println("onMessage " + idx + ": " + msg)
 }
 lib.setFunction(idx, callback)
}
これでも状況は変わらず、しばらくするとコールバックが返ってこなくなる。
ただ、コールバックのセットをし直すと、コールバックがまた返ってくるようになることを発見したので、症状が出る前と後で
println(Tekito.callback)
してみると、
正常時
models.Tekito$$anon$1@5ca99bc
コールバックが返ってこなくなった後
models.Tekito$$anon$1@3833089c
{ぬおーっ!アドレス変ってるじゃないかー!}
c側でコールバックを保持しているのは、ただの関数ポインタなので、これではコールバックは返ってこない。
{これ、どうしてくれようか}
と思ったが、素直に下のようにしてみたら
object Tekito {
 val callback = new JnaCallback {
  def onMessage(msg: String) = println("onMessage " + idx + ": " + msg)
 }
 lib.setFunction(idx, callback)
}
こうしてみたら、Tekito.callbackのアドレスが固定されてコールバックが安定して返ってくるようになった。
私は、JVMやscalaのメモリ管理を詳しく知らないので、これで本当にアドレスが固定されるのか確信がないのだが、とりあえず今のところはアドレスが変ってしまうケースはなくなった。
なので、
「scalaからJNAのコールバックをセットする時は、コールバックをvalに入れておく」
をお勧めします。

追記 2013/06/19
後で気が付いたのですが、コールバックオブジェクトを変数に入れておかないと、そのオブジェクトはどこからも参照されていない状態になりGCが動いた時に掃除されてしまうので、コールバックが迷子になる。が正解っぽいです。
コールバックが迷子になるタイミングが不規則だったのは、GCが動くタイミングが不規則だからで、c側でコールバックを保持していてもJVMのメモリとは当然関係ないので、GCに片付けられてしまうということのようです。