1 /*
   2  * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.jndi.ldap.pool;
  27 
  28 import java.util.Map;
  29 import java.util.WeakHashMap;
  30 import java.util.Collection;
  31 import java.util.Collections;
  32 import java.util.Iterator;
  33 import java.util.LinkedList;
  34 
  35 import java.io.PrintStream;
  36 import java.lang.ref.Reference;
  37 import java.lang.ref.ReferenceQueue;
  38 import javax.naming.NamingException;
  39 
  40 /**
  41  * A map of pool ids to Connections.
  42  * Key is an object that uniquely identifies a PooledConnection request
  43  * (typically information needed to create the connection).
  44  * The definitions of the key's equals() and hashCode() methods are
  45  * vital to its unique identification in a Pool.
  46  *
  47  * Value is a ConnectionsRef, which is a reference to Connections,
  48  * a list of equivalent connections.
  49  *
  50  * Supports methods that
  51  * - retrieves (or creates as necessary) a connection from the pool
  52  * - removes expired connections from the pool
  53  *
  54  * Connections cleanup:
  55  * A WeakHashMap is used for mapping the pool ids and Connections.
  56  * A SoftReference from the value to the key is kept to hold the map
  57  * entry as long as possible. This allows the GC to remove Connections
  58  * from the Pool under situations of VM running out of resources.
  59  * To take an appropriate action of 'closing the connections' before the GC
  60  * reclaims the ConnectionsRef objects, the ConnectionsRef objects are made
  61  * weakly reachable through a list of weak references registered with
  62  * a reference queue.
  63  * Upon an entry gets removed from the WeakHashMap, the ConnectionsRef (value
  64  * in the map) object is weakly reachable. When another sweep of
  65  * clearing the weak references is made by the GC it puts the corresponding
  66  * ConnectionsWeakRef object into the reference queue.
  67  * The reference queue is monitored lazily for reclaimable Connections
  68  * whenever a pooled connection is requested or a call to remove the expired
  69  * connections is made. The monitoring is done regularly when idle connection
  70  * timeout is set as the PoolCleaner removes expired connections periodically.
  71  * As determined by experimentation, cleanup of resources using the
  72  * ReferenceQueue mechanism is reliable and has more immediate effect than the
  73  * finalizer approach.
  74  *
  75  * @author Rosanna Lee
  76  */
  77 
  78 final public class Pool {
  79 
  80     static final boolean debug = com.sun.jndi.ldap.LdapPoolManager.debug;
  81 
  82     /*
  83      * Used for connections cleanup
  84      */
  85     private static final ReferenceQueue<ConnectionsRef> queue =
  86         new ReferenceQueue<>();
  87     private static final Collection<Reference<ConnectionsRef>> weakRefs =
  88         Collections.synchronizedList(new LinkedList<Reference<ConnectionsRef>>());
  89 
  90     final private int maxSize;    // max num of identical conn per pool
  91     final private int prefSize;   // preferred num of identical conn per pool
  92     final private int initSize;   // initial number of identical conn to create
  93     final private Map<Object, ConnectionsRef> map;
  94 
  95     public Pool(int initSize, int prefSize, int maxSize) {
  96         map = new WeakHashMap<>();
  97         this.prefSize = prefSize;
  98         this.maxSize = maxSize;
  99         this.initSize = initSize;
 100     }
 101 
 102     /**
 103      * Gets a pooled connection for id. The pooled connection might be
 104      * newly created, as governed by the maxSize and prefSize settings.
 105      * If a pooled connection is unavailable and cannot be created due
 106      * to the maxSize constraint, this call blocks until the constraint
 107      * is removed or until 'timeout' ms has elapsed.
 108      *
 109      * @param id identity of the connection to get
 110      * @param timeout the number of milliseconds to wait before giving up
 111      * @param factory the factory to use for creating the connection if
 112      *          creation is necessary
 113      * @return a pooled connection
 114      * @throws NamingException the connection could not be created due to
 115      *                          an error.
 116      */
 117     public PooledConnection getPooledConnection(Object id, long timeout,
 118         PooledConnectionFactory factory) throws NamingException {
 119 
 120         d("get(): ", id);
 121         if (debug) {
 122             synchronized (map) {
 123                 d("size: ", map.size());
 124             }
 125         }
 126 
 127         expungeStaleConnections();
 128 
 129         Connections conns;
 130         synchronized (map) {
 131             conns = getConnections(id);
 132             if (conns == null) {
 133                 d("get(): creating new connections list for ", id);
 134 
 135                 // No connections for this id so create a new list
 136                 conns = new Connections(id, initSize, prefSize, maxSize,
 137                     factory);
 138                 ConnectionsRef connsRef = new ConnectionsRef(conns);
 139                 map.put(id, connsRef);
 140 
 141                 // Create a weak reference to ConnectionsRef
 142                 Reference<ConnectionsRef> weakRef =
 143                         new ConnectionsWeakRef(connsRef, queue);
 144 
 145                 // Keep the weak reference through the element of a linked list
 146                 weakRefs.add(weakRef);
 147             }
 148             d("get(): size after: ", map.size());
 149         }
 150 
 151         return conns.get(timeout, factory); // get one connection from list
 152     }
 153 
 154     private Connections getConnections(Object id) {
 155         ConnectionsRef ref = map.get(id);
 156         return (ref != null) ? ref.getConnections() : null;
 157     }
 158 
 159     /**
 160      * Goes through the connections in this Pool and expires ones that
 161      * have been idle before 'threshold'. An expired connection is closed
 162      * and then removed from the pool (removePooledConnection() will eventually
 163      * be called, and the list of pools itself removed if it becomes empty).
 164      *
 165      * @param threshold connections idle before 'threshold' should be closed
 166      *          and removed.
 167      */
 168     public void expire(long threshold) {
 169         synchronized (map) {
 170             Iterator<ConnectionsRef> iter = map.values().iterator();
 171             Connections conns;
 172             while (iter.hasNext()) {
 173                 conns = iter.next().getConnections();
 174                 if (conns.expire(threshold)) {
 175                     d("expire(): removing ", conns);
 176                     iter.remove();
 177                 }
 178             }
 179         }
 180         expungeStaleConnections();
 181     }
 182 
 183     /*
 184      * Closes the connections contained in the ConnectionsRef object that
 185      * is going to be reclaimed by the GC. Called by getPooledConnection()
 186      * and expire() methods of this class.
 187      */
 188     private static void expungeStaleConnections() {
 189         ConnectionsWeakRef releaseRef = null;
 190         while ((releaseRef = (ConnectionsWeakRef) queue.poll())
 191                                         != null) {
 192             Connections conns = releaseRef.getConnections();
 193 
 194             if (debug) {
 195                 System.err.println(
 196                         "weak reference cleanup: Closing Connections:" + conns);
 197             }
 198 
 199             // cleanup
 200             conns.close();
 201             weakRefs.remove(releaseRef);
 202             releaseRef.clear();
 203          }
 204     }
 205 
 206 
 207     public void showStats(PrintStream out) {
 208         Object id;
 209         Connections conns;
 210 
 211         out.println("===== Pool start ======================");
 212         out.println("maximum pool size: " + maxSize);
 213         out.println("preferred pool size: " + prefSize);
 214         out.println("initial pool size: " + initSize);
 215 
 216         synchronized (map) {
 217             out.println("current pool size: " + map.size());
 218 
 219             for (Map.Entry<Object, ConnectionsRef> entry : map.entrySet()) {
 220                 id = entry.getKey();
 221                 conns = entry.getValue().getConnections();
 222                 out.println("   " + id + ":" + conns.getStats());
 223             }
 224         }
 225 
 226         out.println("====== Pool end =====================");
 227     }
 228 
 229     public String toString() {
 230         synchronized (map) {
 231             return super.toString() + " " + map.toString();
 232         }
 233     }
 234 
 235     private void d(String msg, int i) {
 236         if (debug) {
 237             System.err.println(this + "." + msg + i);
 238         }
 239     }
 240 
 241     private void d(String msg, Object obj) {
 242         if (debug) {
 243             System.err.println(this + "." + msg + obj);
 244         }
 245     }
 246 }