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.
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");
}
}