KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > continuent > sequoia > controller > connection > VariablePoolConnectionManager


1 /**
2  * Sequoia: Database clustering technology.
3  * Copyright (C) 2002-2004 French National Institute For Research In Computer
4  * Science And Control (INRIA).
5  * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
6  * Contact: sequoia@continuent.org
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  *
20  * Initial developer(s): Emmanuel Cecchet.
21  * Contributor(s): Mathieu Peltier.
22  */

23
24 package org.continuent.sequoia.controller.connection;
25
26 import java.sql.Connection JavaDoc;
27 import java.sql.SQLException JavaDoc;
28 import java.sql.Statement JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.LinkedList JavaDoc;
31 import java.util.NoSuchElementException JavaDoc;
32
33 import org.continuent.sequoia.common.exceptions.UnreachableBackendException;
34 import org.continuent.sequoia.common.xml.DatabasesXmlTags;
35
36 /**
37  * This connection manager provides connection pooling with a dynamically
38  * adjustable pool size.
39  * <p>
40  * If the maximum number of active connections is not reached, the
41  * {@link #getConnection()}method creates a connection. Else, the execution is
42  * blocked until a connection is freed or the timeout expires. blocked until a
43  * connection is freed or the timeout expires.
44  * <p>
45  * Idle connections in the pool are removed after the timeout idleTimeout if the
46  * minimum pool size has not been reached.
47  *
48  * @author <a HREF="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
49  * @author <a HREF="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
50  * @author <a HREF="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
51  * @version 1.0
52  */

