8304065: HttpServer.stop should terminate immediately if no exchanges are in progress#25333
8304065: HttpServer.stop should terminate immediately if no exchanges are in progress#25333myankelev wants to merge 7 commits intoopenjdk:masterfrom
Conversation
…nges are in progress HttpServer::stop will terminate the server immidiately after all exhcnages are complete. If the exchanges take longer then the specified delay it will terminate straight after the delay. Used to wait until the delay is complete at all times, regardless of the number of active exchanges.
|
👋 Welcome back myankelevich! A progress list of the required criteria for merging this PR into |
|
@myankelev 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 256 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. As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@dfuch, @Michael-Mc-Mahon) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
|
/contributor add @eirbjo |
|
@myankelev |
|
@myankelev The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
dfuch
left a comment
There was a problem hiding this comment.
Some early comments. I will review the test next.
src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
Outdated
Show resolved
Hide resolved
src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
Outdated
Show resolved
Hide resolved
src/jdk.httpserver/share/classes/sun/net/httpserver/StopRequestedEvent.java
Outdated
Show resolved
Hide resolved
src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
Outdated
Show resolved
Hide resolved
| final Duration delayDuration = Duration.ofSeconds(1); | ||
| log("Shutting down the server the first time"); | ||
| timeShutdown(delayDuration); | ||
| log("Shutting down the server the second time"); | ||
| timeShutdown(delayDuration); |
There was a problem hiding this comment.
Should we check the time it took the server to shutdown here too?
There was a problem hiding this comment.
This test just validates, that there is no error thrown. The other tests cover the timing scenarios
|
Looking at Dispatcher::run - there are a couple of additional places where we should take into account the |
src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
Outdated
Show resolved
Hide resolved
src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
Outdated
Show resolved
Hide resolved
|
@myankelev Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. See OpenJDK Developers’ Guide for more information. |
src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
Outdated
Show resolved
Hide resolved
src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
Outdated
Show resolved
Hide resolved
src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
Outdated
Show resolved
Hide resolved
Co-authored-by: Daniel Fuchs <67001856+dfuch@users.noreply.github.com>
|
If I have read the original code correctly there is a flaw in it, that is the startExchange is never called. 391 int exchanges = endExchange(); Exchanges will always be negative. This is inherited in the new update WRT current PR, I don't think it is good that the state of the server is encapsulated in the state of the CountDownLatch. I would make stop synchronised to have only one thread executing at time. First check on entry then becomes test on the server's current state. This means that this state management needs some attention. The addition of Stop event is a neat idea, but draws attention to the semantics of the stop. What is the desired semantics of stop, to not accept any new connections, i.e. stop the listener immediately, not accept new requests on existing connections. To allow existing Exchanges to complete and then shutdown all extant connections. |
| @@ -223,44 +225,69 @@ public HttpsConfigurator getHttpsConfigurator () { | |||
| } | |||
|
|
|||
| public final boolean isFinishing() { | |||
There was a problem hiding this comment.
encapsulating the server state in a CountDownLatch is obfuscated semantics
There was a problem hiding this comment.
Yes, but this is an existing function and it is used in other classes. I didn't change the functionality
There was a problem hiding this comment.
the internal state representation of the server could do with a rethink and overhaul, and representing part of that state in a synchronisation primitive obscures/obfuscates the server state's semantics. I don't think it's "clean code"
There was a problem hiding this comment.
I agree that the semantic of finished finishing is a bit obfuscated. On the other hand I don't think we should go so far as overhauling / rethinking the states in this PR. If you recall there have been lots of fixes in this area (we had some assert firing at some point, and it took some time to find the root cause and fix).
That said having both a boolean and a countdown latch would be duplicate info, and potentially harder to manage consistency.
If we don't want the countdown latch we might go back to looping with Thread.sleep in stop, or use wait/notify, and I am not sure it would be much better.
There was a problem hiding this comment.
I have a suggestion. Let's do this:
public final boolean finished() {
// we're finished when the latch reaches 0
return finishedLatch.getCount() == 0;
}
public final boolean isFinishing() {
return finished();
}
Then replace things like if (finished ... with if (finished() ...
That should minimize the changes and make them look less awkward.
| * @param delay maximum delay to wait for exchanges completion, in seconds | ||
| */ | ||
| public void stop (int delay) { | ||
| if (delay < 0) { |
There was a problem hiding this comment.
with the use of CountDownLatch the negative check becomes superfluous i.e.
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
Causes the current thread to wait until the latch has counted down to zero, unless the thread is interrupted, or the specified waiting time elapses.
. . .
If the specified waiting time elapses then the value false is returned. If the time is less than or equal to zero, the method will not wait at all.
There was a problem hiding this comment.
It's not something we should change though, as it's part of the API contract in HttpServer::stop:
https://docs.oracle.com/en/java/javase/24/docs/api/jdk.httpserver/com/sun/net/httpserver/HttpServer.html#stop(int)
| if (r instanceof WriteFinishedEvent) { | ||
|
|
||
| logger.log(Level.TRACE, "Write Finished"); | ||
| int exchanges = endExchange(); |
There was a problem hiding this comment.
startExchange is never called => endExchange can return negative ?
There was a problem hiding this comment.
I hope that this is a scenario that cannot happen: to end an exchange you would have to start it first.
| * complete within the imparted delay. | ||
| * | ||
| * @param delay maximum delay to wait for exchanges completion, in seconds | ||
| */ |
There was a problem hiding this comment.
make synchronised to allow only one thread to execute stop at any one time
There was a problem hiding this comment.
Not sure that's the best solution. But if the terminating flag is already set then it means that another thread has initiated stop, and maybe in this case we should just jump to waiting on the latch - no need to post the event again or close the channel and wakeup the selector again. The part in stop that need synchronization already have it, so I don't think declaring stop as synchronized would be such a good idea. In particular it would prevent other threads to call getExchange() / endExchange() so I would be afraid it would prevent active exchanges from proceeding - unless we use wait/notify instead of a countdown latch - but that might be even more difficult to get right.
@msheppar what do you mean by that? It's called in ExchangeImpl.java |
Well then I haven't read it correctly :-( needs a deeper dive, I didn't examine the Exchange::run method in sufficient detail ... thanks for the correction .... BUT in my defence I see Dispatcher invoke handle, handle creates an Exchange, which is dispatched to an Executor and I would have expected that the run method of Exchange to call startExchange, as the log message says an exchange has started !! |
Michael-Mc-Mahon
left a comment
There was a problem hiding this comment.
I wonder could the two Event types be consolidated inside Event.java? It seems overkill to have them in three separate source files now.
Do you mean refactoring WriteFinishEvent and StopRequestedEvent as inner classes of Event.java? |
Potentially, but I thought the simplest approach would be as top-level package private classes in Event.java |
| protected Event (ExchangeImpl t) { | ||
| this.exchange = t; | ||
| } | ||
|
|
There was a problem hiding this comment.
I don't see the purpose of this change and the value it brings other than collation and reducing explicit files for the derived Events ?
Could you elaborate the rationale please?
I do see a code smell in WriteFinished directly access the state of ExchangeImpl
startExchange() is called in the constructor for ExchangeImpl, which is not changing in this PR. |
src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthOutputStream.java
Outdated
Show resolved
Hide resolved
src/jdk.httpserver/share/classes/sun/net/httpserver/UndefLengthOutputStream.java
Outdated
Show resolved
Hide resolved
|
@dfuch, @msheppar, @djelinski , @Michael-Mc-Mahon, thank you for your reviews! |
|
/integrate |
|
@myankelev |
|
/sponsor |
|
Going to push as commit 627ef34.
Your commit was automatically rebased without conflicts. |
|
@Michael-Mc-Mahon @myankelev Pushed as commit 627ef34. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
HttpServer::stop will terminate the server immidiately after all exhcnages are complete.
If the exchanges take longer then the specified delay it will terminate straight after the delay, the same as the previous behaviour.
Used to wait until the delay is complete at all times, regardless of the number of active exchanges.
Tests based on @eirbjo work, so adding Eirik as a contributor.
Progress
Issue
Reviewers
Contributors
<eirbjo@openjdk.org>Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/25333/head:pull/25333$ git checkout pull/25333Update a local copy of the PR:
$ git checkout pull/25333$ git pull https://git.openjdk.org/jdk.git pull/25333/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 25333View PR using the GUI difftool:
$ git pr show -t 25333Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/25333.diff
Using Webrev
Link to Webrev Comment