55import io .ebean .datasource .pool .ConnectionPool .Status ;
66
77import java .sql .SQLException ;
8+ import java .util .List ;
89import java .util .concurrent .TimeUnit ;
910import java .util .concurrent .locks .Condition ;
1011import java .util .concurrent .locks .ReentrantLock ;
@@ -87,12 +88,7 @@ private PoolStatus createStatus() {
8788
8889 @ Override
8990 public String toString () {
90- lock .lock ();
91- try {
92- return createStatus ().toString ();
93- } finally {
94- lock .unlock ();
95- }
91+ return status (false ).toString ();
9692 }
9793
9894 PoolStatus status (boolean reset ) {
@@ -147,31 +143,40 @@ void ensureMinimumConnections() throws SQLException {
147143 /**
148144 * Return a PooledConnection.
149145 */
150- void returnPooledConnection (PooledConnection c , boolean forceClose ) {
146+ void returnPooledConnection (PooledConnection c , boolean forceClose , boolean logErrors ) {
147+ boolean closeConnection = false ;
151148 lock .lock ();
152149 try {
153150 if (!busyList .remove (c )) {
154151 Log .error ("Connection [{0}] not found in BusyList?" , c );
155152 }
156153 if (forceClose || c .shouldTrimOnReturn (lastResetTime , maxAgeMillis )) {
157- c . closeConnectionFully ( false );
154+ closeConnection = true ; // close outside lock
158155 } else {
159156 freeList .add (c );
160- notEmpty .signal ();
157+ notEmpty .signal (); // notify _obtainConnectionWaitLoop, that there are new connections in the freelist
161158 }
162159 } finally {
163160 lock .unlock ();
164161 }
162+ if (closeConnection ) {
163+ c .closeConnectionFully (logErrors );
164+ }
165165 }
166166
167- private PooledConnection extractFromFreeList () {
167+ /**
168+ * Returns one connection from the free list and put it into the busy list or null if no connections are free.
169+ *
170+ * @throws StaleConnectionException there was a connection in the freeList, but it is stale.
171+ * The connection has to be closed outside the lock.
172+ */
173+ private PooledConnection extractFromFreeList () throws StaleConnectionException {
168174 if (freeList .isEmpty ()) {
169175 return null ;
170176 }
171177 final PooledConnection c = freeList .remove ();
172178 if (validateStaleMillis > 0 && staleEviction (c )) {
173- c .closeConnectionFully (false );
174- return null ;
179+ throw new StaleConnectionException (c );
175180 }
176181 registerBusyConnection (c );
177182 return c ;
@@ -192,15 +197,24 @@ private boolean stale(PooledConnection c) {
192197 }
193198
194199 PooledConnection obtainConnection () throws SQLException {
195- try {
196- PooledConnection pc = _obtainConnection ();
197- pc .resetForUse ();
198- return pc ;
199-
200- } catch (InterruptedException e ) {
201- // restore the interrupted status as we throw SQLException
202- Thread .currentThread ().interrupt ();
203- throw new SQLException ("Interrupted getting connection from pool" , e );
200+ var start = System .nanoTime ();
201+ while (true ) {
202+ try {
203+ PooledConnection pc = _obtainConnection ();
204+ pc .resetForUse ();
205+ final var elapsed = System .nanoTime () - start ;
206+ totalAcquireNanos += elapsed ;
207+ maxAcquireNanos = Math .max (maxAcquireNanos , elapsed );
208+ return pc ;
209+
210+ } catch (InterruptedException e ) {
211+ // restore the interrupted status as we throw SQLException
212+ Thread .currentThread ().interrupt ();
213+ throw new SQLException ("Interrupted getting connection from pool" , e );
214+ } catch (StaleConnectionException e ) {
215+ e .getConnection ().closeConnectionFully (true );
216+ // try again...
217+ }
204218 }
205219 }
206220
@@ -215,8 +229,8 @@ private int registerBusyConnection(PooledConnection connection) {
215229 return busySize ;
216230 }
217231
218- private PooledConnection _obtainConnection () throws InterruptedException , SQLException {
219- var start = System . nanoTime ();
232+ private PooledConnection _obtainConnection () throws InterruptedException , SQLException , StaleConnectionException {
233+
220234 lock .lockInterruptibly ();
221235 try {
222236 if (doingShutdown ) {
@@ -246,9 +260,6 @@ private PooledConnection _obtainConnection() throws InterruptedException, SQLExc
246260 waitingThreads --;
247261 }
248262 } finally {
249- final var elapsed = System .nanoTime () - start ;
250- totalAcquireNanos += elapsed ;
251- maxAcquireNanos = Math .max (maxAcquireNanos , elapsed );
252263 lock .unlock ();
253264 }
254265 }
@@ -270,7 +281,7 @@ private PooledConnection createConnection() throws SQLException {
270281 /**
271282 * Got into a loop waiting for connections to be returned to the pool.
272283 */
273- private PooledConnection _obtainConnectionWaitLoop () throws SQLException , InterruptedException {
284+ private PooledConnection _obtainConnectionWaitLoop () throws SQLException , InterruptedException , StaleConnectionException {
274285 long nanos = MILLIS_TIME_UNIT .toNanos (waitTimeoutMillis );
275286 for (; ; ) {
276287 if (nanos <= 0 ) {
@@ -307,7 +318,7 @@ PoolStatus shutdown(boolean closeBusyConnections) {
307318 try {
308319 doingShutdown = true ;
309320 PoolStatus status = createStatus ();
310- closeFreeConnections (true );
321+ freeList . closeAll (true );
311322
312323 if (!closeBusyConnections ) {
313324 // connections close on return to pool
@@ -332,14 +343,14 @@ PoolStatus shutdown(boolean closeBusyConnections) {
332343 * <p>
333344 * This is typically done when a database down event occurs.
334345 */
335- void reset (long leakTimeMinutes ) {
346+ void reset (long leakTimeMinutes , boolean logErrors ) {
336347 lock .lock ();
337348 try {
338349 PoolStatus status = createStatus ();
339350 Log .info ("Resetting DataSource [{0}] {1}" , name , status );
340351 lastResetTime = System .currentTimeMillis ();
341352
342- closeFreeConnections ( false );
353+ freeList . closeAll ( logErrors );
343354 closeBusyConnections (leakTimeMinutes );
344355
345356 String busyInfo = getBusyConnectionInformation ();
@@ -353,17 +364,12 @@ void reset(long leakTimeMinutes) {
353364 }
354365
355366 void trim (long maxInactiveMillis , long maxAgeMillis ) {
356- lock .lock ();
357- try {
358- if (trimInactiveConnections (maxInactiveMillis , maxAgeMillis )) {
359- try {
360- ensureMinimumConnections ();
361- } catch (SQLException e ) {
362- Log .error ("Error trying to ensure minimum connections" , e );
363- }
367+ if (trimInactiveConnections (maxInactiveMillis , maxAgeMillis )) {
368+ try {
369+ ensureMinimumConnections ();
370+ } catch (SQLException e ) {
371+ Log .error ("Error trying to ensure minimum connections" , e );
364372 }
365- } finally {
366- lock .unlock ();
367373 }
368374 }
369375
@@ -372,33 +378,32 @@ void trim(long maxInactiveMillis, long maxAgeMillis) {
372378 */
373379 private boolean trimInactiveConnections (long maxInactiveMillis , long maxAgeMillis ) {
374380 final long createdSince = (maxAgeMillis == 0 ) ? 0 : System .currentTimeMillis () - maxAgeMillis ;
375- final int trimmedCount ;
376- if (freeList .size () > minSize ) {
377- // trim on maxInactive and maxAge
378- long usedSince = System .currentTimeMillis () - maxInactiveMillis ;
379- trimmedCount = freeList .trim (minSize , usedSince , createdSince );
380- } else if (createdSince > 0 ) {
381- // trim only on maxAge
382- trimmedCount = freeList .trim (0 , createdSince , createdSince );
383- } else {
384- trimmedCount = 0 ;
385- }
386- if (trimmedCount > 0 && Log .isLoggable (DEBUG )) {
387- Log .debug ("DataSource [{0}] trimmed [{1}] inactive connections. New size[{2}]" , name , trimmedCount , totalConnections ());
388- }
389- return trimmedCount > 0 && freeList .size () < minSize ;
390- }
391-
392- /**
393- * Close all the connections that are in the free list.
394- */
395- private void closeFreeConnections (boolean logErrors ) {
381+ final List <PooledConnection > trimmed ;
396382 lock .lock ();
397383 try {
398- freeList .closeAll (logErrors );
384+ if (freeList .size () > minSize ) {
385+ // trim on maxInactive and maxAge
386+ long usedSince = System .currentTimeMillis () - maxInactiveMillis ;
387+ trimmed = freeList .trim (minSize , usedSince , createdSince );
388+ } else if (createdSince > 0 ) {
389+ // trim only on maxAge
390+ trimmed = freeList .trim (0 , createdSince , createdSince );
391+ } else {
392+ trimmed = null ;
393+ }
399394 } finally {
400395 lock .unlock ();
401396 }
397+ if (trimmed != null ) {
398+ if (Log .isLoggable (DEBUG )) {
399+ Log .debug ("DataSource [{0}] trimmed [{1}] inactive connections. New size[{2}]" , name , trimmed .size (), totalConnections ());
400+ }
401+ for (PooledConnection pc : trimmed ) {
402+ pc .closeConnectionFully (true );
403+ }
404+ return freeList .size () < minSize ;
405+ }
406+ return false ;
402407 }
403408
404409 /**
@@ -412,12 +417,23 @@ private void closeFreeConnections(boolean logErrors) {
412417 * closed and put back into the pool.
413418 */
414419 void closeBusyConnections (long leakTimeMinutes ) {
420+ List <PooledConnection > busyConnections ;
415421 lock .lock ();
416422 try {
417- busyList .closeBusyConnections (leakTimeMinutes );
423+ busyConnections = busyList .removeBusyConnections (leakTimeMinutes );
418424 } finally {
419425 lock .unlock ();
420426 }
427+ if (busyConnections != null ) {
428+ for (PooledConnection pc : busyConnections ) {
429+ try {
430+ Log .warn ("DataSource closing busy connection? {0}" , pc .fullDescription ());
431+ pc .closeConnectionFully (true );
432+ } catch (Exception ex ) {
433+ Log .error ("Error when closing potentially leaked connection " + pc .description (), ex );
434+ }
435+ }
436+ }
421437 }
422438
423439 String getBusyConnectionInformation () {
0 commit comments