53 public class VariablePoolConnectionManager
54     extends AbstractPoolConnectionManager
55 {
56   /** Initial pool size to be initialized at startup. */
57   private int initPoolSize;
58
59   /** Minimum pool size. */
60   private int minPoolSize;
61
62   /** Maximum pool size. */
63   private int maxPoolSize;
64
65   /**
66    * Time a connection can stay idle before begin released (removed from the
67    * pool) in milliseconds (0 means forever)
68    */

69   private int idleTimeout;
70
71   /** Maximum time to wait for a connection in milliseconds. */
72   private int waitTimeout;
73
74   /** Stores the time on which connections have been released. */
75   private LinkedList JavaDoc releaseTimes;
76
77   /** Allow to remove idle connections in the pool. */
78   private RemoveIdleConnectionsThread removeIdleConnectionsThread;
79
80   private Connection JavaDoc ping;
81
82   /**
83    * Creates a new <code>VariablePoolConnectionManager</code> instance with
84    * the default minPoolSize(initial pool size to be initialized at startup).
85    *
86    * @param backendUrl URL of the <code>DatabaseBackend</code> owning this
87    * connection manager
88    * @param backendName name of the <code>DatabaseBackend</code> owning this
89    * connection manager
90    * @param rLogin backend connection login to be used by this connection
91    * manager
92    * @param rPassword backend connection password to be used by this connection
93    * manager
94    * @param driverPath path for driver
95    * @param driverClassName class name for driver
96    * @param minPoolSize minimum pool size.
97    * @param maxPoolSize maximum pool size. 0 means no limit.
98    * @param idleTimeout time a connection can stay idle before begin released
99    * (removed from the pool) in seconds. 0 means no timeout: once
100    * allocated, connections are never released.
101    * @param waitTimeout maximum time to wait for a connection in seconds. 0
102    * means no timeout: waits until one connection is freed.
103    */

104   public VariablePoolConnectionManager(String JavaDoc backendUrl, String JavaDoc backendName,
105       String JavaDoc rLogin, String JavaDoc rPassword, String JavaDoc driverPath,
106       String JavaDoc driverClassName, int minPoolSize, int maxPoolSize,
107       int idleTimeout, int waitTimeout)
108   {
109     this(backendUrl, backendName, rLogin, rPassword, driverPath,
110         driverClassName, minPoolSize, minPoolSize, maxPoolSize, idleTimeout,
111         waitTimeout);
112   }
113
114   /**
115    * @see java.lang.Object#clone()
116    */

117   protected Object JavaDoc clone() throws CloneNotSupportedException JavaDoc
118   {
119     return new VariablePoolConnectionManager(backendUrl, backendName, rLogin,
120         rPassword, driverPath, driverClassName, minPoolSize, maxPoolSize,
121         idleTimeout, waitTimeout);
122   }
123
124   /**
125    * @see org.continuent.sequoia.controller.connection.AbstractConnectionManager#clone(String,
126    * String)
127    */

128   public AbstractConnectionManager clone(String JavaDoc rLogin, String JavaDoc rPassword)
129   {
130     return new VariablePoolConnectionManager(backendUrl, backendName, rLogin,
131         rPassword, driverPath, driverClassName, initPoolSize, minPoolSize,
132         maxPoolSize, idleTimeout, waitTimeout);
133   }
134
135   /**
136    * Creates a new <code>VariablePoolConnectionManager</code> instance.
137    *
138    * @param backendUrl URL of the <code>DatabaseBackend</code> owning this
139    * connection manager
140    * @param backendName name of the <code>DatabaseBackend</code> owning this
141    * connection manager
142    * @param rLogin backend connection login to be used by this connection
143    * manager
144    * @param rPassword backend connection password to be used by this connection
145    * manager
146    * @param driverPath path for driver
147    * @param driverClassName class name for driver
148    * @param initPoolSize initial pool size to be intialized at startup
149    * @param minPoolSize minimum pool size.
150    * @param maxPoolSize maximum pool size. 0 means no limit.
151    * @param idleTimeout time a connection can stay idle before begin released
152    * (removed from the pool) in seconds. 0 means no timeout: once
153    * allocated, connections are never released.
154    * @param waitTimeout maximum time to wait for a connection in seconds. 0
155    * means no timeout: waits until one connection is freed.
156    */

157   public VariablePoolConnectionManager(String JavaDoc backendUrl, String JavaDoc backendName,
158       String JavaDoc rLogin, String JavaDoc rPassword, String JavaDoc driverPath,
159       String JavaDoc driverClassName, int initPoolSize, int minPoolSize,
160       int maxPoolSize, int idleTimeout, int waitTimeout)
161   {
162     super(backendUrl, backendName, rLogin, rPassword, driverPath,
163         driverClassName, maxPoolSize == 0 ? (initPoolSize > minPoolSize
164             ? initPoolSize
165             : minPoolSize) : maxPoolSize);
166     this.initPoolSize = initPoolSize;
167     this.minPoolSize = minPoolSize;
168     this.maxPoolSize = maxPoolSize;
169     this.idleTimeout = idleTimeout * 1000;
170     this.waitTimeout = waitTimeout * 1000;
171   }
172
173   /**
174    * Gets the max pool size.
175    *
176    * @return a <code>int</code> value.
177    */

178   public int getMaxPoolSize()
179   {
180     return maxPoolSize;
181   }
182
183   /**
184    * Gets the min pool size.
185    *
186    * @return a <code>int</code> value.
187    */

188   public int getMinPoolSize()
189   {
190     return minPoolSize;
191   }
192
193   /**
194    * Gets the idle timeout.
195    *
196    * @return a <code>int</code> value.
197    */

198   public int getIdleTimeout()
199   {
200     return idleTimeout;
201   }
202
203   /**
204    * Gets the wait timeout.
205    *
206    * @return a <code>int</code> value.
207    */

208   public int getWaitTimeout()
209   {
210     return waitTimeout;
211   }
212
213   /**
214    * @see org.continuent.sequoia.controller.connection.AbstractConnectionManager#doConnectionInitialization()
215    */

216   protected void doConnectionInitialization() throws SQLException JavaDoc
217   {
218     poolSize = maxPoolSize == 0 ? (initPoolSize > minPoolSize
219         ? initPoolSize
220         : minPoolSize) : maxPoolSize;
221     this.ping = getConnectionFromDriver();
222     synchronized (this)
223     {
224       super.doConnectionInitialization(initPoolSize);
225
226       if (idleTimeout != 0)
227       {
228         // Create the thread which manages the free connections
229
removeIdleConnectionsThread = new RemoveIdleConnectionsThread(
230             this.backendName, this);
231
232         // Intialize release time for the initial connections if an idleTimeout
233
// is set
234
releaseTimes = new LinkedList JavaDoc();
235         Iterator JavaDoc it = freeConnections.iterator();
236         Long JavaDoc currentTime = new Long JavaDoc(System.currentTimeMillis());
237         while (it.hasNext())
238         {
239           it.next();
240           releaseTimes.addLast(currentTime);
241         }
242       }
243     }
244
245     // Start the thread outside synchronized(this)
246
if (removeIdleConnectionsThread != null)
247     {
248       removeIdleConnectionsThread.start();
249
250       synchronized (removeIdleConnectionsThread)
251       {
252         if (releaseTimes.size() > 0)
253         {
254           removeIdleConnectionsThread.notify();
255         }
256       }
257     }
258     if (idlePersistentConnectionPingInterval > 0)
259     {
260       persistentConnectionPingerThread = new IdlePersistentConnectionsPingerThread(
261           backendName, this);
262       persistentConnectionPingerThread.start();
263       idlePersistentConnectionPingRunning = true;
264     }
265   }
266
267   /**
268    * @see org.continuent.sequoia.controller.connection.AbstractConnectionManager#doConnectionFinalization()
269    */

270   protected void doConnectionFinalization() throws SQLException JavaDoc
271   {
272     try
273     {
274       if (ping != null)
275         ping.close();
276       ping = null;
277     }
278     catch (SQLException JavaDoc e)
279     {
280       // ignore the errors we are shutting down
281
}
282     synchronized (this)
283     {
284       super.doConnectionFinalization();
285     }
286
287     if (removeIdleConnectionsThread != null)
288     {
289       synchronized (removeIdleConnectionsThread)
290       {
291         removeIdleConnectionsThread.isKilled = true;
292         removeIdleConnectionsThread.notify();
293       }
294       try
295       {
296         removeIdleConnectionsThread.join();
297       }
298       catch (InterruptedException JavaDoc e)
299       {
300       }
301     }
302   }
303
304   /**
305    * Gets a connection from the pool.
306    * <p>
307    * If the current number of active connections is lower than the maximum pool
308    * size, a new connection is created. If the creation fails, this method waits
309    * for a connection to be freed.
310    * <p>
311    * If the maximum number of active connections is reached, this methods blocks
312    * until a connection is freed or the timeout expires.
313    *
314    * @return a connection from the pool or <code>null</code> if the timeout
315    * has expired.
316    * @throws UnreachableBackendException if the backend must be disabled
317    * @see org.continuent.sequoia.controller.connection.AbstractConnectionManager#getConnection()
318    */

319   public synchronized PooledConnection getConnection()
320       throws UnreachableBackendException
321   {
322     if (!initialized)
323     {
324       logger
325           .error("Requesting a connection from a non-initialized connection manager");
326       return null;
327     }
328     if (isShutdown)
329     {
330       return null;
331     }
332     long lTimeout = waitTimeout;
333     if (freeConnections.isEmpty())
334     {
335       if ((maxPoolSize == 0) || (activeConnections.size() < maxPoolSize))
336       {
337         Connection JavaDoc c = getConnectionFromDriver();
338         if (c == null)
339         {
340           if (activeConnections.size() == 0)
341           { // No connection active and backend unreachable, the backend
342
// is probably dead
343
logger.error("Backend " + backendName + " is no more accessible.");
344             throw new UnreachableBackendException();
345           }
346           /*
347            * Ping the server with an open connection in order to determine
348            * whether the server has failed or is has run out of connections. If
349            * the ping fails, the server is down.
350            */

351           try
352           {
353             Statement JavaDoc pingStatement = ping.createStatement();
354             pingStatement.execute(connectionTestStatement);
355             pingStatement.close();
356           }
357           catch (SQLException JavaDoc e)
358           {
359             isShutdown = true;
360             logger.error("Backend " + backendName + " is no more accessible.");
361             throw new UnreachableBackendException();
362           }
363           /*
364            * There are currently no connections available from the server, just
365            * wait for a connection to be freed
366            */

367
368           if (logger.isWarnEnabled())
369             logger.warn("Failed to create new connection on backend '"
370                 + backendName + "', waiting for a connection to be freed.");
371         }
372         else
373         {
374           freeConnections.addLast(new PooledConnection(c));
375           if (idleTimeout != 0)
376           {
377             releaseTimes.add(new Long JavaDoc(System.currentTimeMillis()));
378           }
379           poolSize++;
380         }
381       }
382
383       /*
384        * We have to do a while loop() because there is a potential race here.
385        * When freeConnections is notified in releaseConnection, a new thread can
386        * take the lock on freeConnections before we wake up/reacquire the lock
387        * on freeConnections. Therefore, we could wake up and have no connection
388        * to take! We ensure that everything is correct with a while statement
389        * and recomputing the timeout between 2 wakeup.
390        */

391       while (freeConnections.isEmpty())
392       {
393         if (activeConnections.size() == 0 || isShutdown)
394         { // No connection active and backend unreachable, the backend
395
// is probably dead
396
logger.error("Backend " + backendName + " is no more accessible.");
397           throw new UnreachableBackendException();
398         }
399         // Wait
400
try
401         {
402           if (lTimeout > 0)
403           {
404             long start = System.currentTimeMillis();
405             // Convert seconds to milliseconds for wait call
406
this.wait(waitTimeout);
407             long end = System.currentTimeMillis();
408             lTimeout -= end - start;
409             if (lTimeout <= 0)
410             {
411               if (logger.isWarnEnabled())
412                 logger.warn("Timeout expired for connection on backend '"
413                     + backendName
414                     + "', consider increasing pool size (current size is "
415                     + poolSize + ") or timeout (current timeout is "
416                     + (waitTimeout / 1000) + " seconds)");
417               return null;
418             }
419           }
420           else
421           {
422             this.wait();
423           }
424         }
425         catch (InterruptedException JavaDoc e)
426         {
427           logger
428               .error("Wait on freeConnections interrupted in VariablePoolConnectionManager");
429           return null;
430         }
431       }
432     }
433
434     // Get the connection
435
try
436     {
437       PooledConnection c = (PooledConnection) freeConnections.removeLast();
438       if (idleTimeout != 0)
439         releaseTimes.removeLast();
440       activeConnections.add(c);
441       return c;
442     }
443     catch (NoSuchElementException JavaDoc e)
444     {
445       if (logger.isErrorEnabled())
446         logger.error("Failed to get a connection on backend '" + backendName
447             + "' but an idle connection was expected");
448       return null;
449     }
450   }
451
452   /**
453    * @see org.continuent.sequoia.controller.connection.AbstractConnectionManager#releaseConnection(org.continuent.sequoia.controller.connection.PooledConnection)
454    */

455   public void releaseConnection(PooledConnection c)
456   {
457     boolean notifyThread = false;
458     synchronized (this)
459     {
460       if (!initialized)
461       {
462         closeConnection(c);
463         return; // We probably have been disabled
464
}
465
466       if (activeConnections.remove(c))
467       {
468         if (idleTimeout != 0)
469         {
470           notifyThread = freeConnections.isEmpty()
471               || (freeConnections.size() == minPoolSize);
472           releaseTimes.addLast(new Long JavaDoc(System.currentTimeMillis()));
473         }
474         freeConnections.addLast(c);
475         this.notify();
476       }
477       else
478         logger.error("Failed to release connection " + c
479             + " (not found in active pool)");
480     }
481
482     if (notifyThread)
483       synchronized (removeIdleConnectionsThread)
484       {
485         removeIdleConnectionsThread.notify();
486       }
487   }
488
489   /**
490    * @see org.continuent.sequoia.controller.connection.AbstractConnectionManager#deleteConnection(org.continuent.sequoia.controller.connection.PooledConnection)
491    */

492   public synchronized void deleteConnection(PooledConnection c)
493   {
494     closeConnection(c);
495
496     if (!initialized)
497       return; // We probably have been disabled
498

499     if (activeConnections.remove(c))
500     {
501       poolSize--;
502       if (poolSize < minPoolSize)
503       {
504         Connection JavaDoc newConnection = getConnectionFromDriver();
505         if (newConnection == null)
506         {
507           if (logger.isDebugEnabled())
508             logger.error("Bad connection " + c
509                 + " has been removed but cannot be replaced.");
510         }
511         else
512         {
513           poolSize++;
514           freeConnections.addLast(new PooledConnection(newConnection));
515           if (idleTimeout != 0)
516             releaseTimes.addLast(new Long JavaDoc(System.currentTimeMillis()));
517           this.notify();
518           if (logger.isDebugEnabled())
519             logger.debug("Bad connection " + c
520                 + " has been replaced by a new connection.");
521         }
522       }
523       else if (logger.isDebugEnabled())
524         logger.debug("Bad connection " + c + " has been removed.");
525     }
526     else
527       logger.error("Failed to release connection " + c
528           + " (not found in active pool)");
529     notifyAll();
530   }
531
532   /**
533    * @see org.continuent.sequoia.controller.connection.AbstractConnectionManager#getXmlImpl()
534    */

535   public String JavaDoc getXmlImpl()
536   {
537     StringBuffer JavaDoc info = new StringBuffer JavaDoc();
538     info.append("<" + DatabasesXmlTags.ELT_VariablePoolConnectionManager + " "
539         + DatabasesXmlTags.ATT_initPoolSize + "=\"" + initPoolSize + "\" "
540         + DatabasesXmlTags.ATT_minPoolSize + "=\"" + minPoolSize + "\" "
541         + DatabasesXmlTags.ATT_maxPoolSize + "=\"" + maxPoolSize + "\" "
542         + DatabasesXmlTags.ATT_idleTimeout + "=\"" + idleTimeout / 1000 + "\" "
543         + DatabasesXmlTags.ATT_waitTimeout + "=\"" + waitTimeout / 1000
544         + "\"/>");
545     return info.toString();
546   }
547
548   /**
549    * Allows to remove idle free connections after the idleTimeout timeout.
550    *
551    * @author <a HREF="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
552    */

553   protected class RemoveIdleConnectionsThread extends Thread JavaDoc
554   {
555     private boolean isKilled = false;
556     private VariablePoolConnectionManager thisPool;
557
558     protected RemoveIdleConnectionsThread(String JavaDoc pBackendName,
559         VariablePoolConnectionManager thisPool)
560     {
561       super("RemoveIdleConnectionsThread for backend:" + pBackendName);
562       this.thisPool = thisPool;
563     }
564
565     /**
566      * @see java.lang.Runnable#run()
567      */

568     public void run()
569     {
570       long idleTime, releaseTime;
571       boolean isMinPoolSizeReached = false;
572       synchronized (this)
573       {
574         try
575         {
576           while (!isKilled)
577           {
578             isMinPoolSizeReached = false;
579             // the thread is not launched if idleTimeout equals to 0 (the
580
// connections are never released in this case)
581
if (freeConnections.isEmpty())
582             {
583               wait(); // wait on the thread RemoveIdleConnectionsThread
584
}
585             else if (freeConnections.size() <= minPoolSize)
586             {
587               // Ping these remaining connections when their time out occurs
588
isMinPoolSizeReached = true;
589             }
590
591             if (isKilled)
592               continue; // Just exit
593

594             PooledConnection c = null;
595             synchronized (thisPool)
596             {
597               if (releaseTimes.isEmpty())
598                 continue; // Sanity check
599

600               releaseTime = ((Long JavaDoc) releaseTimes.getFirst()).longValue();
601               idleTime = System.currentTimeMillis() - releaseTime;
602
603               if (idleTime >= idleTimeout)
604               {
605                 c = (PooledConnection) freeConnections.removeFirst();
606                 releaseTimes.removeFirst();
607               }
608             }
609
610             if (c == null)
611             { // Nothing to free, wait for next deadline
612
wait(idleTimeout - idleTime);
613             }
614             else if (isMinPoolSizeReached)
615             {
616               try
617               {
618                 // check the connection is still valid
619
c.getConnection().createStatement().execute(connectionTestStatement);
620                 // and put it again in the pool
621
releaseTimes.addLast(new Long JavaDoc(System.currentTimeMillis()));
622                 freeConnections.addLast(c);
623               }
624               catch (SQLException JavaDoc e)
625               {
626                 // Connection lost... we release it...
627
try
628                 {
629                   c.getConnection().close();
630                 }
631                 catch (SQLException JavaDoc e1)
632                 {
633                   String JavaDoc msg = "An error occured while closing idle connection after the timeout: "
634                       + e;
635                   logger.error(msg);
636                 }
637                 finally
638                 {
639                   poolSize--;
640                 }
641               }
642             }
643             else
644             { // Free the connection out of the synchronized block
645
try
646               {
647                 c.getConnection().close();
648               }
649               catch (SQLException JavaDoc e)
650               {
651                 String JavaDoc msg = "An error occured while closing idle connection after the timeout: "
652                     + e;
653                 logger.error(msg);
654               }
655               finally
656               {
657                 poolSize--;
658               }
659               logger.debug("Released idle connection (idle timeout reached)");
660               continue;
661
662             }
663           }
664         }
665         catch (InterruptedException JavaDoc e)
666         {
667           logger
668               .error("Wait on removeIdleConnectionsThread interrupted in VariablePoolConnectionManager: "
669                   + e);
670         }
671       }
672     }
673   }
674
675 }
Popular Tags