JDK-8245462 : HttpClient send throws InterruptedException when interrupted but does not cancel request
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.net
  • Affected Version: 15
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2020-05-20
  • Updated: 2020-10-14
  • Resolved: 2020-08-28
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 16
16 b14Fixed
Related Reports
CSR :  
Relates :  
Sub Tasks
JDK-8252515 :  
Description
If HttpClient is invoked with the interrupt status set, or a thread is interrupted while blocked in send, then InterruptedException will be thrown but the HTTP request continues in the background. Consider this simple example:

Thread.currentThread().interrupt();
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(uri)build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());

InterruptedException will be thrown but if you monitor with strace or tcpdump then you'll see that the HTTP transaction continues in the background. The user of the API in the synchronous case does not have a handle to anything that can be used to cancel the operation.



Comments
URL: https://hg.openjdk.java.net/jdk/jdk/rev/1a68faff0ffa User: dfuchs Date: 2020-08-28 09:51:52 +0000
28-08-2020

The proposed fix handles thread interruption (when in synchronous mode) and CompletableFuture.cancel(true) (when in asynchronous mode) to release resources associated with an inflight request when the caller is no longer interested in the response. Because HttpClient::send calls cf.cancel(true) when interrupted then both cases (synchronous and asynchronous) are solved by cancelling the operation through overriding CompletableFuture::cancel. http://cr.openjdk.java.net/~dfuchs/webrev_8245462/webrev.03/ The concepts of *default HttpClient implementation* and *cancelable* futures are introduced: - The default HttpClient implementation returns CompletableFuture objects that are *cancelable*. - CompletableFuture objects derived from cancelable futures are themselves *cancelable*. - Invoking cancel(true) on a cancelable future that is not completed, attempts to cancel the HTTP exchange in an effort to release underlying resources as soon as possible. See CSR: JDK-8251312
28-08-2020

The synchronized case is an interesting use case where the HttpClientImpl could probably do better, as it calls cf.get(). The current implementation doesn't provide any out-of-the-box API to cancel a request. The only way to cancel an HTTP request is by cancelling the Flow.Subscription object that is passed to the response's BodySubscriber. It should be relatively easy for a user of the API to trivially wrap one of the provided BodyHandler/BodySubscriber implementations in order to get hold to the subscription object. There is unfortunately no relationship between the cancel method of the CompletableFuture returned by the client, and the cancel method of the Flow.Subscription passed to the BodySubscriber. It would be nice if the two could be linked - but it might not even be possible. Cancelling the subscription will work both with the synchronous (HttpClient::send) and asynchronous (HttpClient::sendAsync) methods. It will have different effects however depending on whether the request was sent through HTTP/1.1 or HTTP/2.0 (with HTTP/1.1 it will cause the connection to be closed, with HTTP/2.0 it will cause the stream to be reset). And of course it might have no effect at all if the last byte of the response was already delivered to the BodySubscriber. In addition - this doesn't let you cancel the request before the first response byte is received.
20-05-2020