001package org.avaje.datasource.pool; 002 003import org.avaje.datasource.PoolStatistics; 004import org.avaje.datasource.PoolStatus; 005import org.avaje.datasource.pool.ConnectionPool.Status; 006import org.avaje.datasource.pool.PooledConnectionStatistics.LoadValues; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010import java.sql.SQLException; 011import java.util.concurrent.TimeUnit; 012import java.util.concurrent.locks.Condition; 013import java.util.concurrent.locks.ReentrantLock; 014 015public class PooledConnectionQueue { 016 017 private static final Logger logger = LoggerFactory.getLogger(PooledConnectionQueue.class); 018 019 private static final TimeUnit MILLIS_TIME_UNIT = TimeUnit.MILLISECONDS; 020 021 private final String name; 022 023 private final ConnectionPool pool; 024 025 /** 026 * A 'circular' buffer designed specifically for free connections. 027 */ 028 private final FreeConnectionBuffer freeList; 029 030 /** 031 * A 'slots' buffer designed specifically for busy connections. 032 * Fast add remove based on slot id. 033 */ 034 private final BusyConnectionBuffer busyList; 035 036 /** 037 * Load statistics collected off connections that have closed fully (left the pool). 038 */ 039 private final PooledConnectionStatistics collectedStats = new PooledConnectionStatistics(); 040 041 /** 042 * Currently accumulated load statistics. 043 */ 044 private LoadValues accumulatedValues = new LoadValues(); 045 046 /** 047 * Main lock guarding all access 048 */ 049 private final ReentrantLock lock; 050 051 /** 052 * Condition for threads waiting to take a connection 053 */ 054 private final Condition notEmpty; 055 056 private int connectionId; 057 058 private final long waitTimeoutMillis; 059 060 private final long leakTimeMinutes; 061 062 private final long maxAgeMillis; 063 064 private int warningSize; 065 066 private int maxSize; 067 068 private int minSize; 069 070 /** 071 * Number of threads in the wait queue. 072 */ 073 private int waitingThreads; 074 075 /** 076 * Number of times a thread had to wait. 077 */ 078 private int waitCount; 079 080 /** 081 * Number of times a connection was got from this queue. 082 */ 083 private int hitCount; 084 085 /** 086 * The high water mark for the queue size. 087 */ 088 private int highWaterMark; 089 090 /** 091 * Last time the pool was reset. Used to close busy connections as they are 092 * returned to the pool that where created prior to the lastResetTime. 093 */ 094 private long lastResetTime; 095 096 private boolean doingShutdown; 097 098 PooledConnectionQueue(ConnectionPool pool) { 099 100 this.pool = pool; 101 this.name = pool.getName(); 102 this.minSize = pool.getMinSize(); 103 this.maxSize = pool.getMaxSize(); 104 105 this.warningSize = pool.getWarningSize(); 106 this.waitTimeoutMillis = pool.getWaitTimeoutMillis(); 107 this.leakTimeMinutes = pool.getLeakTimeMinutes(); 108 this.maxAgeMillis = pool.getMaxAgeMillis(); 109 110 this.busyList = new BusyConnectionBuffer(maxSize, 20); 111 this.freeList = new FreeConnectionBuffer(); 112 113 this.lock = new ReentrantLock(false); 114 this.notEmpty = lock.newCondition(); 115 } 116 117 private PoolStatus createStatus() { 118 return new Status(minSize, maxSize, freeList.size(), busyList.size(), waitingThreads, highWaterMark, waitCount, hitCount); 119 } 120 121 public String toString() { 122 final ReentrantLock lock = this.lock; 123 lock.lock(); 124 try { 125 return createStatus().toString(); 126 } finally { 127 lock.unlock(); 128 } 129 } 130 131 /** 132 * Collect statistics of a connection that is fully closing 133 */ 134 void reportClosingConnection(PooledConnection pooledConnection) { 135 136 collectedStats.add(pooledConnection.getStatistics()); 137 } 138 139 PoolStatistics getStatistics(boolean reset) { 140 141 final ReentrantLock lock = this.lock; 142 lock.lock(); 143 try { 144 145 LoadValues aggregate = collectedStats.getValues(reset); 146 147 freeList.collectStatistics(aggregate, reset); 148 busyList.collectStatistics(aggregate, reset); 149 150 aggregate.plus(accumulatedValues); 151 152 this.accumulatedValues = (reset) ? new LoadValues() : aggregate; 153 154 return new DataSourcePoolStatistics(aggregate.getCollectionStart(), aggregate.getCount(), aggregate.getErrorCount(), aggregate.getHwmMicros(), aggregate.getTotalMicros()); 155 156 } finally { 157 lock.unlock(); 158 } 159 } 160 161 public PoolStatus getStatus(boolean reset) { 162 final ReentrantLock lock = this.lock; 163 lock.lock(); 164 try { 165 PoolStatus s = createStatus(); 166 if (reset) { 167 highWaterMark = busyList.size(); 168 hitCount = 0; 169 waitCount = 0; 170 } 171 return s; 172 } finally { 173 lock.unlock(); 174 } 175 } 176 177 void setMinSize(int minSize) { 178 final ReentrantLock lock = this.lock; 179 lock.lock(); 180 try { 181 if (minSize > this.maxSize) { 182 throw new IllegalArgumentException("minSize " + minSize + " > maxSize " + this.maxSize); 183 } 184 this.minSize = minSize; 185 } finally { 186 lock.unlock(); 187 } 188 } 189 190 void setMaxSize(int maxSize) { 191 final ReentrantLock lock = this.lock; 192 lock.lock(); 193 try { 194 if (maxSize < this.minSize) { 195 throw new IllegalArgumentException("maxSize " + maxSize + " < minSize " + this.minSize); 196 } 197 this.busyList.setCapacity(maxSize); 198 this.maxSize = maxSize; 199 } finally { 200 lock.unlock(); 201 } 202 } 203 204 void setWarningSize(int warningSize) { 205 final ReentrantLock lock = this.lock; 206 lock.lock(); 207 try { 208 if (warningSize > this.maxSize) { 209 throw new IllegalArgumentException("warningSize " + warningSize + " > maxSize " + this.maxSize); 210 } 211 this.warningSize = warningSize; 212 } finally { 213 lock.unlock(); 214 } 215 } 216 217 private int totalConnections() { 218 return freeList.size() + busyList.size(); 219 } 220 221 void ensureMinimumConnections() throws SQLException { 222 final ReentrantLock lock = this.lock; 223 lock.lock(); 224 try { 225 int add = minSize - totalConnections(); 226 if (add > 0) { 227 for (int i = 0; i < add; i++) { 228 PooledConnection c = pool.createConnectionForQueue(connectionId++); 229 freeList.add(c); 230 } 231 notEmpty.signal(); 232 } 233 234 } finally { 235 lock.unlock(); 236 } 237 } 238 239 /** 240 * Return a PooledConnection. 241 */ 242 void returnPooledConnection(PooledConnection c, boolean forceClose) { 243 244 final ReentrantLock lock = this.lock; 245 lock.lock(); 246 try { 247 if (!busyList.remove(c)) { 248 logger.error("Connection [{}] not found in BusyList? ", c); 249 } 250 if (forceClose || c.shouldTrimOnReturn(lastResetTime, maxAgeMillis)) { 251 c.closeConnectionFully(false); 252 253 } else { 254 freeList.add(c); 255 notEmpty.signal(); 256 } 257 } finally { 258 lock.unlock(); 259 } 260 } 261 262 private PooledConnection extractFromFreeList() { 263 PooledConnection c = freeList.remove(); 264 registerBusyConnection(c); 265 return c; 266 } 267 268 PooledConnection getPooledConnection() throws SQLException { 269 270 try { 271 PooledConnection pc = _getPooledConnection(); 272 pc.resetForUse(); 273 return pc; 274 275 } catch (InterruptedException e) { 276 // restore the interrupted status as we throw SQLException 277 Thread.currentThread().interrupt(); 278 throw new SQLException("Interrupted getting connection from pool", e); 279 } 280 } 281 282 /** 283 * Register the PooledConnection with the busyList. 284 */ 285 private int registerBusyConnection(PooledConnection c) { 286 int busySize = busyList.add(c); 287 if (busySize > highWaterMark) { 288 highWaterMark = busySize; 289 } 290 return busySize; 291 } 292 293 private PooledConnection _getPooledConnection() throws InterruptedException, SQLException { 294 final ReentrantLock lock = this.lock; 295 lock.lockInterruptibly(); 296 try { 297 if (doingShutdown) { 298 throw new SQLException("Trying to access the Connection Pool when it is shutting down"); 299 } 300 301 // this includes attempts that fail with InterruptedException 302 // or SQLException but that is ok as its only an indicator 303 hitCount++; 304 305 // are other threads already waiting? (they get priority) 306 if (waitingThreads == 0) { 307 308 if (!freeList.isEmpty()) { 309 // we have a free connection to return 310 return extractFromFreeList(); 311 } 312 313 if (busyList.size() < maxSize) { 314 // grow the connection pool 315 PooledConnection c = pool.createConnectionForQueue(connectionId++); 316 int busySize = registerBusyConnection(c); 317 318 if (logger.isDebugEnabled()) { 319 logger.debug("DataSourcePool [{}] grow; id[{}] busy[{}] max[{}]", name, c.getName(), busySize, maxSize); 320 } 321 checkForWarningSize(); 322 return c; 323 } 324 } 325 326 try { 327 // The pool is at maximum size. We are going to go into 328 // a wait loop until connections are returned into the pool. 329 waitCount++; 330 waitingThreads++; 331 return _getPooledConnectionWaitLoop(); 332 } finally { 333 waitingThreads--; 334 } 335 336 } finally { 337 lock.unlock(); 338 } 339 } 340 341 /** 342 * Got into a loop waiting for connections to be returned to the pool. 343 */ 344 private PooledConnection _getPooledConnectionWaitLoop() throws SQLException, InterruptedException { 345 346 long nanos = MILLIS_TIME_UNIT.toNanos(waitTimeoutMillis); 347 for (; ; ) { 348 349 if (nanos <= 0) { 350 String msg = "Unsuccessfully waited [" + waitTimeoutMillis + "] millis for a connection to be returned." 351 + " No connections are free. You need to Increase the max connections of [" + maxSize + "]" 352 + " or look for a connection pool leak using datasource.xxx.capturestacktrace=true"; 353 if (pool.isCaptureStackTrace()) { 354 dumpBusyConnectionInformation(); 355 } 356 357 throw new SQLException(msg); 358 } 359 360 try { 361 nanos = notEmpty.awaitNanos(nanos); 362 if (!freeList.isEmpty()) { 363 // successfully waited 364 return extractFromFreeList(); 365 } 366 } catch (InterruptedException ie) { 367 notEmpty.signal(); // propagate to non-interrupted thread 368 throw ie; 369 } 370 } 371 } 372 373 public void shutdown() { 374 final ReentrantLock lock = this.lock; 375 lock.lock(); 376 try { 377 doingShutdown = true; 378 PoolStatus status = createStatus(); 379 PoolStatistics statistics = pool.getStatistics(false); 380 logger.debug("DataSourcePool [{}] shutdown {} - Statistics {}", name, status, statistics); 381 382 closeFreeConnections(true); 383 384 if (!busyList.isEmpty()) { 385 logger.warn("Closing busy connections on shutdown size: " + busyList.size()); 386 dumpBusyConnectionInformation(); 387 closeBusyConnections(0); 388 } 389 } finally { 390 lock.unlock(); 391 } 392 } 393 394 /** 395 * Close all the connections in the pool and any current busy connections 396 * when they are returned. New connections will be then created on demand. 397 * <p> 398 * This is typically done when a database down event occurs. 399 * </p> 400 */ 401 public void reset(long leakTimeMinutes) { 402 final ReentrantLock lock = this.lock; 403 lock.lock(); 404 try { 405 PoolStatus status = createStatus(); 406 logger.info("Reseting DataSourcePool [{}] {}", name, status); 407 lastResetTime = System.currentTimeMillis(); 408 409 closeFreeConnections(false); 410 closeBusyConnections(leakTimeMinutes); 411 412 logger.info("Busy Connections:\n" + getBusyConnectionInformation()); 413 414 } finally { 415 lock.unlock(); 416 } 417 } 418 419 public void trim(long maxInactiveMillis, long maxAgeMillis) { 420 final ReentrantLock lock = this.lock; 421 lock.lock(); 422 try { 423 if (trimInactiveConnections(maxInactiveMillis, maxAgeMillis) > 0) { 424 try { 425 ensureMinimumConnections(); 426 } catch (SQLException e) { 427 logger.error("Error trying to ensure minimum connections", e); 428 } 429 } 430 } finally { 431 lock.unlock(); 432 } 433 } 434 435 /** 436 * Trim connections that have been not used for some time. 437 */ 438 private int trimInactiveConnections(long maxInactiveMillis, long maxAgeMillis) { 439 440 long usedSince = System.currentTimeMillis() - maxInactiveMillis; 441 long createdSince = (maxAgeMillis == 0) ? 0 : System.currentTimeMillis() - maxAgeMillis; 442 443 int trimedCount = freeList.trim(usedSince, createdSince); 444 if (trimedCount > 0) { 445 logger.debug("DataSourcePool [{}] trimmed [{}] inactive connections. New size[{}]", name, trimedCount, totalConnections()); 446 } 447 return trimedCount; 448 } 449 450 /** 451 * Close all the connections that are in the free list. 452 */ 453 private void closeFreeConnections(boolean logErrors) { 454 final ReentrantLock lock = this.lock; 455 lock.lock(); 456 try { 457 freeList.closeAll(logErrors); 458 } finally { 459 lock.unlock(); 460 } 461 } 462 463 /** 464 * Close any busy connections that have not been used for some time. 465 * <p> 466 * These connections are considered to have leaked from the connection pool. 467 * </p> 468 * <p> 469 * Connection leaks occur when code doesn't ensure that connections are 470 * closed() after they have been finished with. There should be an 471 * appropriate try catch finally block to ensure connections are always 472 * closed and put back into the pool. 473 * </p> 474 */ 475 void closeBusyConnections(long leakTimeMinutes) { 476 477 final ReentrantLock lock = this.lock; 478 lock.lock(); 479 try { 480 busyList.closeBusyConnections(leakTimeMinutes); 481 } finally { 482 lock.unlock(); 483 } 484 } 485 486 /** 487 * As the pool grows it gets closer to the maxConnections limit. We can send 488 * an Alert (or warning) as we get close to this limit and hence an 489 * Administrator could increase the pool size if desired. 490 * <p> 491 * This is called whenever the pool grows in size (towards the max limit). 492 * </p> 493 */ 494 private void checkForWarningSize() { 495 496 // the the total number of connections that we can add 497 // to the pool before it hits the maximum 498 int availableGrowth = (maxSize - totalConnections()); 499 500 if (availableGrowth < warningSize) { 501 502 closeBusyConnections(leakTimeMinutes); 503 504 String msg = "DataSourcePool [" + name + "] is [" + availableGrowth + "] connections from its maximum size."; 505 pool.notifyWarning(msg); 506 } 507 } 508 509 String getBusyConnectionInformation() { 510 return getBusyConnectionInformation(false); 511 } 512 513 void dumpBusyConnectionInformation() { 514 getBusyConnectionInformation(true); 515 } 516 517 /** 518 * Returns information describing connections that are currently being used. 519 */ 520 private String getBusyConnectionInformation(boolean toLogger) { 521 522 final ReentrantLock lock = this.lock; 523 lock.lock(); 524 try { 525 526 return busyList.getBusyConnectionInformation(toLogger); 527 528 } finally { 529 lock.unlock(); 530 } 531 } 532 533} 534