JDK-6870947 : 15 sec delay detecting "socket closed" condition when a TCP connection is reset by an LDAP server
  • Type: Bug
  • Component: core-libs
  • Sub-Component: javax.naming
  • Affected Version: 6u15
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2009-08-12
  • Updated: 2016-04-26
  • Resolved: 2010-08-11
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7
7 b105Fixed
Related Reports
Relates :  
Description
PLATFORM:	Any

JDK VERSION:	JDK 5, 6

SYNOPSIS:	15 seconds delay detecting "socket closed" condition when a TCP
connection is reset by an LDAP server

DESCRIPTION and RECREATION INSTRUCTIONS from Licensee:
-------------------------------------
Recreation steps for Windows platform :

1. Run the java client provided below. A real LDAP server must be up and
   running, and the correct URL given in the testcase.  The java client
   does the following:
   -  creates a LDAP connection
   -  sleeps for 120 seconds

2. During the 120 second sleep we need to simulate an abrupt LDAP
   server failure, in such a way that no tcp FIN/RST is sent to the LDAP
   client. There are two ways to achieve this:

   a) Unplug the network cable on the LDAP server, reboot the LDAP
      server, and plug the cable back in again.
    
      OR

   b) Write a testcase that does the following:
      - Start a network filter (firewall rule) to block all LDAP server
        traffic
      - Kill the LDAP server and remove any residual sockets
      - Disable the network filter

3. When the java client wakes up from sleep 120, it will still think the
   connection to LDAP server is valid and send the query.

At the TCP layer the following now happens:

- Client sends an LDAP query to the server
- The server does not know this TCP connection, so it will reply RST
  IMMEDIATELY, with no delay.
- But the client will only throw a Java exception (socket closed) after
  a delay of 15 seconds. ===> PROBLEM

The expected behaviour is for the Exception to be thrown immediately, as soon as the RST response is received from the LDAP server. Note that this is exactly what happens on 1.4.2. We believe the behaviour changed (i.e. a regression was introduced) when CR 6207824 was fixed:

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6207824

It seems that the cleanup thread is not notifying the waiting thread when the connection is reset. The LDAP requests were not cancelled, so the waiting thread was not getting notified. A potential solution is to cancel pending LDAP requests in the cleanup method while notifying the parent.

EXAMPLE OUTPUT
--------------
Here is some example output from the testcase. Note the timestamps which show the 15 second delay between the second search starting and the Exception being thrown:

