S2Hibernateでデッドロック?
S2Hibernateを使用して開発をしているが、ぶんまわしテスト中にTomcatがハングしてしまった。
どうも、以下のクラスでデッドロックが起きているようだ。
ConnectionPoolImpl#checkOut()では、
while (getActivePoolSize() + getTxActivePoolSize() >= getMaxPoolSize()) { try { wait(); } catch (InterruptedException ignore) { } }
となっている。プールの上限になったら空くまで待っているようだ。
ところで、このメソッドは、S2SessionFactoryImpl#getConnection()から呼ばれている。
もっと遡ると、S2SessionFactoryImpl#bindSession()、S2SessionFactoryImpl#getSession()から呼ばれている。
ここで、S2SessionFactoryImpl#bindSession()は、synchronizedメソッドであることに気がついた。
S2SessionFactoryImplのシングルトンをロックしたまま、ConnectionPoolImplでwait()してしまっているようだ。
S2SessionFactoryImpl#closeSession()もsynchronizedメソッドのため、コネクションをプールに戻そうにもブロックされてしまっているようだ。
なにぶん、Seasarを使うのは初めてなので、設定が間違っている可能性も高い。とりあえず、S2SessionFactoryImplのソースをコピーして別のクラスを作成してしのぐことにした。
自作S2SessionFactoryImplでは、以下のようにした。
private S2Session bindSession(Transaction tx) { S2Session session = null; session = (S2Session) txSessions_.get(tx); if (session != null && session.isOpen()) { return session; } session = createSession(); synchronized (this) { txSessions_.put(tx, session); } TransactionUtil.registerSynchronization(tx, this); return session; } private void closeSession() { Transaction tx = getTransaction(); if (tx == null) { return; } S2Session session = null; synchronized (this) { session = (S2Session) txSessions_.remove(tx); } if (session != null && session.isOpen()) { try { if (!session.isReadOnly()) { session.flush(); } } finally { Connection con = session.close(); ConnectionUtil.close(con); } } }
テストしたところ、ハングしなくなった。
正しいかどうかはともかくとして、とりあえずこれでいいか。