web-dev-qa-db-ja.com

Java seアプリケーションでのC3P0でのDEADLOCK

Java SEアプリケーションで使用しているc3p0ライブラリ(バージョン0.9.5.2)で重大な問題が発生しました。

私のアプリケーションは、スレッドプールを使用して、ジョブを実行することでタスクを並列化します。

各ジョブはデータベースを使用して、データの読み取り、更新、または削除を少なくとも1回、最大10,000回(非常にまれなケースですが発生する可能性があります)以上行います。

したがって、プロジェクトc3p0ライブラリにデータベースへの接続プールを含めて、スレッドプール内のすべてのワーカーが同時に対話できるようにしました。

開発環境(OSX 10.11)でアプリケーションを実行しても問題はありませんが、本番環境(Linux Debian 8)で実行すると、大きな問題が発生します。確かにそれはフリーズします....

最初は、次のトレーススタックでデッドロックが発生しました:

[WARNING] com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector@479d237b -- APPARENT DEADLOCK!!! Creating emergency threads for unassigned pending tasks!
[WARNING] com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector@479d237b -- APPARENT DEADLOCK!!! Complete Status: 
    Managed Threads: 3
    Active Threads: 3
    Active Tasks: 
        com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@264fb34f
            on thread: C3P0PooledConnectionPoolManager[identityToken->z8kfsx9l1adv4kd1qtfdi6|659f3099]-HelperThread-#2
        com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@39a5576b
            on thread: C3P0PooledConnectionPoolManager[identityToken->z8kfsx9l1adv4kd1qtfdi6|659f3099]-HelperThread-#1
        com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@5e676544
            on thread: C3P0PooledConnectionPoolManager[identityToken->z8kfsx9l1adv4kd1qtfdi6|659f3099]-HelperThread-#0
    Pending Tasks: 
        com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@6848208c
