diff --git a/src/main/java/com/alibaba/druid/pool/DruidAbstractDataSource.java b/src/main/java/com/alibaba/druid/pool/DruidAbstractDataSource.java index 6f72fe10e0..70ed34ba2b 100644 --- a/src/main/java/com/alibaba/druid/pool/DruidAbstractDataSource.java +++ b/src/main/java/com/alibaba/druid/pool/DruidAbstractDataSource.java @@ -256,6 +256,9 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements protected boolean initGlobalVariants = false; protected volatile boolean onFatalError = false; protected volatile int onFatalErrorMaxActive = 0; + protected volatile long lastFatalErrorTimeMillis = 0; + protected volatile String lastFatalErrorSql = null; + protected volatile Throwable lastFatalError = null; protected static AtomicIntegerFieldUpdater directCreateCountUpdater = AtomicIntegerFieldUpdater.newUpdater(DruidAbstractDataSource.class, "directCreateCount"); protected static AtomicIntegerFieldUpdater createCountUpdater = AtomicIntegerFieldUpdater.newUpdater(DruidAbstractDataSource.class, "creatingCount"); @@ -1493,8 +1496,11 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements } } - public abstract void handleConnectionException(DruidPooledConnection pooledConnection, Throwable t) - throws SQLException; + public void handleConnectionException(DruidPooledConnection conn, Throwable t) throws SQLException { + handleConnectionException(conn, t, null); + } + + public abstract void handleConnectionException(DruidPooledConnection conn, Throwable t, String sql) throws SQLException; protected abstract void recycle(DruidPooledConnection pooledConnection) throws SQLException; diff --git a/src/main/java/com/alibaba/druid/pool/DruidDataSource.java b/src/main/java/com/alibaba/druid/pool/DruidDataSource.java index 4077f4edaa..18ecd6bdc9 100644 --- a/src/main/java/com/alibaba/druid/pool/DruidDataSource.java +++ b/src/main/java/com/alibaba/druid/pool/DruidDataSource.java @@ -23,9 +23,9 @@ import java.security.PrivilegedAction; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; +import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.locks.Lock; @@ -1416,7 +1416,20 @@ public class DruidDataSource extends DruidAbstractDataSource implements DruidDat && onFatalErrorMaxActive > 0 && activeCount >= onFatalErrorMaxActive) { connectErrorCountUpdater.incrementAndGet(this); - throw new SQLException("onFatalError, activeCount " + activeCount + ", onFatalErrorMaxActive " + onFatalErrorMaxActive); + + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String errorMsg = "onFatalError, activeCount " + activeCount + + ", onFatalErrorMaxActive " + onFatalErrorMaxActive; + + if (lastFatalErrorTimeMillis > 0) { + errorMsg += ", time '" + format.format(new Date(lastFatalErrorTimeMillis)) + "'"; + } + + if (lastFatalErrorSql != null) { + errorMsg += ", sql \n" + lastFatalErrorSql; + } + + throw new SQLException(errorMsg, lastFatalError); } connectCount++; @@ -1498,7 +1511,7 @@ public class DruidDataSource extends DruidAbstractDataSource implements DruidDat return poolalbeConnection; } - public void handleConnectionException(DruidPooledConnection pooledConnection, Throwable t) throws SQLException { + public void handleConnectionException(DruidPooledConnection pooledConnection, Throwable t, String sql) throws SQLException { final DruidConnectionHolder holder = pooledConnection.getConnectionHolder(); errorCount.incrementAndGet(); @@ -1516,45 +1529,7 @@ public class DruidDataSource extends DruidAbstractDataSource implements DruidDat // exceptionSorter.isExceptionFatal if (exceptionSorter != null && exceptionSorter.isExceptionFatal(sqlEx)) { - if (pooledConnection.isTraceEnable()) { - activeConnectionLock.lock(); - try { - if (pooledConnection.isTraceEnable()) { - activeConnections.remove(pooledConnection); - pooledConnection.setTraceEnable(false); - } - } finally { - activeConnectionLock.unlock(); - } - } - - boolean requireDiscard = false; - final ReentrantLock lock = pooledConnection.lock; - lock.lock(); - try { - if ((!pooledConnection.isClosed()) || !pooledConnection.isDisable()) { - holder.setDiscard(true); - pooledConnection.disable(t); - requireDiscard = true; - } - - onFatalError = true; - } finally { - lock.unlock(); - } - - if (requireDiscard) { - if (holder.statementTrace != null) { - for (Statement stmt : holder.statementTrace) { - JdbcUtils.close(stmt); - } - } - - this.discardConnection(holder.getConnection()); - holder.setDiscard(true); - } - - LOG.error("discard connection", sqlEx); + handleFatalError(pooledConnection, sqlEx, sql); } throw sqlEx; @@ -1563,6 +1538,58 @@ public class DruidDataSource extends DruidAbstractDataSource implements DruidDat } } + protected final void handleFatalError(DruidPooledConnection conn, SQLException error, String sql) throws SQLException { + final DruidConnectionHolder holder = conn.holder; + + if (conn.isTraceEnable()) { + activeConnectionLock.lock(); + try { + if (conn.isTraceEnable()) { + activeConnections.remove(conn); + conn.setTraceEnable(false); + } + } finally { + activeConnectionLock.unlock(); + } + } + + long lastErrorTimeMillis = this.lastErrorTimeMillis; + if (lastErrorTimeMillis == 0) { + lastErrorTimeMillis = System.currentTimeMillis(); + } + + boolean requireDiscard = false; + final ReentrantLock lock = conn.lock; + lock.lock(); + try { + if ((!conn.isClosed()) || !conn.isDisable()) { + holder.setDiscard(true); + conn.disable(error); + requireDiscard = true; + } + + lastFatalErrorTimeMillis = lastErrorTimeMillis; + onFatalError = true; + lastFatalError = error; + lastFatalErrorSql = sql; + } finally { + lock.unlock(); + } + + if (requireDiscard) { + if (holder.statementTrace != null) { + for (Statement stmt : holder.statementTrace) { + JdbcUtils.close(stmt); + } + } + + this.discardConnection(holder.getConnection()); + holder.setDiscard(true); + } + + LOG.error("discard connection", error); + } + /** * 回收连接 */ diff --git a/src/main/java/com/alibaba/druid/pool/DruidPooledConnection.java b/src/main/java/com/alibaba/druid/pool/DruidPooledConnection.java index c604cfd7d5..e65b89af91 100644 --- a/src/main/java/com/alibaba/druid/pool/DruidPooledConnection.java +++ b/src/main/java/com/alibaba/druid/pool/DruidPooledConnection.java @@ -121,12 +121,16 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. } public SQLException handleException(Throwable t) throws SQLException { + return handleException(t, null); + } + + public SQLException handleException(Throwable t, String sql) throws SQLException { final DruidConnectionHolder holder = this.holder; // if (holder != null) { DruidAbstractDataSource dataSource = holder.getDataSource(); - dataSource.handleConnectionException(this, t); + dataSource.handleConnectionException(this, t, sql); } if (t instanceof SQLException) { @@ -151,7 +155,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { rawStatement.clearParameters(); } catch (SQLException ex) { - this.handleException(ex); + this.handleException(ex, null); if (rawStatement.getConnection().isClosed()) { return; } @@ -184,7 +188,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. //In this case, the exception is desired to be properly handled to remove the unusable connection from the pool. stmt.closeInternal(); } catch (SQLException ex) { - this.handleException(ex); + this.handleException(ex, null); throw ex; } finally { holder.getDataSource().incrementClosedPreparedStatementCount(); @@ -345,7 +349,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. stmtHolder = new PreparedStatementHolder(key, conn.prepareStatement(sql)); holder.getDataSource().incrementPreparedStatementCount(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, sql); } } @@ -384,7 +388,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. resultSetConcurrency)); holder.getDataSource().incrementPreparedStatementCount(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, sql); } } @@ -419,7 +423,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. resultSetHoldability)); holder.getDataSource().incrementPreparedStatementCount(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, sql); } } @@ -450,7 +454,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. stmtHolder = new PreparedStatementHolder(key, conn.prepareStatement(sql, columnIndexes)); holder.getDataSource().incrementPreparedStatementCount(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, sql); } } @@ -481,7 +485,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. stmtHolder = new PreparedStatementHolder(key, conn.prepareStatement(sql, columnNames)); holder.getDataSource().incrementPreparedStatementCount(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, sql); } } @@ -512,7 +516,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. stmtHolder = new PreparedStatementHolder(key, conn.prepareStatement(sql, autoGeneratedKeys)); holder.getDataSource().incrementPreparedStatementCount(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, sql); } } @@ -545,7 +549,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. stmtHolder = new PreparedStatementHolder(key, conn.prepareCall(sql)); holder.getDataSource().incrementPreparedStatementCount(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, sql); } } @@ -580,7 +584,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. resultSetHoldability)); holder.getDataSource().incrementPreparedStatementCount(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, sql); } } @@ -613,7 +617,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. conn.prepareCall(sql, resultSetType, resultSetConcurrency)); holder.getDataSource().incrementPreparedStatementCount(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, sql); } } @@ -635,7 +639,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { stmt = conn.createStatement(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); } holder.getDataSource().initStatement(this, stmt); @@ -655,7 +659,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { stmt = conn.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); } holder.getDataSource().initStatement(this, stmt); @@ -674,7 +678,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { stmt = conn.createStatement(resultSetType, resultSetConcurrency); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); } holder.getDataSource().initStatement(this, stmt); @@ -708,7 +712,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. conn.setAutoCommit(autoCommit); holder.setUnderlyingAutoCommit(autoCommit); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); } } @@ -744,7 +748,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { conn.commit(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); } finally { handleEndTransaction(dataSource, null); } @@ -776,7 +780,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { conn.rollback(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); } finally { handleEndTransaction(dataSource, null); } @@ -789,7 +793,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { return conn.setSavepoint(name); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); return null; // never arrive } } @@ -806,7 +810,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { conn.rollback(savepoint); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); } finally { handleEndTransaction(dataSource, savepoint); } @@ -831,7 +835,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { conn.releaseSavepoint(savepoint); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); } } @@ -842,7 +846,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { return conn.createClob(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); return null; // never arrive } } @@ -885,7 +889,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { conn.setReadOnly(readOnly); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); } holder.setUnderlyingReadOnly(readOnly); @@ -905,7 +909,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { conn.setCatalog(catalog); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); } } @@ -930,7 +934,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { conn.setTransactionIsolation(level); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); } holder.setUnderlyingTransactionIsolation(level); } @@ -955,7 +959,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { conn.clearWarnings(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); } } @@ -1006,7 +1010,7 @@ public class DruidPooledConnection extends PoolableWrapper implements javax.sql. try { return conn.setSavepoint(); } catch (SQLException ex) { - handleException(ex); + handleException(ex, null); return null; } } diff --git a/src/main/java/com/alibaba/druid/pool/DruidPooledPreparedStatement.java b/src/main/java/com/alibaba/druid/pool/DruidPooledPreparedStatement.java index 29c26dbfed..67cf68b2fa 100644 --- a/src/main/java/com/alibaba/druid/pool/DruidPooledPreparedStatement.java +++ b/src/main/java/com/alibaba/druid/pool/DruidPooledPreparedStatement.java @@ -195,7 +195,7 @@ public class DruidPooledPreparedStatement extends DruidPooledStatement implement currentFetchSize = defaultFetchSize; } } catch (Exception e) { - this.conn.handleException(e); + this.conn.handleException(e, null); } } diff --git a/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java b/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java index 640450a874..d991a4bad7 100644 --- a/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java +++ b/src/main/java/com/alibaba/druid/pool/DruidPooledStatement.java @@ -66,13 +66,25 @@ public class DruidPooledStatement extends PoolableWrapper implements Statement { } protected SQLException checkException(Throwable error) throws SQLException { + String sql = null; + if (this instanceof DruidPooledPreparedStatement) { + sql = ((DruidPooledPreparedStatement) this).getSql(); + } + handleScoketTimeout(error); exceptionCount++; - return conn.handleException(error); + return conn.handleException(error, sql); } - private void handleScoketTimeout(Throwable error) throws SQLException { + protected SQLException checkException(Throwable error, String sql) throws SQLException { + handleScoketTimeout(error); + + exceptionCount++; + return conn.handleException(error, sql); + } + + protected void handleScoketTimeout(Throwable error) throws SQLException { if (this.conn == null || this.conn.transactionInfo != null || this.conn.holder == null) { @@ -228,7 +240,7 @@ public class DruidPooledStatement extends PoolableWrapper implements Statement { } catch (Throwable t) { errorCheck(t); - throw checkException(t); + throw checkException(t, sql); } finally { conn.afterExecute(); } @@ -247,7 +259,7 @@ public class DruidPooledStatement extends PoolableWrapper implements Statement { } catch (Throwable t) { errorCheck(t); - throw checkException(t); + throw checkException(t, sql); } finally { conn.afterExecute(); } @@ -298,7 +310,7 @@ public class DruidPooledStatement extends PoolableWrapper implements Statement { } catch (Throwable t) { errorCheck(t); - throw checkException(t); + throw checkException(t, sql); } finally { conn.afterExecute(); } @@ -317,7 +329,7 @@ public class DruidPooledStatement extends PoolableWrapper implements Statement { } catch (Throwable t) { errorCheck(t); - throw checkException(t); + throw checkException(t, sql); } finally { conn.afterExecute(); } @@ -336,7 +348,7 @@ public class DruidPooledStatement extends PoolableWrapper implements Statement { } catch (Throwable t) { errorCheck(t); - throw checkException(t); + throw checkException(t, sql); } finally { conn.afterExecute(); } @@ -355,7 +367,7 @@ public class DruidPooledStatement extends PoolableWrapper implements Statement { } catch (Throwable t) { errorCheck(t); - throw checkException(t); + throw checkException(t, sql); } finally { conn.afterExecute(); } @@ -374,7 +386,7 @@ public class DruidPooledStatement extends PoolableWrapper implements Statement { } catch (Throwable t) { errorCheck(t); - throw checkException(t); + throw checkException(t, sql); } finally { conn.afterExecute(); } @@ -393,7 +405,7 @@ public class DruidPooledStatement extends PoolableWrapper implements Statement { } catch (Throwable t) { errorCheck(t); - throw checkException(t); + throw checkException(t, sql); } finally { conn.afterExecute(); } @@ -547,7 +559,7 @@ public class DruidPooledStatement extends PoolableWrapper implements Statement { } catch (Throwable t) { errorCheck(t); - throw checkException(t); + throw checkException(t, sql); } } @@ -677,7 +689,7 @@ public class DruidPooledStatement extends PoolableWrapper implements Statement { try { stmt.addBatch(sql); } catch (Throwable t) { - throw checkException(t); + throw checkException(t, sql); } } diff --git a/src/test/java/com/alibaba/druid/bvt/pool/OnFatalErrorMaxActiveTest.java b/src/test/java/com/alibaba/druid/bvt/pool/OnFatalErrorMaxActiveTest.java index d862d4dba5..75f0b3e581 100644 --- a/src/test/java/com/alibaba/druid/bvt/pool/OnFatalErrorMaxActiveTest.java +++ b/src/test/java/com/alibaba/druid/bvt/pool/OnFatalErrorMaxActiveTest.java @@ -3,9 +3,11 @@ package com.alibaba.druid.bvt.pool; import com.alibaba.druid.PoolTestCase; import com.alibaba.druid.pool.DruidAbstractDataSource; import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.pool.DruidPooledConnection; import com.alibaba.druid.util.JdbcUtils; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; @@ -28,14 +30,16 @@ public class OnFatalErrorMaxActiveTest extends PoolTestCase { Field field = DruidAbstractDataSource.class.getDeclaredField("onFatalError"); field.setAccessible(true); + SQLException faltalError = new SQLException(); { Connection conn = dataSource.getConnection(); + + Method method = DruidDataSource.class.getDeclaredMethod("handleFatalError", DruidPooledConnection.class, SQLException.class, String.class); + method.setAccessible(true); + method.invoke(dataSource, conn.unwrap(DruidPooledConnection.class), faltalError, "select 'x'"); conn.close(); } - field.set(dataSource, true); - - Connection conn_0 = dataSource.getConnection(); assertTrue(dataSource.isOnFatalError()); @@ -46,7 +50,10 @@ public class OnFatalErrorMaxActiveTest extends PoolTestCase { error = ex; } assertNotNull(error); - assertEquals("onFatalError, activeCount 1, onFatalErrorMaxActive 1", error.getMessage()); + assertTrue(error.getMessage().startsWith("onFatalError, activeCount 1, onFatalErrorMaxActive 1")); + System.out.println(error.getMessage()); + assertNotNull(error.getCause()); + assertSame(faltalError, error.getCause()); Statement stmt = conn_0.createStatement(); ResultSet rs = stmt.executeQuery("select 1");