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