Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

複数スレッドに渡るTransactionへの対応 #6

Open
manhole opened this issue Apr 25, 2014 · 11 comments
Open

複数スレッドに渡るTransactionへの対応 #6

manhole opened this issue Apr 25, 2014 · 11 comments

Comments

@manhole
Copy link
Contributor

manhole commented Apr 25, 2014

http://www.playframework.com/documentation/2.2.x/JavaAsync
にあるように1リクエストの間にF.Promiseでスレッドを切り替えても、トランザクションを保てるようにしている最中です。
(ThreadLocalを使ったトランザクション実装が多く、それではThreadが変わると別トランザクションになってしまう)

PR #5 までで、スレッドが変わってもトランザクションを保てるようにしたつもりです。

これから、DBFluteのAccessContextを対応しようと考えていますが、他の箇所でもThreadLocalを使っているようなのでまとめて相談しておきたいです。

dbflute-1.0.5D では10箇所(9ファイル)で使っているようです。

$ ack ThreadLocal
dbflute/embedded/templates/om/java/other/hibernate/allcommon/HibernateAccessContext.vm
33:    private static final ThreadLocal<${glAccessContext}> _threadLocal = new ThreadLocal<${glAccessContext}>();

dbflute/src/main/java/org/seasar/dbflute/helper/jdbc/context/DfDataSourceContext.java
26:    private static ThreadLocal<DataSource> _threadLocal = new ThreadLocal<DataSource>();

dbflute-runtime/src/main/java/org/seasar/dbflute/AccessContext.java
38:    private static final ThreadLocal<AccessContext> _threadLocal = new ThreadLocal<AccessContext>();

dbflute-runtime/src/main/java/org/seasar/dbflute/bhv/core/ContextStack.java
43:    private static ThreadLocal<Stack<ContextStack>> _threadLocal = new ThreadLocal<Stack<ContextStack>>();

dbflute-runtime/src/main/java/org/seasar/dbflute/CallbackContext.java
34:    private static final ThreadLocal<CallbackContext> _threadLocal = new ThreadLocal<CallbackContext>();

dbflute-runtime/src/main/java/org/seasar/dbflute/cbean/ConditionBeanContext.java
38:    private static final ThreadLocal<ConditionBean> _conditionBeanLocal = new ThreadLocal<ConditionBean>();
79:    private static final ThreadLocal<EntityRowHandler<? extends Entity>> _entityRowHandlerLocal = new ThreadLocal<EntityRowHandler<? extends Entity>>();

dbflute-runtime/src/main/java/org/seasar/dbflute/cbean/FetchAssistContext.java
29:    private static ThreadLocal<FetchBean> _threadLocal = new ThreadLocal<FetchBean>();

dbflute-runtime/src/main/java/org/seasar/dbflute/outsidesql/OutsideSqlContext.java
50:    private static final ThreadLocal<OutsideSqlContext> _threadLocal = new ThreadLocal<OutsideSqlContext>();

dbflute-runtime/src/main/java/org/seasar/dbflute/resource/InternalMapContext.java
34:    private static final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>();

dbflute-runtime/src/main/java/org/seasar/dbflute/resource/ManualThreadDataSourceHandler.java
36:    private static final ThreadLocal<ManualThreadDataSourceHandler> _handlerLocal = new ThreadLocal<ManualThreadDataSourceHandler>();

dbflute-runtime/src/main/java/org/seasar/dbflute/resource/ResourceContext.java
52:    private static final ThreadLocal<ResourceContext> threadLocal = new ThreadLocal<ResourceContext>();

少なくともAccessContextは対応する必要があると考えていますが、他の箇所はどうでしょうか?

  • 対応が必要な箇所を洗い出す
  • 必要だとして、どう対応するのが良いか

について相談させていただけますか。

補足

PR #5までで対応したぶんは、対応が必要かどうかは次のように判断しました。

  • ThreadLocalを使用している箇所を洗い出す。
  • そのThreadLocalへ保持する期間が、単一スレッド内なのか、それとも複数スレッドに渡るのかを判断する。(1 Promiseで閉じているかどうか)
  • 複数スレッドに渡る場合は対応が必要。

そして対応が必要な場合は、ThreadLocalではなくPlayのHttp.Contextを使うように変えました。
(Http.Contextは、1リクエストの間 生存しています。)

@jflute
Copy link
Contributor

jflute commented Apr 25, 2014

HibernateAccessContext は、Hibernate 自動生成のときのためだけなので無関係。
DfDataSourceContext は、自動生成ツールとしてのDBFluteのクラスなので無関係。

