JDK-8251312 : HttpClient send throws InterruptedException when interrupted but does not cancel request
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.net
  • Priority: P3
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 16
  • Submitted: 2020-08-07
  • Updated: 2020-08-24
  • Resolved: 2020-08-24
Related Reports
CSR :  
Description
Summary
-------

There is no easy way to cancel a request sent through the `java.net.http.HttpClient`. This change proposes to handle 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.

Problem
-------

There is no easy way to cancel a request sent through the `java.net.http.HttpClient`. A natural way may seem to call `Thread.interrupt()` on the thread waiting for the response, or to cancel the `CompletableFuture` returned by the `HttpClient`, but although both will cause an exception to be thrown, the actual request will typically continue in the background as if nothing had happened. This is typically an issue when using virtual threads, as  `HttpClient::send` will throw an `InterruptedException` when interrupted but will not cancel the request. 

Solution
--------

This change proposes to handle 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.

Specification
-------------

The concepts of default `HttpClient` implementation and *cancelable* futures are introduced.

src/java.net.http/share/classes/java/net/http/HttpClient.java

A clarification is added to the class level API documentation:

      *
      * <p> An {@code HttpClient} can be used to send {@linkplain HttpRequest
      * requests} and retrieve their {@linkplain HttpResponse responses}. An {@code
    - * HttpClient} is created through a {@link HttpClient#newBuilder() builder}. The
    - * builder can be used to configure per-client state, like: the preferred
    + * HttpClient} is created through a {@link HttpClient.Builder builder}.
    + * The {@link #newBuilder() newBuilder} method returns a builder that creates
    + * instances of the default {@code HttpClient} implementation.
    + * The builder can be used to configure per-client state, like: the preferred
      * protocol version ( HTTP/1.1 or HTTP/2 ), whether to follow redirects, a
      * proxy, an authenticator, etc. Once built, an {@code HttpClient} is immutable,
      * and can be used to send multiple requests.

A paragraph is added to the API documentation of the `newBuilder` method:

         /**
          * Creates a new {@code HttpClient} builder.
          *
    +     * <p> Builders returned by this method create instances
    +     * of the default {@code HttpClient} implementation.
    +     *
          * @return an {@code HttpClient.Builder}
          */
         public static Builder newBuilder() {

A paragraph is added to the API documentation of the `HttpClient::send` method:

    +     * <p> If the operation is interrupted, the default {@code HttpClient}
    +     * implementation attempts to cancel the HTTP exchange and
    +     * {@link InterruptedException} is thrown.
    +     * No guarantee is made as to exactly <em>when</em> the cancellation request
    +     * may be taken into account. In particular, the request might still get sent
    +     * to the server, as its processing might already have started asynchronously
    +     * in another thread, and the underlying resources may only be released
    +     * asynchronously.
    +     * <ul>
    +     *     <li>With HTTP/1.1, an attempt to cancel may cause the underlying
    +     *         connection to be closed abruptly.
    +     *     <li>With HTTP/2, an attempt to cancel may cause the stream to be reset,
    +     *         or in certain circumstances, may also cause the connection to be
    +     *         closed abruptly, if, for instance, the thread is currently trying
    +     *         to write to the underlying socket.
    +     * </ul>
    +     *

A paragraph is added to the API documentation of the three args `HttpClient::sendAsync` method:

    +     * <p> The default {@code HttpClient} implementation returns
    +     * {@code CompletableFuture} objects that are <em>cancelable</em>.
    +     * {@code CompletableFuture} objects {@linkplain CompletableFuture#newIncompleteFuture()
    +     * derived} from cancelable futures are themselves <em>cancelable</em>.
    +     * Invoking {@linkplain CompletableFuture#cancel(boolean) 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.
    +     * No guarantee is made as to exactly <em>when</em> the cancellation request
    +     * may be taken into account. In particular, the request might still get sent
    +     * to the server, as its processing might already have started asynchronously
    +     * in another thread, and the underlying resources may only be released
    +     * asynchronously.
    +     * <ul>
    +     *     <li>With HTTP/1.1, an attempt to cancel may cause the underlying connection
    +     *         to be closed abruptly.
    +     *     <li>With HTTP/2, an attempt to cancel may cause the stream to be reset.
    +     * </ul>
    +     *



Comments
Moving to Approved.
24-08-2020

The full webrev can be seen here: [http://cr.openjdk.java.net/~dfuchs/webrev_8245462/webrev.03/][1] The generated documentation can be seen here [http://cr.openjdk.java.net/~dfuchs/webrev_8245462/docs.03/java.net.http/java/net/http/HttpClient.html][2] [1]: http://cr.openjdk.java.net/~dfuchs/webrev_8245462/webrev.03/ [2]: http://cr.openjdk.java.net/~dfuchs/webrev_8245462/docs.03/java.net.http/java/net/http/HttpClient.html
14-08-2020