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