そして、AccessContext, CallbackContext 以外は、
「Behaviorのメソッド内で閉じられた利用しかされないスレッドローカル」
なので、ぼくのわかってる範囲での話では、対応は不要かなと思います。

CallbackContext は、特殊要件のものなので、とりあえず優先度は低いかなと。
http://dbflute.seasar.org/ja/manual/function/genbafit/runtimefit/sqlloghandler/

AccessContextは対応しないと。。。ちょっとお待ちを

@manhole
Copy link
Contributor Author

manhole commented Apr 27, 2014

ありがとうございます、Behaviorに閉じているものはThreadLocalのままで良さそうですね。
(その対応が必要になるのは、非同期なJDBCドライバが登場するときになるのかな...)

ちょっと考慮が必要なのは、全部をHttp.Contextへ置換すれば良いわけではなさそうな点です。
例えばrequestと独立したタスクを動かしてるケースは、Http.Contextがいないと思います。
(#5 での対応では不十分ということですね。このコメントを書いていて気づきました...)

なので、単純な置換ではなくて、ThreadLocalっぽいものを代替するインタフェースをアプリケーションに要求するふうになるのでしょうか。
AccessContextに限れば、AccessContextResolverのようなインタフェースもアリかと。

@jflute
Copy link
Contributor

jflute commented Apr 27, 2014

確かに、バッチのときはHttpがいないから、
要は利用するスレッドローカルをアプリで指定できるようにしないとねってとこだね。

いったん、Runtimeをいじらないでも動くような形で暫定対応してみて、
その後 Runtime を修正するときに根本対応してみたいと思います。
(GW中になんとかやるぞー!(></)

@jflute
Copy link
Contributor

jflute commented Apr 28, 2014

ひとまず、スーパーミラクルワンダフルべたべたの方法を思い付きました。

commonColumnMap.dfpropにて、
(($$AccessContext$$)play.mvc.Http.Context.current.get().args.get("df:AccessContext")).getAccessTimestamp()
という風に書けば、Http.ContextからAccessContextを取得できます。
もちろん、バッチアプリじゃダメだけど。
(これは単にローカルで試しただけ)

次は、ちょっとだけDBFluteのテンプレートをいじって、
メソッドのオーバーライドで対応できるようにしたいなと。
その次は、Runtimeをいじって、AccessContext自体で対応できるように。

ちなみに、自動生成を試してみて気付いたdfpropの修正ポイントを直しておきました。
(こちらはコミット済みです)

<< basicInfoMap.dfprop >>
generateOutputDirectory -> ../app
resourceOutputDirectory -> ../conf
※自動生成されるクラスやdiconの出力先を調整

<< databaseInfoMap.dfprop >>
url -> jdbc:h2:file:../conf/exampledb/exampledb
※参照するH2データベースの位置を調整

<< dependencyInjectionMap.dfprop >>
j2eeDiconResourceName -> app-j2ee.dicon
※dbflute.diconがincludeするj2ee.diconを差し替え

<< replace-schema.sh >>
SAStrutsExample専用の記述が残っていたのを削除

@jflute
Copy link
Contributor

jflute commented May 6, 2014

次のバージョン(1.0.5F)で、AccessContextならびにCallbackContextにて、
スレッドローカルを差し替えるようにできるようにしました。

AccessContext.unlock();
AccessContext.useThreadLocalProvider(new AccessContextThreadLocalProvider() {
public ThreadLocal provide() {
return Http.Context.current;
}
});

アプリケーションの起動時に一度だけ呼び出すことを想定しています。
SNAPSHOTは既にありますが、正式版をそのうち出します。

@manhole
Copy link
Contributor Author

manhole commented May 7, 2014

*.dfpropの修正ありがとうございます、生成は今後のタスクだったので助かりました。

AccessContextですが、Http.Context.currentをそのまま使うのはまずいと思います。
これはplayのHttp.Contextインスタンス用ですから、AccessContextを入れることはできません。
https://github.com/playframework/playframework/blob/2.2.2/framework/src/play/src/main/java/play/mvc/Http.java#L22

Threadが変わってもアプリからのアクセスが可能になっているのが、
Http.Contextのargsプロパティです。
https://github.com/playframework/playframework/blob/2.2.2/framework/src/play/src/main/java/play/mvc/Http.java#L150

argsにAccessContextを持たせることになると思います。

おそらくこんなふうになるのではと思いますがどうでしょうか。やりすぎ?

// ThreadLocalを抽象化して、
interface AccessContextHolder {
    AccessContext getAccessContext();
    void setAccessContext(AccessContext ac);
}

// デフォルトはThreadLocalで、
class ThreadLocalAccessContextHolder implements AccessContextHolder {
    private static final ThreadLocal<AccessContext> _threadLocal = new ThreadLocal<AccessContext>();
    AccessContext getAccessContext() {
        return _threadLocal.get();
    }
    void setAccessContext(AccessContext ac) {
        _threadLocal.set(ac);
    }
}

// play用の実装を別途作る
class PlayAccessContextHolder implements AccessContextHolder {
    private final String _key;
    public PlayAccessContextHolder(String key) { _key = key }
    public AccessContext getAccessContext() {
        // TODO バッチのことも考慮すべき
        Http.Context context = Http.Context.current.get();
        Map<String, Object> args = context.args;
        AccessContext ac = args.get(_key);
        return ac;
    }
    public void setAccessContext(AccessContext ac) {
        Http.Context context = Http.Context.current.get();
        Map<String, Object> args = context.args;
        args.put(_key, ac);
    }
}

// ThreadLocal以外を使いたかったらアプリで1度だけ設定する
AccessContext.useAccessContextHolder(new PlayAccessContextHolder("some application unique key"));

@jflute
Copy link
Contributor

jflute commented May 7, 2014

ああぁ、そういうことかわかった。
currentのThreadLocal自体は差し代わってるんだけど、
argsの中身が新しいスレッドに引き継がれてるってことね。

たし、current (ThreadLocal) 自体は、Http.Contextを保持してるものなので、
いまのおれの実装だと ClassCastException になっちゃうか。

すると、もうDBFlute側からすると、スレッドローカルじゃなくて、
「AccessContextを提供してくれる何か」を受け取って、
そこからもらうって感じになるわけだね。
まあ、すると厳密には AccessContext のstatic領域を利用する必要もないかもだけど、
単に AccessContext を使った方がDBFluteで設定がしやすいってところだね。

ちょと修正しちゃいますー

@jflute
Copy link
Contributor

jflute commented May 7, 2014

Holder頂きました。
デフォルトのHolderではなく「代理のHolderを使う」というニュアンスに。
DBFluteからすれば、スレッドローカルなのかどうかすらわからないで、
とにかく「Holderから提供してもらう、Holderに保存する」って感じで。

AccessContext.useSurrogateHolder(new AccessContextHolder() {

public AccessContext provide() {
    return (AccessContext)Http.Context.current.get().args.get("foo");
}

public void save(AccessContext accessContext) {
    Http.Context.current.get().args.put("foo", accessContext)
}

});

ExampleにSNAPSHOTを反映させちゃおうかと思ったけど、
build.sbt を修正して、eclipse with-source=true しても変わらなくって、
すぐ忘れちゃいますねぇこれ。他の仕組みと違い過ぎて...(><。

とりあえず、Java8のOptional的な対応をやっているので、
そちらがひと段落したら正式版を出そうかなと。

@manhole
Copy link
Contributor Author

manhole commented May 7, 2014

修正ありがとうございます。

build.sbt を修正して、eclipse with-source=true しても変わらなくって、

build.sbtのlibraryDependenciesを変更すれば、IDEA (community版 + scala plugin)なら勝手に反映してくれると思います。
eclipse向けの手順を書いておきながら、最近はIDEAに傾倒しつつあります...

DBFluteからすれば、スレッドローカルなのかどうかすらわからないで、
とにかく「Holderから提供してもらう、Holderに保存する」って感じで。

はい、そのイメージでいます。
ところでHolderのAccessContext部分をにすれば、ThreadLocal部分を抽象化するのにちょうど良いかなと思っています。

コード部分は行頭をハードタブでインデントすると、いい感じの見栄えになってくれる記法のようです。

@jflute
Copy link
Contributor

jflute commented May 13, 2014

今週リリース予定のDBFlute-1.0.5Fを、DBFluteモジュールだけ反映しました。
まだ自動生成もせず、DBFluteランタイムの方は何も変えていないので、

そちらで、
「自動生成 (manage.sh 叩いて、1番を選択)」

「DBFluteランタイムを1.0.5Fにアップ (sbtのファイルを修正!?)」
をしてくれるとうれしいです。

すると、AccessContext で Holder の差し替えができます。

@manhole
Copy link
Contributor Author

manhole commented May 13, 2014

アイアイサー!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants