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.ArrayList;
29 import java.util.Map;
30 import java.util.WeakHashMap;
31 import java.util.Collection;
32 import java.util.Collections;
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 
Pool(int initSize, int prefSize, int maxSize)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      */
getPooledConnection(Object id, long timeout, PooledConnectionFactory factory)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 
getConnections(Object id)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      */
expire(long threshold)168     public void expire(long threshold) {
169         Collection<ConnectionsRef> copy;
170         synchronized (map) {
171             copy = new ArrayList<>(map.values());
172         }
173 
174         ArrayList<ConnectionsRef> removed = new ArrayList<>();
175         Connections conns;
176         for (ConnectionsRef ref : copy) {
177             conns = ref.getConnections();
178             if (conns.expire(threshold)) {
179                 d("expire(): removing ", conns);
180                 removed.add(ref);
181             }
182         }
183 
184         synchronized (map) {
185             map.values().removeAll(removed);
186         }
187 
188         expungeStaleConnections();
189     }
190 
191     /*
192      * Closes the connections contained in the ConnectionsRef object that
193      * is going to be reclaimed by the GC. Called by getPooledConnection()
194      * and expire() methods of this class.
195      */
expungeStaleConnections()196     private static void expungeStaleConnections() {
197         ConnectionsWeakRef releaseRef = null;
198         while ((releaseRef = (ConnectionsWeakRef) queue.poll())
199                                         != null) {
200             Connections conns = releaseRef.getConnections();
201 
202             if (debug) {
203                 System.err.println(
204                         "weak reference cleanup: Closing Connections:" + conns);
205             }
206 
207             // cleanup
208             conns.close();
209             weakRefs.remove(releaseRef);
210             releaseRef.clear();
211          }
212     }
213 
214 
showStats(PrintStream out)215     public void showStats(PrintStream out) {
216         Object id;
217         Connections conns;
218 
219         out.println("===== Pool start ======================");
220         out.println("maximum pool size: " + maxSize);
221         out.println("preferred pool size: " + prefSize);
222         out.println("initial pool size: " + initSize);
223 
224         synchronized (map) {
225             out.println("current pool size: " + map.size());
226 
227             for (Map.Entry<Object, ConnectionsRef> entry : map.entrySet()) {
228                 id = entry.getKey();
229                 conns = entry.getValue().getConnections();
230                 out.println("   " + id + ":" + conns.getStats());
231             }
232         }
233 
234         out.println("====== Pool end =====================");
235     }
236 
toString()237     public String toString() {
238         synchronized (map) {
239             return super.toString() + " " + map.toString();
240         }
241     }
242 
d(String msg, int i)243     private void d(String msg, int i) {
244         if (debug) {
245             System.err.println(this + "." + msg + i);
246         }
247     }
248 
d(String msg, Object obj)249     private void d(String msg, Object obj) {
250         if (debug) {
251             System.err.println(this + "." + msg + obj);
252         }
253     }
254 }
255