Grailsでワーカスレッドを作ってそこからHibernateで永続化したい、んだけれど…
coroidでは、動画変換serviceを作って、そこで動画の処理を行っています。その中で、処理の経過を永続化しています。
class NicoContentService
def transcode(){
// (1)処理開始状態を永続化
// (2)変換処理
// (3)処理終了状態を永続化
}
}
(2)が重たい処理なので、ワーカスレッドを作成し、処理完了を待たずに制御を戻すことにしました。
class NicoContentService
def transcode(){
Thread.start{
// (1)処理開始状態を永続化
// (2)変換処理
// (3)処理終了状態を永続化
}
}
}
coroid ver.0.1.0まではこのような実装でした。これでGrails1.1.1ではうまく動いていたのですが、1.2.1にバージョンを上げるとこちらに書かれている通り、
org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
という例外が出るようになりました。これは、Thread.startで作ったワーカスレッドがHibernateのセッションを持っていないので発生したのでしょう。1.1.1では、セッションが必要になればGrailsフレームワーク側で自動で取得していたのではないかと想像します。また、1.2.1でも、Grailsフレームワーク側を変更すればそのような動作にできるようです(参考)。Grailsの中で使用しているSpringFrameworkのHibernateTemplateクラスコンストラクタに渡す引数を変更する、という対応のようです。
coroidはGrailsを含めて配布しているわけではないので、上記の対応は採用できません。他の方法を探したところ、BackgroundThread pluginというものがありました。このプラグインの説明に以下のような記述がありましたので、問題を解消できるだろうと考えました。
Beyond the inherent difficulty of concurrent programming, Grails had a few other difficulties: most significantly, the Hibernate session is bound to the thread, and so threads need to do their own Hibernate session management. This plugin takes care of those issues.
このプラグインを用い、以下のようにソースを変更しました。これがcoroid ver.0.2.0です。
class NicoContentService
def transcode(){
backgroundService.execute("Working for converting",{
// (1)処理開始状態を永続化
// (2)変換処理
// (3)処理終了状態を永続化
})
}
}
ここからが問題です。
こちらに記載した通り、次のバージョンでは処理の進捗率を表示させたいと考えています。この情報は、さきゅばすが通知するステータステキスト(さきゅばすをGUIで起動して変換処理を行ったとき、画面下部のステータスバーに表示される情報です)を加工して永続化すれば良いと考えました。
この通知は、さきゅばすが作成するワーカスレッドが行います。つまり、この処理を追加することで、backgroundServiceワーカスレッドとはまた別のスレッドでHibernateを利用する必要がでてきた、ということです。
backgroundThread pluginのソースコードBackgroundThreadManager.javaを見ると、Hibernateセッションを以下のように取り扱っていました。パッケージ名を見るにつけ、どうやらSpring Frameworkの機能を利用しているようです。
開始時(bind):
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
終了時(unbind):
SessionHolder sessionHolder = (SessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
TransactionSynchronizationManager.unbindResource(sessionFactory);
SessionFactoryUtils.closeSession(sessionHolder.getSession());
上記と同じようなコードを、さきゅばすのワーカスレッド永続化処理に追加してみたのですが、”(3)処理終了状態を永続化”のところで以下の例外が出ました。
ERROR events.PatchedDefaultFlushEventListener - Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect):
さて、これをどう解決すればよいやら。Springも絡んできそうな様相なのですが、さっぱり知らないし、というところです。
« coroid ver.0.3.0 全ランキングと詳細情報表示対応 | トップページ | Android web browserのajax(asynchronous request)の挙動が変だ »
この記事へのコメントは終了しました。
« coroid ver.0.3.0 全ランキングと詳細情報表示対応 | トップページ | Android web browserのajax(asynchronous request)の挙動が変だ »
コメント