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