Skip to content

Commit fde2066

Browse files
committed
Improved performance in the ConnectionPool by replacing semaphore with atomic counters and reduced validation overhead.
1 parent fde6f06 commit fde2066

File tree

1 file changed

+69
-29
lines changed

1 file changed

+69
-29
lines changed

src/javaxt/sql/ConnectionPool.java

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class ConnectionPool {
2525
private int minConnections;
2626
private long timeoutMs;
2727
private PrintWriter logWriter;
28-
private Semaphore semaphore; //controls total connections
28+
private final AtomicInteger totalConnections = new AtomicInteger(0); // Lock-free connection counting
2929
private PoolConnectionEventListener poolConnectionEventListener;
3030

3131
// Health monitoring and validation
@@ -47,6 +47,10 @@ public class ConnectionPool {
4747
private volatile PooledConnection connectionInTransition; // a PooledConnection which is currently within a PooledConnection.getConnection() call, or null
4848
private Database database;
4949

50+
// Validation caching for performance optimization
51+
private final ConcurrentHashMap<PooledConnection, Long> validationCache = new ConcurrentHashMap<>();
52+
private static final long VALIDATION_CACHE_TTL = 30000; // 30 seconds
53+
5054

5155
/**
5256
* Thrown in {@link #getConnection()} or {@link #getValidConnection()} when no free connection becomes
@@ -145,8 +149,8 @@ public ConnectionPool(ConnectionPoolDataSource dataSource, int maxConnections,
145149
this.validationQuery = validationQuery;
146150
this.validationTimeout = validationTimeout;
147151

148-
// Initialize semaphore to control total connections
149-
this.semaphore = new Semaphore(maxConnections, true);
152+
// Initialize atomic counter for lock-free connection management
153+
this.totalConnections.set(0);
150154

151155
try { logWriter = dataSource.getLogWriter(); }
152156
catch (SQLException e) {}
@@ -287,24 +291,27 @@ private java.sql.Connection acquireConnection(long timeoutMs) throws SQLExceptio
287291
}
288292

289293
// No recycled connection available, try to create a new one
290-
// Use semaphore to atomically control connection limit
291-
try {
292-
if (semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)) {
294+
// Use atomic counter for lock-free connection limit control
295+
int currentTotal = totalConnections.get();
296+
if (currentTotal < maxConnections) {
297+
if (totalConnections.compareAndSet(currentTotal, currentTotal + 1)) {
293298
try {
294299
java.sql.Connection conn = createNewConnectionInternal();
295300
if (conn != null) {
296301
return conn;
297302
} else {
298-
// Connection creation failed, release the permit
299-
semaphore.release();
303+
// Connection creation failed, decrement counter
304+
totalConnections.decrementAndGet();
300305
}
301306
} catch (SQLException e) {
302-
// Connection creation failed, release the permit
303-
semaphore.release();
307+
// Connection creation failed, decrement counter
308+
totalConnections.decrementAndGet();
304309
// Continue to next iteration to try again
305310
}
306311
}
307-
// If we couldn't acquire a permit, wait a bit and try again
312+
}
313+
// If we couldn't create a connection, wait a bit and try again
314+
try {
308315
Thread.sleep(10);
309316
} catch (InterruptedException e) {
310317
Thread.currentThread().interrupt();
@@ -323,10 +330,10 @@ private java.sql.Connection tryGetRecycledConnection() throws SQLException {
323330

324331
PooledConnection pconn = wrapper.connection;
325332

326-
// Smart validation: skip validation for recently used connections if no validation query is configured
327-
boolean needsValidation = (validationQuery != null && !validationQuery.trim().isEmpty()) ||
328-
wrapper.isExpired(connectionMaxAgeMs) ||
329-
wrapper.isIdle(connectionIdleTimeoutMs);
333+
// Smart validation: skip validation for recently used connections
334+
boolean needsValidation = wrapper.isExpired(connectionMaxAgeMs) ||
335+
wrapper.isIdle(connectionIdleTimeoutMs) ||
336+
!isRecentlyValidated(pconn);
330337

331338
if (needsValidation && !validateConnection(pconn)) {
332339
// Connection is invalid, dispose it properly
@@ -389,6 +396,7 @@ private java.sql.Connection createNewConnectionInternal() throws SQLException {
389396
connectionInTransition = pconn;
390397
conn = pconn.getConnection();
391398
activeConnections.incrementAndGet();
399+
// totalConnections was already incremented in acquireConnection
392400
return conn;
393401
} catch (SQLException e) {
394402
connectionInTransition = null;
@@ -718,11 +726,15 @@ private void performHealthCheck() {
718726
while (currentRecycled < minConnections && total < maxConnections && !isDisposed.get() && attempts < maxAttempts) {
719727
attempts++;
720728

721-
// Try to acquire a semaphore permit for the warm-up connection
722-
if (!semaphore.tryAcquire()) {
723-
log("No semaphore permits available for pool warm-up");
729+
// Try to increment totalConnections for the warm-up connection
730+
int currentTotal = totalConnections.get();
731+
if (currentTotal >= maxConnections) {
732+
log("Maximum connections reached for pool warm-up");
724733
break;
725734
}
735+
if (!totalConnections.compareAndSet(currentTotal, currentTotal + 1)) {
736+
continue; // Try again if CAS failed
737+
}
726738

727739
PooledConnection pconn = null;
728740
try {
@@ -737,16 +749,16 @@ private void performHealthCheck() {
737749
total = currentActive + currentRecycled;
738750
log("Pool warm-up: added connection " + currentRecycled + "/" + minConnections);
739751
} else {
740-
// If validation fails, dispose the connection and release the permit
752+
// If validation fails, dispose the connection and decrement counter
741753
disposeConnection(pconn);
742754
pconn = null; // Ensure pconn is null so finally block doesn't try to close it again
743755
}
744756
} catch (SQLException e) {
745757
log("Failed to create or validate connection during warm-up: " + e.getMessage());
746758
if (pconn != null) {
747-
disposeConnection(pconn); // Dispose and release permit
759+
disposeConnection(pconn); // Dispose and decrement counter
748760
} else {
749-
semaphore.release(); // Release permit if connection creation failed before pconn was assigned
761+
totalConnections.decrementAndGet(); // Decrement counter if connection creation failed before pconn was assigned
750762
}
751763
}
752764
}
@@ -764,11 +776,33 @@ private void performHealthCheck() {
764776
* Validates a connection using the configured validation query.
765777
* This method is completely isolated from the pool lifecycle to prevent race conditions.
766778
*/
779+
private boolean isRecentlyValidated(PooledConnection pooledConnection) {
780+
if (validationQuery == null || validationQuery.trim().isEmpty()) {
781+
return true; // No validation query configured, consider it valid
782+
}
783+
784+
Long lastValidated = validationCache.get(pooledConnection);
785+
if (lastValidated == null) {
786+
return false; // Never validated
787+
}
788+
789+
long now = System.currentTimeMillis();
790+
return (now - lastValidated) < VALIDATION_CACHE_TTL;
791+
}
792+
767793
private boolean validateConnection(PooledConnection pooledConnection) {
768794
if (validationQuery == null || validationQuery.trim().isEmpty()) {
769795
return true;
770796
}
771797

798+
// Check validation cache first
799+
Long lastValidated = validationCache.get(pooledConnection);
800+
long now = System.currentTimeMillis();
801+
802+
if (lastValidated != null && (now - lastValidated) < VALIDATION_CACHE_TTL) {
803+
return true; // Recently validated, skip actual validation
804+
}
805+
772806
try {
773807
// Temporarily remove the event listener to prevent validation from triggering pool events
774808
pooledConnection.removeConnectionEventListener(poolConnectionEventListener);
@@ -785,7 +819,12 @@ private boolean validateConnection(PooledConnection pooledConnection) {
785819
try (java.sql.PreparedStatement stmt = tempConn.prepareStatement(validationQuery)) {
786820
stmt.setQueryTimeout(validationTimeout);
787821
try (java.sql.ResultSet rs = stmt.executeQuery()) {
788-
return rs.next();
822+
boolean isValid = rs.next();
823+
if (isValid) {
824+
// Cache successful validation
825+
validationCache.put(pooledConnection, now);
826+
}
827+
return isValid;
789828
}
790829
}
791830
// Note: We don't close tempConn here because it belongs to the PooledConnection
@@ -858,8 +897,9 @@ private void recycleConnection (PooledConnection pconn) {
858897
private void disposeConnection (PooledConnection pconn) {
859898
pconn.removeConnectionEventListener(poolConnectionEventListener);
860899

861-
// Remove from wrapper map
900+
// Remove from wrapper map and validation cache
862901
connectionWrappers.remove(pconn);
902+
validationCache.remove(pconn);
863903

864904
// Try to remove from recycled connections
865905
boolean foundInRecycled = false;
@@ -884,10 +924,10 @@ private void disposeConnection (PooledConnection pconn) {
884924
// This can happen with validation connections or connections that failed during creation
885925
}
886926

887-
// Only release a semaphore permit when disposing a connection (not recycling)
927+
// Only decrement totalConnections when disposing a connection (not recycling)
888928
// This ensures that the total connection count is properly managed
889929
if (!foundInRecycled) {
890-
semaphore.release();
930+
totalConnections.decrementAndGet();
891931
}
892932

893933
closeConnectionAndIgnoreException(pconn);
@@ -906,7 +946,7 @@ private void log (String msg) {
906946
String s = "ConnectionPool: "+msg;
907947
try {
908948
if (logWriter == null) {
909-
//System.err.println(s);
949+
System.err.println(s);
910950
}
911951
else {
912952
logWriter.println(s);
@@ -918,7 +958,7 @@ private void log (String msg) {
918958
private void assertInnerState() {
919959
int active = activeConnections.get();
920960
int recycled = recycledConnections.size();
921-
int total = active + recycled;
961+
int total = totalConnections.get();
922962

923963
if (active < 0) {
924964
throw new AssertionError("Active connections count is negative: " + active);
@@ -984,8 +1024,8 @@ PooledConnectionWrapper markUsed() {
9841024
public PoolStatistics getPoolStatistics() {
9851025
int active = activeConnections.get();
9861026
int recycled = recycledConnections.size();
987-
int total = active + recycled;
988-
int available = semaphore.availablePermits();
1027+
int total = totalConnections.get();
1028+
int available = maxConnections - total;
9891029

9901030
return new PoolStatistics(
9911031
active,

0 commit comments

Comments
 (0)