View Javadoc

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