C:\>java LdapClient
initializing directory context
Tue Jul 28 22:41:05 CST 2009 : doing 1st search
>>> >>>uid=user
attribute: userpassword
        value: [B@18a47e0
attribute: uid
        value: user
attribute: objectclass
        value: organizationalPerson
        value: person
        value: top
        value: inetOrgPerson
attribute: sn
        value: admin
attribute: cn
        value: example admin

Press Enter to continue


Tue Jul 28 22:42:11 CST 2009 : doing 2nd search
Tue Jul 28 22:42:26 CST 2009Exception caught :
javax.naming.ServiceUnavailableException: phoenix:389; socket closed; remaining name 'ou=users,dc=example,dc=com'
        at com.sun.jndi.ldap.Connection.readReply(Unknown Source)
        at com.sun.jndi.ldap.LdapClient.getSearchReply(Unknown Source)
        at com.sun.jndi.ldap.LdapClient.search(Unknown Source)
        at com.sun.jndi.ldap.LdapCtx.doSearch(Unknown Source)
        at com.sun.jndi.ldap.LdapCtx.searchAux(Unknown Source)
        at com.sun.jndi.ldap.LdapCtx.c_search(Unknown Source)
        at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_search(Unknown Source)
        at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(Unknown Source)
        at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(Unknown Source)
        at javax.naming.directory.InitialDirContext.search(Unknown Source)
        at LdapClient2.main(LdapClient2.java:104)

TESTCASE SOURCE
---------------
import java.io.*;
import java.net.*;
import javax.net.ssl.*;
import java.security.*;
import java.security.cert.*;
import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.ldap.*;
import java.util.Hashtable;

public class LdapClient {

    public static void main(String[] args) {

        Hashtable env = new Hashtable(11);
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://phoenix");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, "cn=root");
        env.put(Context.SECURITY_CREDENTIALS, "s0lution");
        env.put("java.naming.ldap.version", "3");

        LdapContext ctx = null;

        try {
            System.out.println("initializing directory context");
            //DirContext ctx = new InitialDirContext(env);
            ctx = new InitialLdapContext(env,null);
        } catch (Exception e) {
            e.printStackTrace();
        }

        SearchControls scontrols = new SearchControls();
        scontrols.setSearchScope(SearchControls.SUBTREE_SCOPE);

        java.util.Date newDate = new java.util.Date();
        System.out.println( newDate + " : doing 1st search");

        try {

            NamingEnumeration answer = ctx.search ("ou=users,dc=example,dc=com","uid=user",scontrols);

            while (answer.hasMore()) {
                SearchResult sr = (SearchResult)answer.next();
                System.out.println(">>>" + sr.getName());

                Attributes attrs = sr.getAttributes();
                if ( attrs == null ) {
                    System.out.println("No attributes");
                } else {
                    for (NamingEnumeration ae = attrs.getAll();ae.hasMore();) {
                        Attribute attr = (Attribute)ae.next();
                        System.out.println("attribute: " + attr.getID());
                        for (NamingEnumeration e = attr.getAll();
                            e.hasMore();
                            System.out.println("	value: " + e.next())
                            );
                    }
                }
            }  // end while
        } catch ( Exception e ) {
            newDate = new java.util.Date();
            System.out.println( newDate + "Exception caught : ");
            e.printStackTrace();
        }

        newDate = new java.util.Date();
        System.out.println();
        System.out.println(newDate + " : sleeping for 120 seconds");

        try {
            Thread.sleep(120000);
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println();
        newDate = new java.util.Date();
        System.out.println( newDate + " : doing 2nd search");

        try {

            NamingEnumeration answer = ctx.search ("ou=users,dc=example,dc=com","uid=user",scontrols);

            while (answer.hasMore()) {
                SearchResult sr = (SearchResult)answer.next();
                System.out.println(">>>" + sr.getName());

                Attributes attrs = sr.getAttributes();
                if ( attrs == null ) {
                    System.out.println("No attributes");
                } else {
                    for (NamingEnumeration ae = attrs.getAll();ae.hasMore();) {
                        Attribute attr = (Attribute)ae.next();
                        System.out.println("attribute: " + attr.getID());
                        for (NamingEnumeration e = attr.getAll();
                            e.hasMore();
                            System.out.println("       value: " + e.next())
                            );
                    }
                }
            }  // end while
        } catch ( Exception e ) {
            newDate = new java.util.Date();
            System.out.println( newDate + "Exception caught : ");
            e.printStackTrace();
        }
    }
}

SUGGESTED FIX:	A suggested fix has been uploaded to this bug report.

WORKAROUND:  Set the system property "com.sun.jndi.ldap.read.timeout" to zero or a low value.  However, this should not be necessary - the Exception should be thrown as soon as the RST is received. There should be no need to tweak any timeout settings, because the client should not need to time out.

Comments
EVALUATION fix as suggestion
27-07-2010

SUGGESTED FIX Licensee has provided the following suggested fix: src\classes\sov\com\sun\jndi\ldap\Connection.java *** Connection(old).java 2009-05-04 00:02:54.000000000 +0100 --- Connection(new).java 2009-08-03 13:43:18.000000000 +0100 *************** *** 641,646 **** --- 641,651 ---- } } if (nparent) { + LdapRequest ldr = pendingRequests; + while (ldr != null) { + ldr.notify(); + ldr = ldr.next; + } parent.processConnectionClosure(); } }
12-08-2009