Why doesn't DirContext.close() return the LDAP connection to the pool?

10,374

Solution 1

After a bit of investigation I've found that the LDAP connection isn't returned to the pool because the SearchResult object contains a reference to the LdapCtx object.

If you replace

results.next();

With the following

SeachResult ob = results.next();
((Context)ob.getObject()).close();

The the connection will be correctly returned to the pool. This seems like a bug in the default implementation.

Theproblem doesn't exist when using the Spring LDAPTemplate because it provides a custom "java.naming.factory.object" in the environment that closes the LdapCtx as part of the process of constructing the SearchResult. This can be easily demostrated by adding the Spring LDAP library to your classpath and adding the following to the InitialContext

java.naming.factory.object=org.springframework.ldap.core.support.DefaultDirObjectFactory

When this is done the object held by the SearchResult changes from com.sun.jndi.ldap.LdapCtx:com.sun.jndi.ldap.LdapCtx to org.springframework.ldap.core.DirContextAdapter. The DefaultDirObjectFactory class is responsible for creating the DirContextAdapter and takes care to close the LdapCtx before returning the DirContextAdapter to the DirectoryManager. Here is the finally block from the DefaultDirObjectFactory

finally {
        // It seems that the object supplied to the obj parameter is a
        // DirContext instance with reference to the same Ldap connection as
        // the original context. Since it is not the same instance (that's
        // the nameCtx parameter) this one really needs to be closed in
        // order to correctly clean up and return the connection to the pool
        // when we're finished with the surrounding operation.
        if (obj instanceof Context) {

            Context ctx = (Context) obj;
            try {
                ctx.close();
            }
            catch (Exception e) {
                // Never mind this
            }

        }
    }

Solution 2

Change your SearchControls object to have the returningObjFlag attribute false. You usually don't need the object itself, just its nameInNamespace and its attributes. You only need the object itself if you are going to create or modify subcontexts.

Share:
10,374

Related videos on Youtube

bacar
Author by

bacar

An experienced & analytical software development leader, with polyglot hand-on development skills, expertise in design/architecture of maintainable, scalable systems, and exotics equity derivatives pricing & risk systems. Extensive experience with user-facing systems in investment banks, working closely with quants, and on regulatory projects. A strong record in driving DevOps & agile, improving development processes and self-improvement. https://www.linkedin.com/in/barisacar/

Updated on October 21, 2022

Comments

  • bacar
    bacar over 1 year

    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 DirContext from 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
    
  • bacar
    bacar over 11 years
    Thanks! It's frustrating that the documentation says in order for the LDAP provider to properly manage the pooled connections, you must be diligent about invoking Context.close() on contexts that you no longer need, yet doesn't explain how about how you can meaningfully do this nor tell you which provided objects may themselves take hold of these objects that also need closing.
  • bacar
    bacar over 11 years
    Thanks - that does seem to work, and I've got a better understanding of what the flag does from the LDAP tutorial page on Search Results