View Javadoc
1 package org.apache.torque.pool; 2 3 /* ==================================================================== 4 * The Apache Software License, Version 1.1 5 * 6 * Copyright (c) 2001-2003 The Apache Software Foundation. All rights 7 * reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in 18 * the documentation and/or other materials provided with the 19 * distribution. 20 * 21 * 3. The end-user documentation included with the redistribution, 22 * if any, must include the following acknowledgment: 23 * "This product includes software developed by the 24 * Apache Software Foundation (http://www.apache.org/)." 25 * Alternately, this acknowledgment may appear in the software itself, 26 * if and wherever such third-party acknowledgments normally appear. 27 * 28 * 4. The names "Apache" and "Apache Software Foundation" and 29 * "Apache Turbine" must not be used to endorse or promote products 30 * derived from this software without prior written permission. For 31 * written permission, please contact apache@apache.org. 32 * 33 * 5. Products derived from this software may not be called "Apache", 34 * "Apache Turbine", nor may "Apache" appear in their name, without 35 * prior written permission of the Apache Software Foundation. 36 * 37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 40 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 41 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 44 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 46 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 47 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 48 * SUCH DAMAGE. 49 * ==================================================================== 50 * 51 * This software consists of voluntary contributions made by many 52 * individuals on behalf of the Apache Software Foundation. For more 53 * information on the Apache Software Foundation, please see 54 * <http://www.apache.org/>. 55 */ 56 57 import java.sql.SQLException; 58 import java.util.HashMap; 59 import java.util.Map; 60 import java.util.Stack; 61 62 import javax.sql.ConnectionEvent; 63 import javax.sql.ConnectionEventListener; 64 import javax.sql.ConnectionPoolDataSource; 65 import javax.sql.PooledConnection; 66 67 import org.apache.commons.logging.Log; 68 import org.apache.commons.logging.LogFactory; 69 70 /*** 71 * This class implements a simple connection pooling scheme. 72 * 73 * @author <a href="mailto:csterg@aias.gr">Costas Stergiou</a> 74 * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a> 75 * @author <a href="mailto:bmclaugh@algx.net">Brett McLaughlin</a> 76 * @author <a href="mailto:greg@shwoop.com">Greg Ritter</a> 77 * @author <a href="mailto:dlr@collab.net">Daniel L. Rall</a> 78 * @author <a href="mailto:paul@evolventtech.com">Paul O'Leary</a> 79 * @author <a href="mailto:magnus@handtolvur.is">Magnús Þór Torfason</a> 80 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a> 81 * @author <a href="mailto:jmcnally@collab.net">John McNally</a> 82 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> 83 * @version $Id: ConnectionPool.java,v 1.27 2003/08/18 21:48:11 mpoeschl Exp $ 84 * @deprecated as of version 3.1 85 */ 86 class ConnectionPool implements ConnectionEventListener 87 { 88 89 /*** Default maximum Number of connections from this pool: One */ 90 public static final int DEFAULT_MAX_CONNECTIONS = 1; 91 92 /*** Default Expiry Time for a pool: 1 hour */ 93 public static final int DEFAULT_EXPIRY_TIME = 60 * 60 * 1000; 94 95 /*** Default Connect Wait Timeout: 10 Seconds */ 96 public static final int DEFAULT_CONNECTION_WAIT_TIMEOUT = 10 * 1000; 97 98 /*** Pool containing database connections. */ 99 private Stack pool; 100 101 /*** The url for this pool. */ 102 private String url; 103 104 /*** The user name for this pool. */ 105 private String username; 106 107 /*** The password for this pool. */ 108 private String password; 109 110 /*** The current number of database connections that have been created. */ 111 private int totalConnections; 112 113 /*** The maximum number of database connections that can be created. */ 114 private int maxConnections = DEFAULT_MAX_CONNECTIONS; 115 116 /*** The amount of time in milliseconds that a connection will be pooled. */ 117 private long expiryTime = DEFAULT_EXPIRY_TIME; 118 119 /*** 120 * Counter that keeps track of the number of threads that are in 121 * the wait state, waiting to aquire a connection. 122 */ 123 private int waitCount = 0; 124 125 /*** The logging logger. */ 126 private static Log log = LogFactory.getLog(ConnectionPool.class); 127 128 /*** Interval (in seconds) that the monitor thread reports the pool state */ 129 private int logInterval = 0; 130 131 /*** Monitor thread reporting the pool state */ 132 private Monitor monitor; 133 134 /*** 135 * Amount of time a thread asking the pool for a cached connection will 136 * wait before timing out and throwing an error. 137 */ 138 private long connectionWaitTimeout = DEFAULT_CONNECTION_WAIT_TIMEOUT; 139 140 /*** The ConnectionPoolDataSource */ 141 private ConnectionPoolDataSource cpds; 142 143 /*** 144 * Keep track of when connections were created. Keyed by a 145 * PooledConnection and value is a java.util.Date 146 */ 147 private Map timeStamps; 148 149 /*** 150 * Creates a <code>ConnectionPool</code> with the default 151 * attributes. 152 * 153 * @param cpds The datasource 154 * @param username The user name for this pool. 155 * @param password The password for this pool. 156 * @param maxConnections max number of connections 157 * @param expiryTime connection expiry time 158 * @param connectionWaitTimeout timeout 159 * @param logInterval log interval 160 */ 161 ConnectionPool(ConnectionPoolDataSource cpds, String username, 162 String password, int maxConnections, int expiryTime, 163 int connectionWaitTimeout, int logInterval) 164 { 165 totalConnections = 0; 166 pool = new Stack(); 167 timeStamps = new HashMap(); 168 169 this.cpds = cpds; 170 this.username = username; 171 this.password = password; 172 173 this.maxConnections = 174 (maxConnections > 0) ? maxConnections : DEFAULT_MAX_CONNECTIONS; 175 176 this.expiryTime = 177 ((expiryTime > 0) ? expiryTime * 1000 : DEFAULT_EXPIRY_TIME); 178 179 this.connectionWaitTimeout = 180 ((connectionWaitTimeout > 0) 181 ? connectionWaitTimeout * 1000 182 : DEFAULT_CONNECTION_WAIT_TIMEOUT); 183 184 this.logInterval = 1000 * logInterval; 185 186 if (logInterval > 0) 187 { 188 log.debug("Starting Pool Monitor Thread with Log Interval " 189 + logInterval + " Milliseconds"); 190 191 // Create monitor thread 192 monitor = new Monitor(); 193 194 // Indicate that this is a system thread. JVM will quit only 195 // when there are no more active user threads. Settings threads 196 // spawned internally by Torque as daemons allows commandline 197 // applications using Torque to terminate in an orderly manner. 198 monitor.setDaemon(true); 199 monitor.start(); 200 } 201 } 202 203 /*** 204 * Returns a connection that maintains a link to the pool it came from. 205 * 206 * @param username The name of the database user. 207 * @param password The password of the database user. 208 * @return A database connection. 209 * @exception SQLException if there is aproblem with the db connection 210 */ 211 final synchronized PooledConnection getConnection(String username, 212 String password) 213 throws SQLException 214 { 215 if (username != this.username || password != this.password) 216 { 217 throw new SQLException("Username and password are invalid."); 218 } 219 220 PooledConnection pcon = null; 221 if (pool.empty() && totalConnections < maxConnections) 222 { 223 pcon = getNewConnection(); 224 } 225 else 226 { 227 try 228 { 229 pcon = getInternalPooledConnection(); 230 } 231 catch (Exception e) 232 { 233 throw new SQLException(e.getMessage()); 234 } 235 } 236 return pcon; 237 } 238 239 /*** 240 * Returns a fresh connection to the database. The database type 241 * is specified by <code>driver</code>, and its connection 242 * information by <code>url</code>, <code>username</code>, and 243 * <code>password</code>. 244 * 245 * @return A database connection. 246 * @exception SQLException if there is aproblem with the db connection 247 */ 248 private PooledConnection getNewConnection() 249 throws SQLException 250 { 251 PooledConnection pc = null; 252 if (username == null) 253 { 254 pc = cpds.getPooledConnection(); 255 } 256 else 257 { 258 pc = cpds.getPooledConnection(username, password); 259 } 260 pc.addConnectionEventListener(this); 261 262 // Age some connections so that there will not be a run on the db, 263 // when connections start expiring 264 // 265 // I did some experimentation here with integers but as this 266 // is not a really time critical path, we keep the floating 267 // point calculation. 268 long currentTime = System.currentTimeMillis(); 269 270 double ratio = new Long(maxConnections - totalConnections).doubleValue() 271 / maxConnections; 272 273 long ratioTime = new Double(currentTime - (expiryTime * ratio) / 4) 274 .longValue(); 275 276 ratioTime = (expiryTime < 0) ? currentTime : ratioTime; 277 278 timeStamps.put(pc, new Long(ratioTime)); 279 totalConnections++; 280 return pc; 281 } 282 283 /*** 284 * Gets a pooled database connection. 285 * 286 * @return A database connection. 287 * @exception ConnectionWaitTimeoutException Wait time exceeded. 288 * @exception Exception No pooled connections. 289 */ 290 private synchronized PooledConnection getInternalPooledConnection() 291 throws ConnectionWaitTimeoutException, Exception 292 { 293 // We test waitCount > 0 to make sure no other threads are 294 // waiting for a connection. 295 if (waitCount > 0 || pool.empty()) 296 { 297 // The connection pool is empty and we cannot allocate any new 298 // connections. Wait the prescribed amount of time and see if 299 // a connection is returned. 300 try 301 { 302 waitCount++; 303 wait(connectionWaitTimeout); 304 } 305 catch (InterruptedException ignored) 306 { 307 // Don't care how we come out of the wait state. 308 } 309 finally 310 { 311 waitCount--; 312 } 313 314 // Check for a returned connection. 315 if (pool.empty()) 316 { 317 // If the pool is still empty here, we were not awoken by 318 // someone returning a connection. 319 throw new ConnectionWaitTimeoutException(url); 320 } 321 } 322 return popConnection(); 323 } 324 /*** 325 * Helper function that attempts to pop a connection off the pool's stack, 326 * handling the case where the popped connection has become invalid by 327 * creating a new connection. 328 * 329 * @return An existing or new database connection. 330 * @throws Exception if the pool is empty 331 */ 332 private PooledConnection popConnection() 333 throws Exception 334 { 335 while (!pool.empty()) 336 { 337 PooledConnection con = (PooledConnection) pool.pop(); 338 339 // It's really not safe to assume this connection is 340 // valid even though it's checked before being pooled. 341 if (isValid(con)) 342 { 343 return con; 344 } 345 else 346 { 347 // Close invalid connection. 348 con.close(); 349 totalConnections--; 350 351 // If the pool is now empty, create a new connection. We're 352 // guaranteed not to exceed the connection limit since we 353 // just killed off one or more invalid connections, and no 354 // one else can be accessing this cache right now. 355 if (pool.empty()) 356 { 357 return getNewConnection(); 358 } 359 } 360 } 361 362 // The connection pool was empty to start with--don't call this 363 // routine if there's no connection to pop! 364 // TODO: Propose general Turbine assertion failure exception? -PGO 365 throw new Exception("Assertion failure: Attempted to pop " 366 + "connection from empty pool!"); 367 } 368 369 /*** 370 * Helper method which determines whether a connection has expired. 371 * 372 * @param pc The connection to test. 373 * @return True if the connection is expired, false otherwise. 374 */ 375 private boolean isExpired(PooledConnection pc) 376 { 377 // Test the age of the connection (defined as current time 378 // minus connection birthday) against the connection pool 379 // expiration time. 380 long birth = ((Long) timeStamps.get(pc)).longValue(); 381 long age = System.currentTimeMillis() - birth; 382 383 boolean dead = (expiryTime > 0) 384 ? age > expiryTime 385 : age > DEFAULT_EXPIRY_TIME; 386 387 return dead; // He is dead, Jim. 388 } 389 390 /*** 391 * Determines if a connection is still valid. 392 * 393 * @param connection The connection to test. 394 * @return True if the connection is valid, false otherwise. 395 */ 396 private boolean isValid(PooledConnection connection) 397 { 398 // all this code is commented out because 399 // connection.getConnection() is called when the connection 400 // is returned to the pool and it will open a new logical Connection 401 // which does not get closed, then when it is called again 402 // when a connection is requested it likely fails because a 403 // new Connection has been requested and the old one is still 404 // open. need to either do it right or skip it. null check 405 // was not working either. 406 407 //try 408 //{ 409 // This will throw an exception if: 410 // The connection is null 411 // The connection is closed 412 // Therefore, it would be false. 413 //connection.getConnection(); 414 // Check for expiration 415 return !isExpired(connection); 416 /* 417 } 418 catch (SQLException e) 419 { 420 return false; 421 } 422 */ 423 } 424 425 426 /*** 427 * Close any open connections when this object is garbage collected. 428 * 429 * @exception Throwable Anything might happen... 430 */ 431 protected void finalize() 432 throws Throwable 433 { 434 shutdown(); 435 } 436 437 /*** 438 * Close all connections to the database, 439 */ 440 void shutdown() 441 { 442 if (pool != null) 443 { 444 while (!pool.isEmpty()) 445 { 446 try 447 { 448 ((PooledConnection) pool.pop()).close(); 449 } 450 catch (SQLException ignore) 451 { 452 } 453 finally 454 { 455 totalConnections--; 456 } 457 } 458 } 459 monitor.shutdown(); 460 } 461 462 /*** 463 * Returns the Total connections in the pool 464 * 465 * @return total connections in the pool 466 */ 467 int getTotalCount() 468 { 469 return totalConnections; 470 } 471 472 /*** 473 * Returns the available connections in the pool 474 * 475 * @return number of available connections in the pool 476 */ 477 int getNbrAvailable() 478 { 479 return pool.size(); 480 } 481 482 /*** 483 * Returns the checked out connections in the pool 484 * 485 * @return number of checked out connections in the pool 486 */ 487 int getNbrCheckedOut() 488 { 489 return (totalConnections - pool.size()); 490 } 491 492 /*** 493 * Decreases the count of connections in the pool 494 * and also calls <code>notify()</code>. 495 */ 496 void decrementConnections() 497 { 498 totalConnections--; 499 notify(); 500 } 501 502 /*** 503 * Get the name of the pool 504 * 505 * @return the name of the pool 506 */ 507 String getPoolName() 508 { 509 return toString(); 510 } 511 512 // *********************************************************************** 513 // java.sql.ConnectionEventListener implementation 514 // *********************************************************************** 515 516 /*** 517 * This will be called if the Connection returned by the getConnection 518 * method came from a PooledConnection, and the user calls the close() 519 * method of this connection object. What we need to do here is to 520 * release this PooledConnection from our pool... 521 * 522 * @param event the connection event 523 */ 524 public void connectionClosed(ConnectionEvent event) 525 { 526 releaseConnection((PooledConnection) event.getSource()); 527 } 528 529 /*** 530 * If a fatal error occurs, close the underlying physical connection so as 531 * not to be returned in the future 532 * 533 * @param event the connection event 534 */ 535 public void connectionErrorOccurred(ConnectionEvent event) 536 { 537 try 538 { 539 System.err.println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR"); 540 //remove this from the listener list because we are no more 541 //interested in errors since we are about to close this connection 542 ((PooledConnection) event.getSource()) 543 .removeConnectionEventListener(this); 544 } 545 catch (Exception ignore) 546 { 547 //just ignore 548 } 549 550 try 551 { 552 closePooledConnection((PooledConnection) event.getSource()); 553 } 554 catch (Exception ignore) 555 { 556 //just ignore 557 } 558 } 559 560 /*** 561 * This method returns a connection to the pool, and <b>must</b> 562 * be called by the requestor when finished with the connection. 563 * 564 * @param pcon The database connection to release. 565 */ 566 private synchronized void releaseConnection(PooledConnection pcon) 567 { 568 if (isValid(pcon)) 569 { 570 pool.push(pcon); 571 notify(); 572 } 573 else 574 { 575 closePooledConnection(pcon); 576 } 577 } 578 579 /*** 580 * 581 * @param pcon The database connection to close. 582 */ 583 private void closePooledConnection(PooledConnection pcon) 584 { 585 try 586 { 587 pcon.close(); 588 timeStamps.remove(pcon); 589 } 590 catch (Exception e) 591 { 592 log.error("Error occurred trying to close a PooledConnection.", e); 593 } 594 finally 595 { 596 decrementConnections(); 597 } 598 } 599 600 /////////////////////////////////////////////////////////////////////////// 601 602 /*** 603 * This inner class monitors the <code>PoolBrokerService</code>. 604 * 605 * This class is capable of logging the number of connections available in 606 * the pool periodically. This can prove useful if you application 607 * frozes after certain amount of time/requests and you suspect 608 * that you have connection leakage problem. 609 * 610 * Set the <code>logInterval</code> property of your pool definition 611 * to the number of seconds you want to elapse between loging the number of 612 * connections. 613 */ 614 protected class Monitor extends Thread 615 { 616 /*** true if the monot is running */ 617 private boolean isRun = true; 618 619 /*** 620 * run method for the monitor thread 621 */ 622 public void run() 623 { 624 StringBuffer buf = new StringBuffer(); 625 while (logInterval > 0 && isRun) 626 { 627 buf.setLength(0); 628 629 buf.append(getPoolName()); 630 buf.append(" avail: ").append(getNbrAvailable()); 631 buf.append(" in use: ").append(getNbrCheckedOut()); 632 buf.append(" total: ").append(getTotalCount()); 633 log.info(buf.toString()); 634 635 // Wait for a bit. 636 try 637 { 638 Thread.sleep(logInterval); 639 } 640 catch (InterruptedException ignored) 641 { 642 } 643 } 644 } 645 646 /*** 647 * Shut down the monitor 648 */ 649 public void shutdown() 650 { 651 isRun = false; 652 } 653 } 654 }

This page was automatically generated by Maven