RFR: 8377302: HttpServer::stop uses full timeout duration if handler throws

Volkan Yazici vyazici at openjdk.org
Mon Feb 9 21:27:19 UTC 2026


On Fri, 6 Feb 2026 13:54:06 GMT, Daniel Fuchs <dfuchs at openjdk.org> wrote:

> When a HttpHandler::handle method throws an unexpected exception, the HttpServer rightfully closes the associated connection. However, the exchange is still discounted as pending, which causes HttpServer::stop to wait for the full timeout duration, even though all connections have been closed.

Marked as reviewed by vyazici (Committer).

src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java line 91:

> 89: 
> 90:     final AtomicBoolean ended = new AtomicBoolean();
> 91:     final AtomicBoolean finished = new AtomicBoolean();

*Nit:* I'd suggest preferring the minimum visibility, that is,

- `ended` and `finished` can be `private`
- `endExchange`, `postWriteFinished`, and `postExchangeFinished` can be package private

src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java line 440:

> 438: 
> 439:             try {
> 440:                 if (r instanceof Event.WriteFinished || r instanceof Event.ExchangeFinished) {

This line is the only place in the code where `WriteFinished` and `ExchangeFinished` are read. Instead of creating two separate events with a big overlap, have you considered changing `WriteFinished` as follows:

    static final class ExchangeFinished extends Event {
        ExchangeFinished(ExchangeImpl t, boolean writeFinished) {
            super(Objects.requireNonNull(t));
            if (writeFinished) {
                assert !t.writefinished;
                t.writefinished = true;
            }
        }
    }

src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java line 442:

> 440:                 if (r instanceof Event.WriteFinished || r instanceof Event.ExchangeFinished) {
> 441: 
> 442:                     logger.log(Level.TRACE, "Write Finished");

This log information is not accurate anymore — event can be `ExchangeFinished` too. I see a similar log line a couple of lines above right after the `r instanceof Event.StopRequested` check. Shall we instead remove both and add a `logger.log(Level.TRACE, "Handling {}", r.getClass().getSimpleName())` line at the beginning of the `handleEvent(Event)` method?

src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java line 905:

> 903:                         uc.doFilter(new HttpExchangeImpl(tx));
> 904:                     }
> 905:                 } catch (Throwable t) {

1. I'm in favor of catching `Exception` instead of `Throwable` here, since the difference between the two are mostly irrecoverable `Error`s.
2. A couple of lines below, we have a `catch (Exception e)` block containing `closeConnection()`. Why can't we have `tx.postExchangeFinished()` there and remove the need for this new `try/catch`?

test/jdk/com/sun/net/httpserver/FailAndStopTest.java line 30:

> 28:  * @modules jdk.httpserver java.logging
> 29:  * @library /test/lib
> 30:  * @run main/othervm FailAndStopTest

A nice JTreg best practice:

Suggestion:

 * @run main/othervm ${test.main.class}

test/jdk/com/sun/net/httpserver/FailAndStopTest.java line 84:

> 82:                 default -> shouldFail && !"HEAD".equals(method);
> 83:             };
> 84:         }

We have a `shouldFail` flag, but we're introducing further logic to it in `shouldFail()` method. I _personally_ find it difficult to wrap my mind around it. Can we instead change the type of `shouldFail` from `boolean` to `Predicate<String>`? Or simplify this design some other way?

test/jdk/com/sun/net/httpserver/FailAndStopTest.java line 160:

> 158:     public static void main(String[] args) throws Exception {
> 159:         LOGGER.setLevel(Level.ALL);
> 160:         Logger.getLogger("").getHandlers()[0].setLevel(Level.ALL);

1. The `LOGGER` class constant is only used here. Can we move it here as a local variable?
2. Would you mind documenting these two lines for mere mortals who did not author JUL, please? 😅

test/jdk/com/sun/net/httpserver/FailAndStopTest.java line 165:

> 163:             test(test, Optional.empty());
> 164:         }
> 165:         // test with HEAD

What is the reason we repeat the test twice, once for `GET`, and once for `HEAD`?

test/jdk/com/sun/net/httpserver/FailAndStopTest.java line 175:

> 173:                 new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
> 174: 
> 175:         System.out.println("Test: " + method.orElse("GET") + " " + test.query);

AFAICT, there are time-sensitive asynchronous processing involved in the test. You might consider replacing `println("foo")` lines with `println(now() + " foo")` to help with troubleshooting. `now` can be statically imported from `java.util.time.Instant`, or you can roll-out your own `log(String format, Object... args)` method doing that.

test/jdk/com/sun/net/httpserver/FailAndStopTest.java line 178:

> 176:         System.out.println("Server listening at: " + server.getAddress());
> 177:         try {
> 178:             server.createContext("/FailAndStopTest/", new FailAndStopTest());

*Nit:* Rename resiliency?

Suggestion:

            server.createContext('/' + FailAndStopTest.class.getSimpleName() + '/', new FailAndStopTest());


Note that `.path("/FailAndStopTest/")` line below needs to be adapted too.

test/jdk/com/sun/net/httpserver/FailAndStopTest.java line 182:

> 180: 
> 181:             URL url = URIBuilder.newBuilder()
> 182:                     .scheme("http")

Would testing `HttpsServer` (i.e., the `https` scheme) be useful too?

test/jdk/com/sun/net/httpserver/FailAndStopTest.java line 200:

> 198:                             .formatted(test.query));
> 199:                 }
> 200:                 System.out.println("Client: read body: \"%s\"".formatted(body));

You might want to simplify `println("...".formatted(...))` with `printf`, in particular, if this needs to be backported.

-------------

PR Review: https://git.openjdk.org/jdk/pull/29606#pullrequestreview-3774798828
PR Review Comment: https://git.openjdk.org/jdk/pull/29606#discussion_r2781308125
PR Review Comment: https://git.openjdk.org/jdk/pull/29606#discussion_r2781366227
PR Review Comment: https://git.openjdk.org/jdk/pull/29606#discussion_r2781328367
PR Review Comment: https://git.openjdk.org/jdk/pull/29606#discussion_r2781402147
PR Review Comment: https://git.openjdk.org/jdk/pull/29606#discussion_r2784018143
PR Review Comment: https://git.openjdk.org/jdk/pull/29606#discussion_r2781608044
PR Review Comment: https://git.openjdk.org/jdk/pull/29606#discussion_r2781529825
PR Review Comment: https://git.openjdk.org/jdk/pull/29606#discussion_r2781623523
PR Review Comment: https://git.openjdk.org/jdk/pull/29606#discussion_r2781568626
PR Review Comment: https://git.openjdk.org/jdk/pull/29606#discussion_r2781557562
PR Review Comment: https://git.openjdk.org/jdk/pull/29606#discussion_r2781574117
PR Review Comment: https://git.openjdk.org/jdk/pull/29606#discussion_r2781584621


More information about the net-dev mailing list