XA関連メモ XAER_DUPIDによるXAExceptionの発生条件

そもそもエラー判定およびXAER_DUPIDのコードの返却をしているのはリソースアダプタ側でありJBoss内部からによるものではない.例えばMySQLに対してmysql clientから直接"XA START 0x1"を連発してもエラーにはならないことなどもあるので,単純に渡したXidが重複していたのでは,とは推測できない.→やっぱWebSphereMQ使わないと駄目かなあ・・・.



Xidの生成を洗う
Xidは以下の条件によってJBoss内で一意に識別される(equalsメソッドのオーバーライド)

  • フォーマットIDが同じ(ver4.0.1sp1ではJBossからは常にデフォルトフォーマットIDを使っている)
  • グローバルIDおよびブランチIDのそれぞれの配列数が同じ
  • グローバルIDおよびブランチIDの配列の要素が同じ

ログを見ると現在の環境ではブランチIDは使用せず,グローバルIDも1つのみしか使われていないようだ.どういう条件でこれが変わるかは未調査.

これらの属性はXidがnewされる際のコンストラクタ引数によって決められるが,Xidを生成するのはXidFactoryクラスのみである.XidFactoryのnewXidメソッドかあるいはnewBranchを呼び出した時にのみ生成される.newBranchメソッドで引数にXidインスタンスを用いる必要があり,グローバルIDはそれと同じ物が使われる.従ってグローバルIDに限って言えば,newXidメソッドでのみ新しく定義される.

グローバルIDの決定プロセス

  • 元となるbaseGlobalIdがまず決められる.baseGlobalIdはXidFactoryがnewされる際に決まる.
    • baseGlobalIdはInetAddress.getLocalHost.getHostName()で解決されるホスト名が使用される.
    • ただしJBossでは49バイトを超える長さのホスト名では超えた分がカットされる.(49+1+14=64)
    • 最後に/が追加される.
  • これにローカルID(localId)が付与される.
    • ローカルIDはprivate static synchronizedなgetNextId()メソッドで取得した値である.
    • getNextId()ではprivate longな変数globalIdNumber(初期値0)を1加算して返す.
  • これにてグローバルIDが決定される."myhostname/128"みたいなやつが出来上がる.

XidFactoryインスタンスごとにglobalIdNumberが管理されているので,複数のXidFactoryインスタンスが生成されていなければ普通はXidの重複は発生しないはずだ.ちょっと気になるのは,globalIdNumberにJMX用のR/Wなアクセッサメソッドが付いている点.誰かが書き換えを行う可能性がある(普通はしないとおもうが).

一応ソースディレクトリを"setGlobalIdNumber"で全文検索してみたが,これを明示的に呼び出す他のクラスは見つからなかった.直接呼出しであれ,JMXinvokeメソッドあるいはReflectionのInvokeメソッドであれ,使用していればこの検索に引っかかるはず.まさかmethod[]の要素指定で呼び出しはせんだろうし.このラインでの捜索はここで途切れてしまった.



XidFactoryの生成を洗う

XidFactoryごとにXidは一意に生成されるので,XidFactoryが同時に複数生成されるか,あるいはサーバ再起動,XidFactoryインスタンスの消滅と再生成による場合を除いては同じ値を持つXidインスタンス(参照ではない)が複数存在することは有り得ない.1台のマシンに複数のJBossインスタンスを起動している場合には可能性ありだが.

XidFactoryインスタンスJBossサーバ起動時に1つだけ生成される.ラッシュをかけたら複数生成されるのかな?→そんなわけないと思いつつラッシュかけてログとったらやはり起動後に生成されるなんてことは無い.XAを使うモジュール2つをそれぞれ20スレッドずつ合計40スレッドで同時にラッシュかけさせましたが毎秒40+40=80トランザクション処理してもやっぱりXidFactoryが後から生成されるなんてことは無いわけですよ.

XidFactoryをnewする可能性があるのはTransactionImplのみ.static void defaultXidFactory()メソッドでstatic変数にXidFactoryインスタンスが入っていなければnewする.ただしdefaultXidFactory()メソッドはsingleton実装になっていないので(synchronizedではなくnullチェックからnewまでの間に別のスレッドによる呼び出しが可能),ここに問題の余地はある.

