【问题标题】:DEADLOCK with C3P0 on java se applicationjava se应用程序上的C3P0死锁
【发布时间】:2017-01-16 20:31:53
【问题描述】:

我在 Java SE 应用程序中使用的 c3p0 库(版本 0.9.5.2)遇到了一个严重问题。

我的应用程序使用线程池通过执行作业来并行化任务。

每个作业使用数据库读取、更新或删除数据至少一次(在极少数情况下,但可能发生)超过 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)

随后我根据不同网站上的建议进行了一些更改:

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 函数对我的应用程序的影响。

非常感谢!祝你有美好的一天。

【问题讨论】:

  • 死锁的可能不是 C3P0,而是您的事务。尤其是对所有事情都进行锁定的 SQL Server 女士。您应该首先尝试开源驱动程序 JTDS,它比官方驱动程序更高效(并且错误更少)。然后,检查您的事务是否存在潜在的表锁。
  • 当你使用连接池时,你必须关闭连接,这会将它返回到池中(实际上并没有关闭它)
  • 好的,谢谢!我会尽力通知您。
  • 我遇到了一些“类似”的问题,Skay 的分辨率有什么更新吗?
  • @Johnny 不抱歉...

标签: java sql-server multithreading threadpool c3p0


【解决方案1】:

好的。所以。你的基本问题很简单。在您的生产环境中,连接获取尝试最终会冻结,即它们既没有成功也没有失败并出现异常,它们只是挂起。最终,这就是您必须调试的内容:为什么当 c3p0 尝试连接到您的生产数据库时,有时对 Driver.connect() 的调用会挂起?无论是什么原因,都超出了 c3p0 的控制范围。您可能在 DBMS 端达到了总连接数的限制(不是来自此应用程序,您的 maxPoolSize 相当适中,但您的生产服务器可能过度扩展)。如果您在较旧的 JVM 上运行,则存在与 SQLServer 挂起有关的已知问题,请参见例如JDBC connection hangs with no response from SQL Server 2008 r2 Driver.getConnection hangs using SQLServer driver and Java 1.6.0_29 但我怀疑您目前正在运行 Java 6,而且我不知道最近的问题。无论如何,从您的日志中可以清楚地看到正在发生的事情:c3p0 正在尝试从 DBMS 获取连接,DBMS 无限期挂起,最终所有 c3p0 的辅助线程都被挂起的任务饱和,您会看到 @987654326 @。要解决此问题,您必须调试 JDBC 驱动程序连接到 DBMS 的尝试有时会挂起的原因。

您在搜索随机故障排除帖子后所做的大部分事情都与此问题不太相关。确实导致您的日志发生更改的是此设置

cpds.setMaxAdministrativeTaskTime( 60 );

这以一种丑陋的方式解决了这个问题。如果一个任务长时间挂起,该设置会导致 c3p0 到 interrupt() 它正在运行的线程并放弃它。这可以防止死锁,但不能解决其原因。

不过,两个日志之间有一个令人惊讶的变化。用“任务已超过最大允许任务时间”的报告替换 APPARENT DEADLOCK spew 是意料之中的。但有趣的是,在您的第二个日志中,获得interrupt()ed 的任务不是连接获取尝试,而是连接破坏尝试。我不知道为什么会发生这种变化,但核心问题是相同的:JDBC 驱动程序与 DBMS 交互的尝试会无限期冻结,既不会成功也不会因异常而立即失败。这就是你需要调试的。

如果您无法解决问题,您也许可以解决它。这是非常难看的,但是如果你减少maxAdministrativeTaskTime(比如30)并增加numHelperThreads(比如20),你可以在很大程度上消除应用程序暂停,只要冻结不频繁。增加numHelperThreads 会增加c3p0 的线程池在被完全阻塞之前可以容忍的冻结任务的数量。减少maxAdministrativeTaskTime 会减少阻塞的生命周期。显然,正确的做法是调试 JDBC 驱动程序和 DBMS 之间的问题。但是,如果这被证明是不可能的,有时您可以采取最好的解决方法。

我会消除(至少现在)这三个设置:

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

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

前两个开启语句缓存,从应用程序的性能角度来看,这可能是可取的,也可能不是。但它们增加了 c3p0 与 DBMS 交互的复杂性。 SQLServer(在几个数据库中)在多线程使用 Connection 方面非常脆弱(至少根据 JDBC 规范的早期版本,这应该是合法的,但太糟糕了)。将 statementCacheNumDeferredCloseThreads 设置为 1 可确保 Statement 缓存在 Connection 正在使用时不会尝试关闭即将到期的 Statement,从而防止冻结,APPARENT DEADLOCKs 通常显示为挂起的 Statement 关闭任务,而不是你的问题。如果您打开语句缓存,请务必将statementCacheNumDeferredCloseThreads 设置为1 以避免冻结。但最安全、最明智的做法是在调试主要问题之前避免语句缓存的所有复杂性。您可以稍后恢复这些设置以测试它们是否会提高您的应用程序的性能。 (如果您确实重新打开了语句缓存,我的建议是您只设置maxStatementsPerConnection,而不设置全局maxStatements,或者如果您同时设置两者,请将每个连接限制设置为比全局限制。但现在,再次关闭所有这些东西。)

要解决您的具体问题:

  1. 为什么应用程序在开发环境中运行良好,而在生产过程中遇到这些问题?

这是您在调试 JDBC 驱动程序和 DBMS 之间的挂起时要使用的重要线索。关于您的生产服务器的某些事情会导致在您的开发服务器中没有出现的挂起。这可能只是开发服务器上相对较低的负载和生产服务器上的高负载的问题。但可能存在其他设置差异,可以提供有关挂起的线索。

  1. 最重要的是,如何补救?

调试挂起。如果您无法调试挂起,请尝试使用较短的 maxAdministrativeTaskTime 和较大的 numHelperThreads 来解决此问题。

  1. 当连接达到使用 setMaxStatements 和 setMaxStatementsPerConnection 定义的最大语句数时,会发生什么情况?连接已关闭,然后在创建另一个连接时另一个接管?

连接没有到达任何这些东西。这些是描述语句缓存的参数。当缓存语句的总数达到maxStatements 时,最近最少使用的缓存语句被关闭(只是语句,而不是它的连接)。当连接的maxStatementsPerConnection 被命中时,该连接的最近最少使用的缓存语句被关闭(但连接本身保持打开和活动状态)。

  1. 我不太了解 setStatementCacheNumDeferredCloseThreads 函数对我的应用程序的影响。

如果您正在使用语句缓存(再次,我建议您现在将其关闭),此设置可确保过期语句(见上文)在其父连接被其他线程使用时不会close()ed .该设置创建一个专用线程(或多个线程),其唯一目的是等待不再使用连接并仅在那时关闭它们(因此,语句缓存延迟关闭线程)。

我希望这会有所帮助!

更新:您遇到的错误看起来很像Java 6 bug。如果您运行的是 Java 6,那么您很幸运,修复可能只是将您的生产 JVM 更新到 Java 6 的最新版本。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-15
    • 2017-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-19
    • 1970-01-01
    相关资源
    最近更新 更多