Pool thread stack traces:
    Thread[C3P0PooledConnectionPoolManager[identityToken->z8kfsx9l1adv4kd1qtfdi6|659f3099]-HelperThread-#2,5,main]
        Sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
        Sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.Java:269)
        Sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.Java:93)
        Sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.Java:86)
        Sun.nio.ch.SelectorImpl.select(SelectorImpl.Java:97)
        com.Microsoft.sqlserver.jdbc.SocketFinder.findSocketUsingJavaNIO(IOBuffer.Java:2438)
        com.Microsoft.sqlserver.jdbc.SocketFinder.findSocket(IOBuffer.Java:2290)
        com.Microsoft.sqlserver.jdbc.TDSChannel.open(IOBuffer.Java:551)
        com.Microsoft.sqlserver.jdbc.SQLServerConnection.connectHelper(SQLServerConnection.Java:1962)
        com.Microsoft.sqlserver.jdbc.SQLServerConnection.login(SQLServerConnection.Java:1627)
        com.Microsoft.sqlserver.jdbc.SQLServerConnection.connectInternal(SQLServerConnection.Java:1458)
        com.Microsoft.sqlserver.jdbc.SQLServerConnection.connect(SQLServerConnection.Java:772)
        com.Microsoft.sqlserver.jdbc.SQLServerDriver.connect(SQLServerDriver.Java:1168)
        com.mchange.v2.c3p0.DriverManagerDataSource.getConnection(DriverManagerDataSource.Java:175)
        com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.Java:220)
        com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.Java:206)
        com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.acquireResource(C3P0PooledConnectionPool.Java:203)
        com.mchange.v2.resourcepool.BasicResourcePool.doAcquire(BasicResourcePool.Java:1138)
        com.mchange.v2.resourcepool.BasicResourcePool.doAcquireAndDecrementPendingAcquiresWithinLockOnSuccess(BasicResourcePool.Java:1125)
        com.mchange.v2.resourcepool.BasicResourcePool.access$700(BasicResourcePool.Java:44)
        com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask.run(BasicResourcePool.Java:1870)
        com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.Java:696)
    Thread[C3P0PooledConnectionPoolManager[identityToken->z8kfsx9l1adv4kd1qtfdi6|659f3099]-HelperThread-#1,5,main]
        Sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
        Sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.Java:269)
        Sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.Java:93)
        Sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.Java:86)
        Sun.nio.ch.SelectorImpl.select(SelectorImpl.Java:97)
        com.Microsoft.sqlserver.jdbc.SocketFinder.findSocketUsingJavaNIO(IOBuffer.Java:2438)
        com.Microsoft.sqlserver.jdbc.SocketFinder.findSocket(IOBuffer.Java:2290)
        com.Microsoft.sqlserver.jdbc.TDSChannel.open(IOBuffer.Java:551)
        com.Microsoft.sqlserver.jdbc.SQLServerConnection.connectHelper(SQLServerConnection.Java:1962)
        com.Microsoft.sqlserver.jdbc.SQLServerConnection.login(SQLServerConnection.Java:1627)
        com.Microsoft.sqlserver.jdbc.SQLServerConnection.connectInternal(SQLServerConnection.Java:1458)
        com.Microsoft.sqlserver.jdbc.SQLServerConnection.connect(SQLServerConnection.Java:772)
        com.Microsoft.sqlserver.jdbc.SQLServerDriver.connect(SQLServerDriver.Java:1168)
        com.mchange.v2.c3p0.DriverManagerDataSource.getConnection(DriverManagerDataSource.Java:175)
        com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.Java:220)
        com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.Java:206)
        com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.acquireResource(C3P0PooledConnectionPool.Java:203)
        com.mchange.v2.resourcepool.BasicResourcePool.doAcquire(BasicResourcePool.Java:1138)
        com.mchange.v2.resourcepool.BasicResourcePool.doAcquireAndDecrementPendingAcquiresWithinLockOnSuccess(BasicResourcePool.Java:1125)
        com.mchange.v2.resourcepool.BasicResourcePool.access$700(BasicResourcePool.Java:44)
        com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask.run(BasicResourcePool.Java:1870)
        com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.Java:696)
    Thread[C3P0PooledConnectionPoolManager[identityToken->z8kfsx9l1adv4kd1qtfdi6|659f3099]-HelperThread-#0,5,main]
        Sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
        Sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.Java:269)
        Sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.Java:93)
        Sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.Java:86)
        Sun.nio.ch.SelectorImpl.select(SelectorImpl.Java:97)
        com.Microsoft.sqlserver.jdbc.SocketFinder.findSocketUsingJavaNIO(IOBuffer.Java:2438)
        com.Microsoft.sqlserver.jdbc.SocketFinder.findSocket(IOBuffer.Java:2290)
        com.Microsoft.sqlserver.jdbc.TDSChannel.open(IOBuffer.Java:551)
        com.Microsoft.sqlserver.jdbc.SQLServerConnection.connectHelper(SQLServerConnection.Java:1962)
        com.Microsoft.sqlserver.jdbc.SQLServerConnection.login(SQLServerConnection.Java:1627)
        com.Microsoft.sqlserver.jdbc.SQLServerConnection.connectInternal(SQLServerConnection.Java:1458)
        com.Microsoft.sqlserver.jdbc.SQLServerConnection.connect(SQLServerConnection.Java:772)
        com.Microsoft.sqlserver.jdbc.SQLServerDriver.connect(SQLServerDriver.Java:1168)
        com.mchange.v2.c3p0.DriverManagerDataSource.getConnection(DriverManagerDataSource.Java:175)
        com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.Java:220)
        com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.Java:206)
        com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.acquireResource(C3P0PooledConnectionPool.Java:203)
        com.mchange.v2.resourcepool.BasicResourcePool.doAcquire(BasicResourcePool.Java:1138)
        com.mchange.v2.resourcepool.BasicResourcePool.doAcquireAndDecrementPendingAcquiresWithinLockOnSuccess(BasicResourcePool.Java:1125)
        com.mchange.v2.resourcepool.BasicResourcePool.access$700(BasicResourcePool.Java:44)
        com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask.run(BasicResourcePool.Java:1870)
        com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.Java:696)