TransactionImplインスタンス自体は大量に生成されるもの.現在の環境だと合計11084回とか(この数値は同じデプロイ状況でも固定ではないっぽい).TransactionImplそのものはJBoss内でトランザクションが発行されるたびに生成されるものである模様.CMTなEJB呼び出し1回につき1回生成されたりとか.

となると気になるのはdefaultXidFactory()メソッドはどれくらいの頻度で呼ばれているのかだ.しかしまたラッシュをかけてログを見たところこれは1回しか呼ばれていなかった.ソースを確認するとdefaultXidFactory()はTxManagerからしか呼ばれておらず,しかもTxManagerのprivateなコンストラクタからのみであって,TxManagerはSingletonなオブジェクトとなっていた.

さてさて,そういうことになるとXidFactoryはTxManagerがロードされる際のクラス変数の初期化のタイミングで生成されてるだけで,後は生成しないことの裏づけが得られたことになる.XidFactory複数生成説を押し通すなら,TxManagerはJBossの複数のクラスローダによってロードされて別々のTxManagerで生成されたXidFactoryが同時に使われている,あるいは一度アンロードされてから再ロードされたというラインしかもう無い.まあ後者は絶対にありえないでしょう(ラッシュ時にそんなことするわけない).

このラインで行くならJBossのクラスローダについてちょっと調べないといけないですね.そういえば手元の環境では2つのモジュールを1つのJARでデプロイしてました.→2つに分けようが1つにまとめようが変わらない.

→TxManagerがロードされるよりも先にXidFactoryが生成されたのでboot.logを読んだら上は間違いであることが判明.XidFactoryMBeanのデプロイ時にnewInstanceで生成されているかも.こっちのほうが早い.

XidFactoryのコンストラクタに強引にthrow new Exceptionを突っ込んでみた.

Caused by: java.lang.Exception
	at org.jboss.tm.XidFactory.<init>(XidFactory.java:98)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:274)
	at org.jboss.mx.server.MBeanServerImpl.instantiate(MBeanServerImpl.java:1212)
	at org.jboss.mx.server.MBeanServerImpl.instantiate(MBeanServerImpl.java:269)
	at org.jboss.mx.server.MBeanServerImpl.createMBean(MBeanServerImpl.java:327)
	at org.jboss.system.ServiceCreator.install(ServiceCreator.java:125)
	at org.jboss.system.ServiceConfigurator.internalInstall(ServiceConfigurator.java:153)
	at org.jboss.system.ServiceConfigurator.install(ServiceConfigurator.java:118)
	... 36 more

MBeanServerImpl.java の1212行目あたり

Constructor constructor = clazz.getConstructor(sign);
return constructor.newInstance(params);

確かにこいつがnewしてる.

TransactionImplのstatic変数xidFactoryはよく見ると修飾子がprivateではなくpackage private(デフォルト)となっている.そしてコメントに思いっきりヒントが.最初見過ごしてた・・・orz

/**
 *  Factory for Xid instances of specified class.
 *  This is set from the <code>TransactionManagerService</code>
 *  MBean.
 */
static XidFactoryMBean xidFactory;

だそうです.TransactionImpl.xidFactoryがXidFactory型ではなくXidFactoryMBean型で定義されているのもそのためだったのか.TransactionImpl.xidFactoryへの書き込みアクセスをしているのはこのTransactionManagerServiceだけで,こいつはMBeanServerから先ほどの"MBeanServerImpl.java の1212行目あたり"で登録されたXidFactoryMBean型のXidFactoryインスタンスへの代理参照を取得してTransactionImpl.xidFactoryに直接突っ込んでいる(TransactionManagerService.java 70行目)

そしてこれによってJBossの起動プロセスのかなり最初の頃にTransactionImpl.xidFactoryの生成と代入が行われることになるので,それ以降どれだけTransactionImplのstaticメソッドdefaultXidFactory()が呼ばれてもXidFactoryインスタンスが新たに生成されることは無い.

これでJBossがXidFactoryを複数生成するという疑いは一切無くなり,従って生成される全てのXidは一意になることがソース上で確認できた.ちなみにうちの環境ではどれだけ負荷をかけてもログからはXidの重複は確認されていない.

さて,これでJBossに対する疑いが晴れたので例のアレを疑って調べてみますか.