I noticed while using an LDAP connect pool that calling close() on the context did not appear to return it to the pool, despite the documentation saying otherwise. Thus when I attempt to get an item from the pool when it is already at its max size, it hangs.
I managed to narrow it down to a minimal case. Even though I believe I'm calling close() on all of the relevant objects deterministically, it seems to rely on garbage collection to actually return objects to the pool, which is unexpected. Why is this happening? Is there some other object I should be closing?
In the code snippet below:
- I've artifically set the max pool size to 1 to highlight the problem.
- I get a
DirContextfrom the pool (line (2)), attempt to return it to the pool (line (4)), then get another one from the pool (line (6)) which ought to return the same, returned object. - instead, the second request (line (6)) hangs on some internal call to
Object.wait(). I'm guessing it's waiting for a pooled object to become available. - if turn off pooling by commenting out (1), it doesn't hang (but I want pooling!).
- if I comment out (3) - a call to
SearchResults.next()- it works fine. - if I uncomment line (5) to force garbage collection between the 'return to pool' call and requesting a new object to the pool, it does not hang.
Since commenting out line (3) makes the problem go away, perhaps I'm not closing the return value of it correctly, and it's holding the pooled connection open. However, the method results.next() returns a SearchResult in this case, which has no close method and no guidance in its documentation on how to close it cleanly.
The test case:
@Test
public void testHangs() throws NamingException {
System.setProperty("com.sun.jndi.ldap.connect.pool.debug", "fine");
System.setProperty("com.sun.jndi.ldap.connect.pool.maxsize", "1");
Hashtable<String,String> env = new Hashtable<String,String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_PRINCIPAL, user);
env.put(Context.SECURITY_CREDENTIALS, passwd);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.PROVIDER_URL, ldapUrl);
// use a connection pool
env.put("com.sun.jndi.ldap.connect.pool", "true"); // ----------------- (1)
// get a context from the pool.
DirContext context = new InitialDirContext(env); // -------------------- (2)
NamingEnumeration<SearchResult> results = context.search("", query, getSC());
// obviously the next two lines would normally be in a
// while(results.hasMore()) { ... = results.next(); } loop.
assertTrue(results.hasMore()); // this is only a problem when there are some results.
results.next(); // ----------------------------------------------------- (3)
// ensure the context is returned to the pool.
results.close();
context.close(); // ---------------------------------------------------- (4)
//System.gc(); // ------------------------------------------------------ (5)
new InitialDirContext(env); // hangs here! ---------------------------- (6)
}
With the code as it is, my console shows:
Create com.sun.jndi.ldap.LdapClient@1a7bf11[ldapad:389]
Use com.sun.jndi.ldap.LdapClient@1a7bf11
Whereas if I force the GC I additionally see:
Release com.sun.jndi.ldap.LdapClient@93dee9 <-- on GC
Use com.sun.jndi.ldap.LdapClient@93dee9 <-- on new InitialDirContext