Main Page | Packages | Class Hierarchy | Alphabetical List | Class List | File List | Class Members | Related Pages

VariablePoolConnectionManager.java

00001 /**
00002  * C-JDBC: Clustered JDBC.
00003  * Copyright (C) 2002-2004 French National Institute For Research In Computer
00004  * Science And Control (INRIA).
00005  * Contact: c-jdbc@objectweb.org
00006  * 
00007  * This library is free software; you can redistribute it and/or modify it
00008  * under the terms of the GNU Lesser General Public License as published by the
00009  * Free Software Foundation; either version 2.1 of the License, or any later
00010  * version.
00011  * 
00012  * This library is distributed in the hope that it will be useful, but WITHOUT
00013  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00014  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
00015  * for more details.
00016  * 
00017  * You should have received a copy of the GNU Lesser General Public License
00018  * along with this library; if not, write to the Free Software Foundation,
00019  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
00020  *
00021  * Initial developer(s): Emmanuel Cecchet.
00022  * Contributor(s): Mathieu Peltier.
00023  */
00024 
00025 package org.objectweb.cjdbc.controller.connection;
00026 
00027 import java.io.Serializable;
00028 import java.sql.Connection;
00029 import java.sql.SQLException;
00030 import java.util.EmptyStackException;
00031 import java.util.Iterator;
00032 import java.util.Stack;
00033 
00034 import org.objectweb.cjdbc.common.exceptions.UnreachableBackendException;
00035 import org.objectweb.cjdbc.common.xml.DatabasesXmlTags;
00036 
00037 /**
00038  * This connection manager provides connection pooling with a dynamically
00039  * adjustable pool size.
00040  * <p>
00041  * If the maximum number of active connections is not reached, the
00042  * {@link #getConnection()}method creates a connection. Else, the execution is
00043  * blocked until a connection is freed or the timeout expires. blocked until a
00044  * connection is freed or the timeout expires.
00045  * <p>
00046  * Idle connections in the pool are removed after the timeout idleTimeout if the
00047  * minimum pool size has not been reached.
00048  * 
00049  * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
00050  * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
00051  * @author <a href="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
00052  * @version 1.0
00053  */
00054 public class VariablePoolConnectionManager
00055     extends
00056       AbstractPoolConnectionManager implements Serializable
00057 {
00058   /** Default maximum pool size: default is 0 and means no limit. */
00059   public static final int             DEFAULT_MAX_POOL_SIZE = 0;
00060 
00061   /**
00062    * Default idle timeout in milliseconds: default is 0 and means that once
00063    * allocated, connections are never released.
00064    */
00065   public static final int             DEFAULT_IDLE_TIMEOUT  = 0;
00066 
00067   /**
00068    * Default wait timeout in milliseconds: the default is 0 and means no
00069    * timeout: waits until one connection is freed.
00070    */
00071   public static final int             DEFAULT_WAIT_TIMEOUT  = 0;
00072 
00073   /** Initial pool size to be initialized at startup. */
00074   private int                         initPoolSize;
00075 
00076   /** Minimum pool size. */
00077   private int                         minPoolSize;
00078 
00079   /** Maximum pool size. */
00080   private int                         maxPoolSize;
00081 
00082   /**
00083    * Time a connection can stay idle before begin released (removed from the
00084    * pool) in milliseconds (0 means forever)
00085    */
00086   private int                         idleTimeout;
00087 
00088   /** Maximum time to wait for a connection in milliseconds. */
00089   private int                         waitTimeout;
00090 
00091   /** Stores the time on which connections have been released. */
00092   private Stack                       releaseTimes;
00093 
00094   /** Allow to remove idle connections in the pool. */
00095   private RemoveIdleConnectionsThread removeIdleConnectionsThread;
00096 
00097   /**
00098    * Creates a new <code>VariablePoolConnectionManager</code> instance with
00099    * the default minPoolSize(initial pool size to be initialized at startup).
00100    * 
00101    * @param backendUrl URL of the <code>DatabaseBackend</code> owning this
00102    *          connection manager
00103    * @param backendName name of the <code>DatabaseBackend</code> owning this
00104    *          connection manager
00105    * @param rLogin backend connection login to be used by this connection
00106    *          manager
00107    * @param rPassword backend connection password to be used by this connection
00108    *          manager
00109    * @param driverPath path for driver
00110    * @param driverClassName class name for driver
00111    * @param minPoolSize minimum pool size.
00112    * @param maxPoolSize maximum pool size. 0 means no limit.
00113    * @param idleTimeout time a connection can stay idle before begin released
00114    *          (removed from the pool) in seconds. 0 means no timeout: once
00115    *          allocated, connections are never released.
00116    * @param waitTimeout maximum time to wait for a connection in seconds. 0
00117    *          means no timeout: waits until one connection is freed.
00118    */
00119   public VariablePoolConnectionManager(String backendUrl, String backendName,
00120       String rLogin, String rPassword, String driverPath,
00121       String driverClassName, int minPoolSize, int maxPoolSize,
00122       int idleTimeout, int waitTimeout)
00123   {
00124     this(backendUrl, backendName, rLogin, rPassword, driverPath,
00125         driverClassName, minPoolSize, minPoolSize, maxPoolSize, idleTimeout,
00126         waitTimeout);
00127   }
00128 
00129   /**
00130    * @see java.lang.Object#clone()
00131    */
00132   protected Object clone() throws CloneNotSupportedException
00133   {
00134     return new VariablePoolConnectionManager(backendUrl, backendName, rLogin,
00135         rPassword, driverPath, driverClassName, minPoolSize, maxPoolSize,
00136         idleTimeout, waitTimeout);
00137   }
00138 
00139   /**
00140    * Creates a new <code>VariablePoolConnectionManager</code> instance.
00141    * 
00142    * @param backendUrl URL of the <code>DatabaseBackend</code> owning this
00143    *          connection manager
00144    * @param backendName name of the <code>DatabaseBackend</code> owning this
00145    *          connection manager
00146    * @param rLogin backend connection login to be used by this connection
00147    *          manager
00148    * @param rPassword backend connection password to be used by this connection
00149    *          manager
00150    * @param driverPath path for driver
00151    * @param driverClassName class name for driver
00152    * @param initPoolSize initial pool size to be intialized at startup
00153    * @param minPoolSize minimum pool size.
00154    * @param maxPoolSize maximum pool size. 0 means no limit.
00155    * @param idleTimeout time a connection can stay idle before begin released
00156    *          (removed from the pool) in seconds. 0 means no timeout: once
00157    *          allocated, connections are never released.
00158    * @param waitTimeout maximum time to wait for a connection in seconds. 0
00159    *          means no timeout: waits until one connection is freed.
00160    */
00161   public VariablePoolConnectionManager(String backendUrl, String backendName,
00162       String rLogin, String rPassword, String driverPath,
00163       String driverClassName, int initPoolSize, int minPoolSize,
00164       int maxPoolSize, int idleTimeout, int waitTimeout)
00165   {
00166     super(backendUrl, backendName, rLogin, rPassword, driverPath,
00167         driverClassName, maxPoolSize == 0 ? (initPoolSize > minPoolSize
00168             ? initPoolSize
00169             : minPoolSize) : maxPoolSize);
00170     this.initPoolSize = initPoolSize;
00171     this.minPoolSize = minPoolSize;
00172     this.maxPoolSize = maxPoolSize;
00173     this.idleTimeout = idleTimeout * 1000;
00174     this.waitTimeout = waitTimeout * 1000;
00175   }
00176 
00177   /**
00178    * Gets the max pool size.
00179    * 
00180    * @return a <code>int</code> value.
00181    */
00182   public int getMaxPoolSize()
00183   {
00184     return maxPoolSize;
00185   }
00186 
00187   /**
00188    * Gets the min pool size.
00189    * 
00190    * @return a <code>int</code> value.
00191    */
00192   public int getMinPoolSize()
00193   {
00194     return minPoolSize;
00195   }
00196 
00197   /**
00198    * Gets the idle timeout.
00199    * 
00200    * @return a <code>int</code> value.
00201    */
00202   public int getIdleTimeout()
00203   {
00204     return idleTimeout;
00205   }
00206 
00207   /**
00208    * Gets the wait timeout.
00209    * 
00210    * @return a <code>int</code> value.
00211    */
00212   public int getWaitTimeout()
00213   {
00214     return waitTimeout;
00215   }
00216 
00217   /**
00218    * @see org.objectweb.cjdbc.controller.connection.AbstractPoolConnectionManager#initializeConnections()
00219    */
00220   public synchronized void initializeConnections() throws SQLException
00221   {
00222     poolSize = maxPoolSize == 0 ? (initPoolSize > minPoolSize
00223         ? initPoolSize
00224         : minPoolSize) : maxPoolSize;
00225     super.initializeConnections(initPoolSize);
00226 
00227     if (idleTimeout != 0)
00228     {
00229       // Create the thread which manages the free connections
00230       removeIdleConnectionsThread = new RemoveIdleConnectionsThread(
00231           this.backendName);
00232 
00233       // Intialize release time for the initial connections if an idleTimeout
00234       // is set
00235       releaseTimes = new Stack();
00236       Iterator it = freeConnections.iterator();
00237       Long currentTime = new Long(System.currentTimeMillis());
00238       while (it.hasNext())
00239       {
00240         it.next();
00241         releaseTimes.push(currentTime);
00242       }
00243 
00244       // Start the thread
00245       removeIdleConnectionsThread.start();
00246 
00247       synchronized (removeIdleConnectionsThread)
00248       {
00249         if (releaseTimes.size() > 0)
00250         {
00251           removeIdleConnectionsThread.notify();
00252         }
00253       }
00254     }
00255   }
00256 
00257   /**
00258    * @see org.objectweb.cjdbc.controller.connection.AbstractConnectionManager#finalizeConnections()
00259    */
00260   public synchronized void finalizeConnections() throws SQLException
00261   {
00262     if (removeIdleConnectionsThread != null)
00263     {
00264       synchronized (removeIdleConnectionsThread)
00265       {
00266         removeIdleConnectionsThread.isKilled = true;
00267         idleTimeout = 0;
00268         removeIdleConnectionsThread.notify();
00269       }
00270       try
00271       {
00272         removeIdleConnectionsThread.join();
00273       }
00274       catch (InterruptedException e)
00275       {
00276       }
00277     }
00278     super.finalizeConnections();
00279   }
00280 
00281   /**
00282    * Gets a connection from the pool.
00283    * <p>
00284    * If the current number of active connections is lower than the maximum pool
00285    * size, a new connection is created. If the creation fails, this method waits
00286    * for a connection to be freed.
00287    * <p>
00288    * If the maximum number of active connections is reached, this methods blocks
00289    * until a connection is freed or the timeout expires.
00290    * 
00291    * @return a connection from the pool or <code>null</code> if the timeout
00292    *         has expired.
00293    * @throws UnreachableBackendException if the backend must be disabled
00294    * @see org.objectweb.cjdbc.controller.connection.AbstractConnectionManager#getConnection()
00295    */
00296   public Connection getConnection() throws UnreachableBackendException
00297   {
00298     if (!initialized)
00299     {
00300       logger
00301           .error("Requesting a connection from a non-initialized connection manager");
00302       return null;
00303     }
00304 
00305     long lTimeout = waitTimeout;
00306     synchronized (freeConnections)
00307     {
00308       if (freeConnections.isEmpty())
00309       {
00310         if ((maxPoolSize == 0) || (activeConnections.size() < maxPoolSize))
00311         {
00312           Connection c = getConnectionFromDriver();
00313           if (c == null)
00314           {
00315             if (activeConnections.size() == 0)
00316             { // No connection active and backend unreachable, the backend
00317               // is probably dead
00318               logger
00319                   .error("Backend " + backendName + " is no more accessible.");
00320               throw new UnreachableBackendException();
00321             }
00322             // If it fails, just wait for a connection to be freed
00323             if (logger.isWarnEnabled())
00324               logger.warn("Failed to create new connection on backend '"
00325                   + backendName + "', waiting for a connection to be freed.");
00326           }
00327           else
00328           {
00329             freeConnections.add(c);
00330             if (idleTimeout != 0)
00331             {
00332               releaseTimes.add(new Long(System.currentTimeMillis()));
00333             }
00334             poolSize++;
00335           }
00336         }
00337 
00338         /*
00339          * We have to do a while loop() because there is a potential race here.
00340          * When freeConnections is notified in releaseConnection, a new thread
00341          * can take the lock on freeConnections before we wake up/reacquire the
00342          * lock on freeConnections. Therefore, we could wake up and have no
00343          * connection to take! We ensure that everything is correct with a while
00344          * statement and recomputing the timeout between 2 wakeup.
00345          */
00346         while (freeConnections.isEmpty())
00347         {
00348           // Wait
00349           try
00350           {
00351             if (lTimeout > 0)
00352             {
00353               long start = System.currentTimeMillis();
00354               // Convert seconds to milliseconds for wait call
00355               freeConnections.wait(waitTimeout);
00356               long end = System.currentTimeMillis();
00357               lTimeout -= end - start;
00358               if (lTimeout <= 0)
00359               {
00360                 if (logger.isWarnEnabled())
00361                   logger.warn("Timeout expired for connection on backend '"
00362                       + backendName
00363                       + "', consider increasing pool size (current size is "
00364                       + poolSize + ") or timeout (current timeout is "
00365                       + (waitTimeout / 1000) + " seconds)");
00366                 return null;
00367               }
00368             }
00369             else
00370             {
00371               freeConnections.wait();
00372             }
00373           }
00374           catch (InterruptedException e)
00375           {
00376             logger
00377                 .error("Wait on freeConnections interrupted in VariablePoolConnectionManager");
00378             return null;
00379           }
00380         }
00381       }
00382 
00383       // Get the connection
00384       try
00385       {
00386         Connection c = (Connection) freeConnections.pop();
00387         if (idleTimeout != 0)
00388         {
00389           releaseTimes.pop();
00390         }
00391         activeConnections.add(c);
00392         return c;
00393       }
00394       catch (EmptyStackException e)
00395       {
00396         if (logger.isErrorEnabled())
00397           logger.error("Failed to get a connection on backend '" + backendName
00398               + "' but an idle connection was expected");
00399         return null;
00400       }
00401     }
00402   }
00403 
00404   /**
00405    * @see org.objectweb.cjdbc.controller.connection.AbstractConnectionManager#releaseConnection(Connection)
00406    */
00407   public void releaseConnection(Connection c)
00408   {
00409     if (!initialized)
00410       return; // We probably have been disabled
00411 
00412     boolean notifyThread = false;
00413     synchronized (freeConnections)
00414     {
00415       if (activeConnections.remove(c))
00416       {
00417         if (idleTimeout != 0)
00418         {
00419           notifyThread = freeConnections.isEmpty()
00420               || (freeConnections.size() == minPoolSize);
00421           freeConnections.push(c);
00422           freeConnections.notify();
00423           releaseTimes.push(new Long(System.currentTimeMillis()));
00424         }
00425         else
00426         {
00427           freeConnections.push(c);
00428           freeConnections.notify();
00429         }
00430       }
00431       else
00432         logger.error("Failed to release connection " + c
00433             + " (not found in active pool)");
00434     }
00435 
00436     if (notifyThread)
00437       synchronized (removeIdleConnectionsThread)
00438       {
00439         removeIdleConnectionsThread.notify();
00440       }
00441   }
00442 
00443   /**
00444    * @see org.objectweb.cjdbc.controller.connection.AbstractPoolConnectionManager#deleteConnection(Connection)
00445    */
00446   public void deleteConnection(Connection c)
00447   {
00448     if (!initialized)
00449       return; // We probably have been disabled
00450 
00451     synchronized (freeConnections)
00452     {
00453       if (activeConnections.remove(c))
00454       {
00455         poolSize--;
00456         if (poolSize < minPoolSize)
00457         {
00458           Connection newConnection = getConnectionFromDriver();
00459           if (newConnection == null)
00460           {
00461             if (logger.isDebugEnabled())
00462               logger.error("Bad connection " + c
00463                   + " has been removed but cannot be replaced.");
00464           }
00465           else
00466           {
00467             freeConnections.push(newConnection);
00468             freeConnections.notify();
00469             if (logger.isDebugEnabled())
00470               logger.debug("Bad connection " + c
00471                   + " has been replaced by a new connection.");
00472           }
00473         }
00474         else if (logger.isDebugEnabled())
00475           logger.debug("Bad connection " + c + " has been removed.");
00476       }
00477       else
00478         logger.error("Failed to release connection " + c
00479             + " (not found in active pool)");
00480     }
00481   }
00482 
00483   /**
00484    * @see org.objectweb.cjdbc.controller.connection.AbstractConnectionManager#getXmlImpl()
00485    */
00486   public String getXmlImpl()
00487   {
00488     StringBuffer info = new StringBuffer();
00489     info.append("<" + DatabasesXmlTags.ELT_VariablePoolConnectionManager + " "
00490         + DatabasesXmlTags.ATT_initPoolSize + "=\"" + initPoolSize + "\" "
00491         + DatabasesXmlTags.ATT_minPoolSize + "=\"" + minPoolSize + "\" "
00492         + DatabasesXmlTags.ATT_maxPoolSize + "=\"" + maxPoolSize + "\" "
00493         + DatabasesXmlTags.ATT_idleTimeout + "=\"" + idleTimeout / 1000 + "\" "
00494         + DatabasesXmlTags.ATT_waitTimeout + "=\"" + waitTimeout / 1000
00495         + "\"/>");
00496     return info.toString();
00497   }
00498 
00499   /**
00500    * Allows to remove idle free connections after the idleTimeout timeout.
00501    * 
00502    * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
00503    */
00504   protected class RemoveIdleConnectionsThread extends Thread
00505   {
00506     private boolean isKilled = false;
00507 
00508     protected RemoveIdleConnectionsThread(String pBackendName)
00509     {
00510       super("RemoveIdleConnectionsThread for backend:" + pBackendName);
00511     }
00512 
00513     /**
00514      * @see java.lang.Runnable#run()
00515      */
00516     public void run()
00517     {
00518       long idleTime, releaseTime;
00519       synchronized (this)
00520       {
00521         try
00522         {
00523           while (!isKilled)
00524           {
00525             // the thread is not launched if idleTimeout equals to 0 (the
00526             // connections are never released in this case)
00527             if (freeConnections.isEmpty()
00528                 || (freeConnections.size() == minPoolSize))
00529             {
00530               wait();
00531             }
00532 
00533             Connection c = null;
00534             synchronized (freeConnections)
00535             {
00536               if (releaseTimes.isEmpty())
00537                 continue; // Sanity check
00538 
00539               releaseTime = ((Long) releaseTimes.get(0)).longValue();
00540               idleTime = System.currentTimeMillis() - releaseTime;
00541 
00542               if (idleTime >= idleTimeout)
00543                 c = (Connection) freeConnections.remove(0);
00544             }
00545 
00546             if (c == null)
00547             { // Nothing to free, wait for next deadline
00548               wait(idleTimeout - idleTime);
00549             }
00550             else
00551             { // Free the connection out of the synchronized block
00552               try
00553               {
00554                 c.close();
00555               }
00556               catch (SQLException e)
00557               {
00558                 String msg = "An error occured while closing idle connection after the timeout: "
00559                     + e;
00560                 logger.error(msg);
00561               }
00562               finally
00563               {
00564                 releaseTimes.remove(0);
00565                 poolSize--;
00566               }
00567               logger.debug("Released idle connection (idle timeout reached)");
00568               continue;
00569 
00570             }
00571           }
00572         }
00573         catch (InterruptedException e)
00574         {
00575           logger
00576               .error("Wait on removeIdleConnectionsThread interrupted in VariablePoolConnectionManager: "
00577                   + e);
00578         }
00579       }
00580     }
00581   }
00582 
00583 }

Generated on Mon Apr 11 22:01:35 2005 for C-JDBC by  doxygen 1.3.9.1