-
Notifications
You must be signed in to change notification settings - Fork 6.2k
8357708: com.sun.jndi.ldap.Connection ignores queued LDAP replies if Connection is subsequently closed #25449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…Connection is subsequently closed
|
👋 Welcome back jpai! A progress list of the required criteria for merging this PR into |
|
@jaikiran This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be: You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 10 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
Webrevs
|
| lock.unlock(); | ||
| // Add a new reply to the queue of unprocessed replies. | ||
| try { | ||
| replies.put(ber); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So theoretically this could get into the queue after one of the two markers has already been put in the queue, since we no longer use the lock. The question is: is this a problem? I'd be tempted to say no - except that getReplyBer() will take the markers out of the queue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello Daniel,
I'd be tempted to say no - except that getReplyBer() will take the markers out of the queue.
That's correct - the change intentionally removes the lock and also lets the close/cancel markers land into the queue so that if the getReplyBer() is already blocked in a take() or poll() call, then it will be unblocked and if it isn't yet blocked on those calls then a subsequent call will find these markers (which are distinct for close and cancel).
Adding these (distinct) markers will also allow for an ordered identifiable content in the queue - some replies followed by close/cancel marker or a close/cancel marker followed by replies.
Additionally, the addRequest(), close() and cancel() methods of this LdapRequet get called by a single thread managed by the Connection class, so there isn't expected to be concurrent calls across these methods. So, I think, removing the lock and letting the (distinct) markers end up in the queue makes this code in the LdapRequest simpler.
The getReplyBer() the implementation polls the ordered queue, so it find all replies that have arrived before the cancel/close marker is encountered. Once it encounters those markers it can then throw the relevant exception as per its current specified behaviour.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, the addRequest(), close() and cancel() methods of this LdapRequet get called by a single thread managed by the Connection class, so there isn't expected to be concurrent calls across these methods.
I am not seeing that - close() is called by Connection.cleanup() which seems to be callable asynchronously.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to reply here, but yes Daniel is right that the close() method can be called concurrently. With the current proposed change, it should be OK to have close() be invoked concurrently. The close()/cancel() invocation will place the respective marker in the queue and will also mark the close/cancel flag. We intentionally place the close/cancel markers in the queue so that the getReplyBer() will find that marker in the right order, when it is next invoked or if it is currently blocked waiting for the next message.
Given the current expected semantics of getReplyBer(), we skip adding any new replies after the close/cancel markers have been placed in the queue. But that's on a best effort basis. Due to a race, if we do add replies to the queue after the close/cancel has been invoked, then it's OK because the getReplyBer() upon noticing the close/cancel markers first, will not process the subsequent replies in the queue. Thus it retains the current behaviour of not processing any replies after close/cancel has been noticed.
|
@jaikiran This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply issue a |
|
/keepalive |
|
@AlekseiEfimov The pull request is being re-evaluated and the inactivity timeout has been reset. |
| : replies.take(); | ||
| // poll from 'replies' blocking queue ended-up with timeout | ||
| if (result == null) { | ||
| throw new IOException(String.format(TIMEOUT_MSG_FMT, millis)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| throw new IOException(String.format(TIMEOUT_MSG_FMT, millis)); | |
| throw new IOException("LDAP response read timed out, timeout used: %d ms.".formatted(millis)); |
TIMEOUT_MSG_FMT is only used once here, and defining a constant would make the code less readable.
| // Unexpected EOF can be caused by connection closure or cancellation | ||
| if (result == EOF) { | ||
| if (result == CANCELLED_MARKER) { | ||
| throw new CommunicationException("Request: " + msgId + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current LdapRequest class should use a unified style to construct exception information, either using string concatenation or String.format. A class should not mix the two styles.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello Shaojin, these are pre-existing messages. I have however updated the PR to use a uniform style.
| final int numTasks = 10; | ||
| try (final ExecutorService executor = Executors.newCachedThreadPool()) { | ||
| for (int i = 1; i <= numTasks; i++) { | ||
| final String taskName = "task-" + i; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| final String taskName = "task-" + i; | |
| String taskName = "task-" + i; |
Variables used only in the current block do not need to be final
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This a more a personal preference and since this is a new test file, I let it stay in this form. If there's a strong preference to remove it, I'll do so.
| ber.parseSeq(null); | ||
| ber.parseInt(); | ||
| completed = (ber.peekByte() == LdapClient.LDAP_REP_RESULT); | ||
| isLdapResResult = (ber.peekByte() == LdapClient.LDAP_REP_RESULT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other boolean type variables in the LdapRequest class do not have the is prefix. The local variable isLdapResResult here should also use the same style. We should not use two styles in one class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling it ldapResResult instead of isLdapResResult isn't appropriate here, so I've let this stay in its current form.
|
@jaikiran This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply issue a |
| * @modules java.naming/com.sun.jndi.ldap | ||
| * @library /test/lib | ||
| * @build jdk.test.lib.net.URIBuilder | ||
| * @run junit LdapClientConnTest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this test creates a daemon thread and does not try to join the thread at the end it might be more prudent to run it in /othervm mode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello Daniel, good catch. It took me a while to understand this - leaving around arbitrary test specific threads in an agent VM isn't wise. You are right, making it othervm would be better. I've updated the PR to do so.
dfuch
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
Hello Jaikiran, The fix and its logic looks correct to me. I have a couple of suggestions regarding the new test:
|
|
/contributor @AlekseiEfimov |
|
@jaikiran Syntax:
User names can only be used for users in the census associated with this repository. For other contributors you need to supply the full name and email address. |
|
Hello Aleksei, I have updated the PR to implement your suggestion 1 and 2. As for the other suggestion of moving these constants to the so that then forces me to reintroduce the: in this new test, which I think defeats the entire cleanup. So I decided to leave those constants this in this test class for now and reconsider that move as a future work. Would that be OK? |
|
/contributor add @AlekseiEfimov |
|
@jaikiran |
Oh, it is complicated, thank you for trying to move the constants. |
AlekseiEfimov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
|
Thank you everyone for the reviews. I've triggered a CI run with these latest changes and when that completes I'll go ahead and integrate this. |
|
/integrate |
|
Going to push as commit e2eaa2e.
Your commit was automatically rebased without conflicts. |
Can I please get a review of this change which proposes to address the issue noted in https://bugs.openjdk.org/browse/JDK-8357708?
As noted in the issue, the current code in
com.sun.jndi.ldap.Connection.readReply()is susceptible to throwing aServiceUnavailableExceptioneven when the LDAP replies have already been received and queued for processing. The JBS issue has additional details about how that can happen.The commit in this PR simplifies the code in
com.sun.jndi.ldap.LdapRequestto make sure it always gives out the replies that have been queued when theLdapRequest.getReplyBer()gets invoked. One of those queued values could be markers for a cancelled or closed request. In that case, thegetReplyBer(), like previously, continues to throw the right exception. With this change, the call toreplies.take()orreplies.poll()(with an infinite timeout) is no longer expected to hang forever, if theConnectionis closed (or the request cancelled). This then allows us to remove the connection closure (sock == null) check inConnection.readReply().A new jtreg test has been introduced to reproduce this issue and verify the fix. The test reproduces this issue consistently when the source fix isn't present. With the fix present, even after several thousand runs of this test, the issue no longer reproduces.
tier1, tier2 and tier3 tests continue to pass with this change. I've marked the fix version of this issue for 26 and I don't plan to push this for 25.
Progress
Issue
Reviewers
Contributors
<aefimov@openjdk.org>Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/25449/head:pull/25449$ git checkout pull/25449Update a local copy of the PR:
$ git checkout pull/25449$ git pull https://git.openjdk.org/jdk.git pull/25449/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 25449View PR using the GUI difftool:
$ git pr show -t 25449Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/25449.diff
Using Webrev
Link to Webrev Comment