JDK-8327053 : com.sun.jndi.ldap.LdapClient overrides readTimeout with connectionTimeout
  • Type: Bug
  • Component: core-libs
  • Sub-Component: javax.naming
  • Affected Version: 8,11,17
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: generic
  • CPU: generic
  • Submitted: 2024-02-28
  • Updated: 2024-03-25
  • Resolved: 2024-03-25
Related Reports
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
We are looking at JDK 1.8 and 17 on Mac, but assume this issue is the same for all releases.

A DESCRIPTION OF THE PROBLEM :
When using LDAP, we can set these properties: "com.sun.jndi.ldap.connect.timeout", and "com.sun.jndi.ldap.read.timeout".

If we only set the read timeout, then that will be used once and then overridden by the connection timeout.

We can see where the issue is. It's in LdapClient.java in the authenticate() method, which has the following:

   synchronized LdapResult
    authenticate(boolean initial, String name, Object pw, int version,
        String authMechanism, Control[] ctls,  Hashtable<?,?> env)
        throws NamingException {

        int readTimeout = conn.readTimeout;
        conn.readTimeout = conn.connectTimeout;

We can see that the readTimeout is initialise from the connections readTimeout, but immediately following that, the connection's read timeout is re-initialised to the connection timeout (that makes no sense). If the two are different (or when the connection timeout is unset), then the read timeout will no longer have the correct value.

That change was made in 2012:

a14592d8914a jdk/src/share/classes/com/sun/jndi/ldap/LdapClient.java             (Rob McKenna         2012-10-15 22:34:35 +0100  153)         int readTimeout = conn.readTimeout;
a14592d8914a jdk/src/share/classes/com/sun/jndi/ldap/LdapClient.java             (Rob McKenna         2012-10-15 22:34:35 +0100  154)         conn.readTimeout = conn.connectTimeout;
a14592d8914a jdk/src/share/classes/com/sun/jndi/ldap/LdapClient.java             (Rob McKenna         2012-10-15 22:34:35 +0100  155)         LdapResult res = null;

We noticed this because the readTimeout was <= 0 (even though we had set it) in this code:

at sun.misc.Unsafe.park(native code)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at com.sun.jndi.ldap.LdapRequest.getReplyBer(LdapRequest.java:120)
at com.sun.jndi.ldap.Connection.readReply(Connection.java:469)
at com.sun.jndi.ldap.LdapClient.ldapBind(LdapClient.java:365)
at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:127)
at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:236)
at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2897)
at com.sun.jndi.ldap.LdapCtx.<init>(http://LdapCtx.java:347)
at com.sun.jndi.url.ldap.ldapURLContextFactory.getUsingURLIgnoreRootDN(ldapURLContextFactory.java:60)
at com.sun.jndi.url.ldap.ldapURLContext.getRootURLContext(ldapURLContext.java:61)
at com.sun.jndi.toolkit.url.GenericURLDirContext.getAttributes(GenericURLDirContext.java:100)
at com.sun.jndi.url.ldap.ldapURLContext.getAttributes(ldapURLContext.java:316)
at javax.naming.directory.InitialDirContext.getAttributes(InitialDirContext.java:142)

That thread would never return, because the readTimeout was <= 0 which means the method:

 BerDecoder getReplyBer(long millis) throws NamingException,
                                               InterruptedException {
  
In com.sun.jndi.ldap,LdapRequest would defer to take() in the following snippet:

      BerDecoder result = millis > 0 ?
                replies.poll(millis, TimeUnit.MILLISECONDS) : replies.take();

And in some cases, that would never return, and the thread would be indefinitely blocked - because our configured timeout value had never been passed on.



STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Hard to reproduce - we hit a lockup, inspected the code and saw the issue (as described above).

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The value configured for "com.sun.jndi.ldap.read.timeout" would be honoured.
ACTUAL -
The value for "com.sun.jndi.ldap.read.timeout" is overridden by the setting for "com.sun.jndi.ldap.connect.timeout" (which may be unset).

---------- BEGIN SOURCE ----------
The source code is in the JDK.
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Set both "com.sun.jndi.ldap.connect.timeout" and "com.sun.jndi.ldap.read.timeout" to the same value.

FREQUENCY : always



Comments
LDAP authenticate operation is treated as part of a connect timeout. JDK-8000487 changed LdapClient code to use read timeout functionality of Connection::readReply method for implementation of connect timeout. The following comment describes the change: "When attempting to connect while using simple auth, we actually perform a read. This means that the connection is subject to the read timeout. The solution is to alter the readtimeout to the same value as the connect timeout for the duration of the auth." The solution added by the fix was the following: For auth the read timeout is set to connect timeout value for the duration of auth operation (the original read timeout is saved to a local variable): int readTimeout = conn.readTimeout; conn.readTimeout = conn.connectTimeout; Then the read timeout in connection instance is restored back to original value (from the local variable) in final block in LdapClient::authenticate method: } finally { conn.readTimeout = readTimeout; } > If the two are different (or when the connection timeout is unset), then the read timeout will no longer have the correct value. Based on analysis above the `readTimeout` is overriden by `connectTimeout` value only for the duration of auth operation, but then it is restored back to the original `readTimeout` value. The code looks correct and follows the expectations described in JDK-8000487.
25-03-2024

Looks like there could be an issue here.
01-03-2024