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 }