4

I have the following example code which starts built-in Java http server, issues a request to it using built-in Java http client and then tries to stop a server.

The issue is: stop(delay) method blocks for exactly delay seconds, no matter what delay.

I expected for this method to process all incoming requests and shut down the server. So in this case I expected for this method to shut down immediately, as there are no incoming requests.

I can use stop(0), however it does not seem ideal for production server, as it won't give a chance for current requests to complete.

package test;

import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.LocalDateTime;

public class Test {
  public static void main(String[] args) throws IOException, InterruptedException {
    var httpServer = HttpServer.create();
    httpServer.createContext("/", exchange -> {
      exchange.sendResponseHeaders(200, -1);
      exchange.close();
    });
    httpServer.bind(new InetSocketAddress("127.0.0.1", 0), 0);
    httpServer.start();

    var httpClient = HttpClient.newHttpClient();

    var host = httpServer.getAddress().getAddress().getHostAddress();
    var port = httpServer.getAddress().getPort();

    var httpRequest = HttpRequest.newBuilder()
        .uri(URI.create("http://" + host + ":" + port + "/"))
        .GET()
        .build();

    var httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray());

    System.out.println(httpResponse.statusCode());

    System.out.println(LocalDateTime.now() + " Stopping server");
    httpServer.stop(10);
    System.out.println(LocalDateTime.now() + " Server stopped");
  }
}
200
2023-07-29T18:37:25.492361 Stopping server
2023-07-29T18:37:35.668797 Server stopped

Here's javadoc for stop method:

    /**
     * Stops this server by closing the listening socket and disallowing
     * any new exchanges from being processed. The method will then block
     * until all current exchange handlers have completed or else when
     * approximately <i>delay</i> seconds have elapsed (whichever happens
     * sooner). Then, all open TCP connections are closed, the background
     * thread created by {@link #start()} exits, and the method returns.
     * Once stopped, a {@code HttpServer} cannot be re-used.
     *
     * @param delay the maximum time in seconds to wait until exchanges have finished
     * @throws IllegalArgumentException if delay is less than zero
     */

It is my understanding that stop(10) method is not supposed to wait 10 seconds if there're no requests currently executing. This timeout is to give currently executing requests time to finish before closing the sockets.

3
  • Not sure I understand the issue. What did you expect stop(10);; to do if not wait 10 seconds? Does the Javadoc say it waits for requests to finish? What requests are actually being made? You've one sent and finished one request here Commented Jul 29, 2023 at 12:47
  • @OneCricketeer I added javadoc citation to the question. I don't make any other requests, only this single request that I specified in code is being made. Commented Jul 29, 2023 at 13:04
  • 1
    I suppose you can argue that "until all current exchange handlers have completed" doesn't happen if there are zero current exchange handlers, and so there is no bug, but that seems against the spirit of the method. Commented Jul 29, 2023 at 13:28

1 Answer 1

3

The Javadoc for stop says:

The method will then block until all current exchange handlers have completed or else when approximately delay seconds have elapsed (whichever happens sooner).

This looks like a bug to me, as ServerImpl.stop() does:

        terminating = true;
        try { schan.close(); } catch (IOException e) {}
        selector.wakeup();
        long latest = System.currentTimeMillis() + delay * 1000;
        while (System.currentTimeMillis() < latest) {
            delay();
            if (finished) {
                break;
            }
        }

So it will only return early if finished is true.

The finished field can only be set to true in ServerImpl.Dispatcher.handleEvent:

                if (r instanceof WriteFinishedEvent) {

                    logger.log(Level.TRACE, "Write Finished");
                    int exchanges = endExchange();
                    if (terminating && exchanges == 0) {
                        finished = true;
                    }

But terminating will be false, unless a WriteFinishedEvent occurs after a call to stop.

We can change the program slightly to get the expected behaviour by ensuring that the request is issued before we call stop() but completes after we call stop():

import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;

public class Test {
    public static void main(String[] args) throws IOException, InterruptedException {
        var httpServer = HttpServer.create();
        httpServer.createContext("/", exchange -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            exchange.sendResponseHeaders(200, -1);
            System.out.println("Did request");
            exchange.close();
        });
        httpServer.bind(new InetSocketAddress("127.0.0.1", 0), 0);
        httpServer.start();
        Executors.newSingleThreadExecutor().submit(() -> {
            var httpClient = HttpClient.newHttpClient();

            var host = httpServer.getAddress().getAddress().getHostAddress();
            var port = httpServer.getAddress().getPort();

            var httpRequest = HttpRequest.newBuilder()
                    .uri(URI.create("http://" + host + ":" + port + "/"))
                    .GET()
                    .build();
            HttpResponse<byte[]> httpResponse = null;
            try {
                httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray());
            } catch (IOException e) {
                throw new RuntimeException(e);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(httpResponse.statusCode());
        });
        Thread.sleep(500);
        System.out.println(LocalDateTime.now() + " Stopping server");
        httpServer.stop(10);
        System.out.println(LocalDateTime.now() + " Server stopped");
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, it seems to be this bug: bugs.openjdk.org/browse/JDK-8304065

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.