その後、さまざまなWebサイトのアドバイスに従っていくつかの変更を加えました:

System.setProperty("com.mchange.v2.log.MLog", "com.mchange.v2.log.FallbackMLog");
System.setProperty("com.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVEL", "WARNING");

// Create db pool
final ComboPooledDataSource cpds = new ComboPooledDataSource() ;

// Driver
cpds.setDriverClass( "com.Microsoft.sqlserver.jdbc.SQLServerDriver" ); // loads the jdbc driver                     

// Url
cpds.setJdbcUrl( "jdbc:xxxx://xxxxx:xxxx;database=xxxxx;" );

// Username / Password
cpds.setUser( "xxxx" ) ;
cpds.setPassword( "xxxx" ) ;                                  

// Start size of db pool
cpds.setInitialPoolSize( 8 );

// Min and max db pool size
cpds.setMinPoolSize(  8 ) ;
cpds.setMaxPoolSize( 10 ) ;

// ????
cpds.setNumHelperThreads( 5 ) ;

// Max allowed time to execute statement for a connection
// @See http://stackoverflow.com/questions/14730379/apparent-deadlock-creating-emergency-threads-for-unassigned-pending-tasks
cpds.setMaxAdministrativeTaskTime( 60 ) ;

// ?????
cpds.setMaxStatements( 180 ) ;
cpds.setMaxStatementsPerConnection( 180 ) ;

// ?????
cpds.setUnreturnedConnectionTimeout( 60 ) ;

// ?????
cpds.setStatementCacheNumDeferredCloseThreads(1);

// We make a test : open and close opened connection
cpds.getConnection().close() ;

これらの変更後、一部のジョブの実行後、アプリケーションは数十秒間フリーズし、次のエラーメッセージを表示します:

[WARNING] A task has exceeded the maximum allowable task time. Will interrupt() thread [Thread[C3P0PooledConnectionPoolManager[identityToken->z8kfsx9l1ao3z0x88z7oi|4dd889bd]-HelperThread-#4,5,main]], with current task: com.mchange.v2.resourcepool.BasicResourcePool$1DestroyResourceTask@4128b402
[WARNING] Thread [Thread[C3P0PooledConnectionPoolManager[identityToken->z8kfsx9l1ao3z0x88z7oi|4dd889bd]-HelperThread-#4,5,main]] interrupted.
[WARNING] A task has exceeded the maximum allowable task time. Will interrupt() thread [Thread[C3P0PooledConnectionPoolManager[identityToken->z8kfsx9l1ao3z0x88z7oi|4dd889bd]-HelperThread-#3,5,main]], with current task: com.mchange.v2.resourcepool.BasicResourcePool$1DestroyResourceTask@5d6aab6d
[WARNING] Thread [Thread[C3P0PooledConnectionPoolManager[identityToken->z8kfsx9l1ao3z0x88z7oi|4dd889bd]-HelperThread-#3,5,main]] interrupted.
[WARNING] A task has exceeded the maximum allowable task time. Will interrupt() thread [Thread[C3P0PooledConnectionPoolManager[identityToken->z8kfsx9l1ao3z0x88z7oi|4dd889bd]-HelperThread-#0,5,main]], with current task: com.mchange.v2.resourcepool.BasicResourcePool$1DestroyResourceTask@70a3328f
[WARNING] Thread [Thread[C3P0PooledConnectionPoolManager[identityToken->z8kfsx9l1ao3z0x88z7oi|4dd889bd]-HelperThread-#0,5,main]] interrupted.

私の質問は:

  1. アプリケーションが開発環境で完全に機能し、本番環境でこれらの問題が発生するのはなぜですか?
  2. とりわけ、それをどのように改善するのですか?
  3. 接続がsetMaxStatementsおよびsetMaxStatementsPerConnectionで定義されたステートメントの最大数に達すると、接続はどうなりますか?接続が閉じられ、別の接続が作成されている間に別の接続が引き継がれますか?
  4. SetStatementCacheNumDeferredCloseThreads関数がアプリケーションに与える影響を完全には理解していませんでした。

どうもありがとうございました !良い一日を過ごしてください。

6
Skaÿ

OK。そう。あなたの基本的な問題は単純です。実稼働環境では、接続取得の試行は最終的にフリーズします。つまり、例外で成功も失敗もせず、単にハングします。最終的には、これをデバッグする必要があります。c3p0が本番データベースに接続しようとすると、Driver.connect()の呼び出しがハングすることがあるのはなぜですか。それを引き起こしているものは何でもc3p0の制御の外にあります。 DBMS側で接続の総数が制限に達している可能性があります(このアプリケーションからではなく、maxPoolSizeはかなり控えめですが、本番サーバーが拡張されすぎている可能性があります)。古いJVMで実行している場合は、SQLServerへのハングに関する既知の問題がありました。たとえば、 JDBC接続がSQL Server 2008 r2からの応答なしでハングしますDriver.getConnectionがSQLServerドライバーを使用してハングしますJava 1.6.0_29 しかし、私はあなたを疑っています実行中Java 6この時点で、最近の問題はわかりません。いずれにせよ、これが起こっていることはログから明らかです。c3p0は取得しようとしています。 DBMSからの接続では、DBMSが無期限にハングし、最終的にc3p0のすべてのヘルパースレッドがハングしたタスクによって飽和状態になり、_APPARENT DEADLOCK_が表示されます。この問題を解決するには、JDBCドライバーが接続を試みる理由をデバッグする必要があります。 DBMSがハングすることがあります。

ランダムなトラブルシューティングの投稿を探した後に行ったことのほとんどは、この問題とはあまり関係がありませんでした。ログが変更されたのはこの設定でした

_cpds.setMaxAdministrativeTaskTime( 60 );
_

それは醜い方法で問題を回避します。タスクが長時間ハングする場合、その設定により、c3p0は実行中のスレッドをinterrupt()し、タスクを放棄します。これはデッドロックを防ぎますが、その原因には対処していません。

ただし、2つのログの間には驚くべき変更があります。 _APPARENT DEADLOCK_ spewを、「タスクが最大許容タスク時間を超えた」というレポートに置き換えることが期待されていました。しかし、興味深いことに、2番目のログでは、interrupt() edを取得するタスクは、接続の取得の試行ではなく、接続の破棄の試行です。それが変更された理由はわかりませんが、主要な問題は同じです。JDBCドライバーによるDBMSとの対話の試みは無期限にフリーズし、例外ですぐに成功したり失敗したりしません。それはあなたがデバッグする必要があるものです。

問題を解決できない場合は、回避できる可能性があります。非常に醜いですが、maxAdministrativeTaskTimeを減らして(たとえば_30_)、numHelperThreadsを増やすと(たとえば_20_)、アプリケーションを大幅に削除できる可能性がありますフリーズの頻度が低い限り、一時停止します。 numHelperThreadsを増やすと、c3p0のスレッドプールが完全にブロックされる前に許容できる凍結タスクの数が増えます。 maxAdministrativeTaskTimeを減らすと、閉塞の寿命が短くなります。明らかに、正しいことは、JDBCドライバーとDBMSの間の問題をデバッグすることです。ただし、それが不可能であることが判明した場合は、回避策が最善の方法である場合があります。

私は(少なくとも今のところ)これらの3つの設定を削除します:

_// ?????
cpds.setMaxStatements( 180 ) ;
cpds.setMaxStatementsPerConnection( 180 ) ;

// ?????
cpds.setStatementCacheNumDeferredCloseThreads(1);
_

最初の2つは、ステートメントのキャッシュをオンにします。これは、アプリケーションのパフォーマンスの観点から望ましい場合と望ましくない場合があります。ただし、c3p0とDBMSとの相互作用の複雑さが増します。 SQLServer(いくつかのデータベースの中で)は、接続のマルチスレッド使用に関して非常に脆弱です(少なくとも、JDBC仕様の初期バージョンでは、合法であるはずですが、あまりにも悪いです)。 statementCacheNumDeferredCloseThreadsを_1_に設定すると、接続が使用されている間、ステートメントキャッシュが期限切れのステートメントを閉じようとしないため、フリーズが防止され、通常は_APPARENT DEADLOCK_ sとして表示されます。ハングしたステートメントは、問題ではなく、タスクを閉じます。ステートメントキャッシュをオンにする場合は、フリーズを回避するために、必ずstatementCacheNumDeferredCloseThreadsを_1_に設定したままにしてください。ただし、最も安全で正直なことは、主要な問題をデバッグするまで、Statementキャッシュの複雑さをすべて回避することです。これらの設定を後で復元して、アプリケーションのパフォーマンスが向上するかどうかをテストできます。 (ステートメントキャッシュをオンに戻す場合は、maxStatementsPerConnectionを設定し、グローバルmaxStatementsを設定しないか、両方を設定する場合は、接続ごとの制限を設定することをお勧めします。グローバル制限よりもはるかに小さい値になります。ただし、今のところ、これらすべてをオフにしてください。)

特定の質問に答えるには:

  1. アプリケーションが開発環境で完全に機能し、本番環境でこれらの問題が発生するのはなぜですか?

これは、JDBCドライバーとDBMSの間のハングをデバッグする際に使用する重要な手がかりです。本番サーバーに関する何かが原因で、開発サーバーに表示されないハングが発生します。これは、開発サーバーの負荷が比較的低く、本番サーバーの負荷が高いという問題かもしれません。ただし、ハングについての手がかりを提供する設定には他の違いがある場合があります。

  1. とりわけ、それをどのように改善するのですか?

ハングをデバッグします。ハングをデバッグできない場合は、maxAdministrativeTaskTimeを短くしてnumHelperThreadsを大きくして問題を回避してみてください。

  1. 接続がsetMaxStatementsおよびsetMaxStatementsPerConnectionで定義されたステートメントの最大数に達すると、接続はどうなりますか?接続が閉じられ、別の接続が作成されている間に別の接続が引き継がれますか?

接続はそれらのいずれにも到達しません。これらは、ステートメントキャッシュを説明するパラメータです。キャッシュされたステートメントの総数がmaxStatementsに達すると、最も使用頻度の低いキャッシュされたステートメントが閉じられます(接続ではなくステートメントのみ)。接続のmaxStatementsPerConnectionがヒットすると、その接続で最も使用頻度の低いキャッシュされたステートメントが閉じられます(ただし、接続自体は開いたままアクティブなままです)。

  1. SetStatementCacheNumDeferredCloseThreads関数がアプリケーションに与える影響を完全には理解していませんでした。

ステートメントキャッシュを使用している場合(ここでもオフにすることをお勧めします)、この設定により、親接続が他のスレッドで使用されている間、期限切れのステートメント(上記を参照)がclose()されないようにします。 。この設定は、接続が使用されなくなったときに待機し、そのときだけそれらを閉じることを唯一の目的とする専用のスレッドを作成します(したがって、ステートメントキャッシュはスレッドを閉じるのを延期しました)。

これがお役に立てば幸いです。

Update:発生しているバグは、 Java 6のバグ と非常によく似ています。 Java 6を実行している場合、運が良ければ、修正はおそらく本番JVMをJava 6の最新バージョンに更新することです。

15
Steve Waldman