Skip to content

Commit 255090d

Browse files
committed
closeConnectionFully is performed without lock
1 parent 74978ae commit 255090d

File tree

5 files changed

+124
-89
lines changed

5 files changed

+124
-89
lines changed

ebean-datasource/src/main/java/io/ebean/datasource/pool/BusyConnectionBuffer.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.ebean.datasource.pool;
22

3+
import java.util.ArrayList;
34
import java.util.Arrays;
5+
import java.util.List;
46

57
/**
68
* A buffer especially designed for Busy PooledConnections.
@@ -82,9 +84,10 @@ boolean remove(PooledConnection pc) {
8284
}
8385

8486
/**
85-
* Close connections that should be considered leaked.
87+
* Remove connections that should be considered leaked.
8688
*/
87-
void closeBusyConnections(long leakTimeMinutes) {
89+
List<PooledConnection> removeBusyConnections(long leakTimeMinutes) {
90+
List<PooledConnection> busyConnections = null;
8891
long olderThanTime = System.currentTimeMillis() - (leakTimeMinutes * 60000);
8992
Log.debug("Closing busy connections using leakTimeMinutes {0}", leakTimeMinutes);
9093
for (int i = 0; i < slots.length; i++) {
@@ -98,20 +101,14 @@ void closeBusyConnections(long leakTimeMinutes) {
98101
} else {
99102
slots[i] = null;
100103
--size;
101-
closeBusyConnection(pc);
104+
if (busyConnections == null) {
105+
busyConnections = new ArrayList<>();
106+
}
107+
busyConnections.add(pc);
102108
}
103109
}
104110
}
105-
}
106-
107-
private void closeBusyConnection(PooledConnection pc) {
108-
try {
109-
Log.warn("DataSource closing busy connection? {0}", pc.fullDescription());
110-
System.out.println("CLOSING busy connection: " + pc.fullDescription());
111-
pc.closeConnectionFully(false);
112-
} catch (Exception ex) {
113-
Log.error("Error when closing potentially leaked connection " + pc.description(), ex);
114-
}
111+
return busyConnections;
115112
}
116113

117114
/**

ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ public SQLException dataSourceDownReason() {
292292

293293
private void notifyDataSourceIsDown(SQLException reason) {
294294
if (dataSourceUp.get()) {
295-
reset();
295+
reset(false);
296296
notifyDown(reason);
297297
}
298298
}
@@ -316,7 +316,7 @@ private void notifyDown(SQLException reason) {
316316

317317
private void notifyDataSourceIsUp() {
318318
if (!dataSourceUp.get()) {
319-
reset();
319+
reset(true);
320320
notifyUp();
321321
}
322322
}
@@ -553,7 +553,7 @@ void returnConnectionForceClose(PooledConnection pooledConnection, boolean testP
553553
}
554554

555555
void removeClosedConnection(PooledConnection pooledConnection) {
556-
queue.returnPooledConnection(pooledConnection, true);
556+
queue.returnPooledConnection(pooledConnection, true, false);
557557
}
558558

559559
/**
@@ -564,13 +564,13 @@ private void returnTheConnection(PooledConnection pooledConnection, boolean forc
564564
if (poolListener != null && !forceClose) {
565565
poolListener.onBeforeReturnConnection(pooledConnection);
566566
}
567-
queue.returnPooledConnection(pooledConnection, forceClose);
567+
queue.returnPooledConnection(pooledConnection, forceClose, true);
568568
}
569569

570570
void returnConnectionReset(PooledConnection pooledConnection) {
571-
queue.returnPooledConnection(pooledConnection, true);
571+
queue.returnPooledConnection(pooledConnection, true, false);
572572
Log.warn("Resetting DataSource on read-only failure [{0}]", name);
573-
reset();
573+
reset(false);
574574
}
575575

576576
/**
@@ -599,9 +599,9 @@ PooledConnection createConnectionForQueue(int connId) throws SQLException {
599599
* <li>Busy connections are closed when they are returned to the pool.</li>
600600
* </ul>
601601
*/
602-
private void reset() {
602+
private void reset(boolean logErrors) {
603603
heartbeatPoolExhaustedCount = 0;
604-
queue.reset(leakTimeMinutes);
604+
queue.reset(leakTimeMinutes, logErrors);
605605
}
606606

607607
/**

ebean-datasource/src/main/java/io/ebean/datasource/pool/FreeConnectionBuffer.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,22 @@ void closeAll(boolean logErrors) {
5959

6060
/**
6161
* Trim any inactive connections that have not been used since usedSince.
62+
* <p>
63+
* The connections are returned to close.
6264
*/
63-
int trim(int minSize, long usedSince, long createdSince) {
64-
int trimCount = 0;
65+
List<PooledConnection> trim(int minSize, long usedSince, long createdSince) {
66+
List<PooledConnection> trimmedConnections = null;
6567
ListIterator<PooledConnection> iterator = freeBuffer.listIterator(minSize);
6668
while (iterator.hasNext()) {
6769
PooledConnection pooledConnection = iterator.next();
6870
if (pooledConnection.shouldTrim(usedSince, createdSince)) {
6971
iterator.remove();
70-
pooledConnection.closeConnectionFully(true);
71-
trimCount++;
72+
if (trimmedConnections == null) {
73+
trimmedConnections = new ArrayList<>();
74+
}
75+
trimmedConnections.add(pooledConnection);
7276
}
7377
}
74-
return trimCount;
78+
return trimmedConnections;
7579
}
7680
}

ebean-datasource/src/main/java/io/ebean/datasource/pool/PooledConnectionQueue.java

Lines changed: 79 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.ebean.datasource.pool.ConnectionPool.Status;
66

77
import java.sql.SQLException;
8+
import java.util.List;
89
import java.util.concurrent.TimeUnit;
910
import java.util.concurrent.locks.Condition;
1011
import 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

Comments
 (0)