今日はDBIx::TransactionManager-1.09。1ファイルしかないのでさっくりと。
中では2クラス定義がされていて、DBIx::TransactionManager と DBIx::TransactionManager::ScopeGuard。前者がメインで、後者はスコープガード用のクラス。
txn_begin の中では$dbh->begin_work をしていて、こいつは$dbh->{AutoCommit}を一度無効にして、トランザクションが終わったら自動で$dbh->{AutoCommit} を有効にしてくれるという優れもの。なんだけど、begin_work はネストして呼べないのでそれを可能にするのが TransactionManager のお仕事。active_transactions フィールドがミソになっていて、txn_begin されるごとにここへcaller の情報を貯めていく。このフィールドを見ればネストしているか分かるので、ネストしている場合はbegin_work はしない。
txn_begin へcaller を外から引数として渡せるようになっているのは、TransactionManager のラッパーを作る場合を考慮してのこと。ラッパーを作るときは正しい呼び出し元をcaller パラメータへ渡すようにするとよい。
txn_commit は、ネストの内側で呼ぶと何もしない。ネストの一番外で呼んだときのみ$dbh->commit をする。ネストの途中で txn_rollbackされていると、エラーで落ちてしまう*1。同様にtxn_rollback で実際にrollback が走ってくれるのは、ネストの一番外で呼んだときのみ。
- ScopeGuard は基本的にTransactionManager へ処理を委譲する。が、各スコープに対して2度以上commit やrollback をするとtxn_begin の呼び出し回数と矛盾してしまうので、一度しか効果を及ぼさないようにフラグで管理している。rollback も commit もしないでインスタンスが解放される場合には、DESTROY 内でwarning を出しつつ txn_rollback している。
*1:ネストの深ーいとこで誰かがrollback した場合に、一番外側でcommit ではなくrollback を呼ぶ必要があると思うんだけど、commit しちゃあイカンのだってことをどうやって知るのがいいんだろう。