Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8328100

Improve HttpClient documentation with regard to reclaiming resources

XMLWordPrintable

    • Icon: CSR CSR
    • Resolution: Approved
    • Icon: P3 P3
    • 23
    • core-libs
    • None
    • behavioral
    • minimal
    • This is a documentation only change
    • Java API
    • JDK

      Summary

      The API documentation of java.net.http.HttpClient, java.net.http.HttpResponse.BodySubscribers, and java.net.http.HttpResponse.BodyHandlers is improved to better explain how resources allocated by the HttpClient are reclaimed, and what could prevent to reclaim them.

      Problem

      The java.net.http.HttpClient implements AutoCloseable, but the API is silent about what happens if an instance becomes unreachable without being closed. Some implementations of BodySubscriber and BodyHandler return streaming or publishing bodies, which needs to be properly closed or cancelled in order for resources to be reclaimed. The API documentation is missing some apiNotes or links to make this more visible.

      Solution

      Extend the @implNote in the java.net.http.HttpClient class-level API documentation to better explain how and when resources are reclaimed. Add or extend @apiNotes in BodyHandlers and BodySubscribers to make it more clear which body implementation need to be explicitly closed or cancelled.

      Specification

      A diff will be pasted here when the PR has been reviewed. In the mean time refer to the proposed changes in the PR at https://github.com/openjdk/jdk/pull/18270/files

      See specdiff in the attached specdiff-8327991.01.zip file if more convenient for review. Diff also pasted here:

      --- a/src/java.net.http/share/classes/java/net/http/HttpClient.java
      +++ b/src/java.net.http/share/classes/java/net/http/HttpClient.java
      @@ -132,21 +134,47 @@
        * reclaimed early by {@linkplain #close() closing} the client.
        *
        * @implNote
      - *  <p id="closing">
      - *  The JDK built-in implementation of the {@code HttpClient} overrides
      + *  <p id="streaming">
      + *  The {@link BodyHandlers} and {@link BodySubscribers}
      + *  classes provide some {@linkplain BodySubscribers##streaming-body streaming
      + *  or publishing {@code BodyHandler} and {@code BodySubscriber}
      + *  implementations} which allow to stream body data back to the caller.
      + *  In order for the resources associated with these streams to be
      + *  reclaimed, and for the HTTP request to be considered completed,
      + *  a caller must eventually {@linkplain  HttpResponse#body()
      + *  obtain the streaming response body} and close, cancel, or
      + *  read the returned streams to exhaustion. Likewise, a custom
      + *  {@link BodySubscriber} implementation should either {@linkplain
      + *  Subscription#request(long) request} all data until {@link
      + *  BodySubscriber#onComplete() onComplete} or {@link
      + *  BodySubscriber#onError(Throwable) onError} is signalled, or eventually
      + *  {@linkplain Subscription#cancel() cancel} its subscription.
      + *
      + * <p id="closing">
      + * The JDK built-in implementation of the {@code HttpClient} overrides
        * {@link #close()}, {@link #shutdown()}, {@link #shutdownNow()},
        * {@link #awaitTermination(Duration)}, and {@link #isTerminated()} to
        * provide a best effort implementation. Failing to close, cancel, or
      - * read returned streams to exhaustion, such as streams provided when using
      - * {@link BodyHandlers#ofInputStream()}, {@link BodyHandlers#ofLines()}, or
      - * {@link BodyHandlers#ofPublisher()}, may prevent requests submitted
      - * before an {@linkplain #shutdown() orderly shutdown}
      - * to run to completion. Likewise, failing to
      - * {@linkplain Subscription#request(long) request data} or {@linkplain
      - * Subscription#cancel() cancel subscriptions} from a custom {@linkplain
      - * java.net.http.HttpResponse.BodySubscriber BodySubscriber} may stop
      - * delivery of data and {@linkplain #awaitTermination(Duration) stall an
      - * orderly shutdown}.
      + * read {@link ##streaming streaming or publishing bodies} to exhaustion
      + * may stop delivery of data while leaving the request open, and
      + * {@linkplain #awaitTermination(Duration) stall an
      + * orderly shutdown}. The {@link #shutdownNow()} method, if called, will
      + * attempt to cancel any such non-completed requests, but may cause
      + * abrupt termination of any on going operation.
      + *
      + * <p id="gc">
      + * If not {@linkplain ##closing explicitly closed}, the JDK
      + * built-in implementation of the {@code HttpClient} releases
      + * its resources when an {@code HttpClient} instance is no longer
      + * strongly reachable, and all operations started on that instance have
      + * eventually completed. This relies both on the garbage collector
      + * to notice that the instance is no longer reachable, and on all
      + * requests started on the client to eventually complete. Failure
      + * to properly close {@linkplain ##streaming streaming or publishing bodies}
      + * may prevent the associated requests from running to completion, and
      + * prevent the resources allocated by the associated client from
      + * being reclaimed by the garbage collector.
      + *
        *
        * <p>
        * If an explicit {@linkplain HttpClient.Builder#executor(Executor)
      @@ -155,7 +183,7 @@
        * dependent tasks in a context that is granted no permissions. Custom
        * {@linkplain HttpRequest.BodyPublisher request body publishers}, {@linkplain
        * HttpResponse.BodyHandler response body handlers}, {@linkplain
      - * HttpResponse.BodySubscriber response body subscribers}, and {@linkplain
      + * BodySubscriber response body subscribers}, and {@linkplain
        * WebSocket.Listener WebSocket Listeners}, if executing operations that require
        * privileges, should do so within an appropriate {@linkplain
        * AccessController#doPrivileged(PrivilegedAction) privileged context}.
      @@ -686,7 +714,7 @@ public enum Redirect {
            *          information.</li>
            * </ul>
            *
      -     * <p> The default {@code HttpClient} implementation returns
      +     * <p id="cancel"> 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>.
      diff --git a/src/java.net.http/share/classes/java/net/http/HttpResponse.java b/src/java.net.http/share/classes/java/net/http/HttpResponse.java
      index 47f7da46d761..08fcc894f9aa 100644
      --- a/src/java.net.http/share/classes/java/net/http/HttpResponse.java
      +++ b/src/java.net.http/share/classes/java/net/http/HttpResponse.java
      @@ -296,6 +296,13 @@ public interface BodyHandler<T> {
            *   HttpResponse<Void> response = client
            *     .send(request, BodyHandlers.discarding());  }
            *
      +     *  @apiNote
      +     *  Some {@linkplain HttpResponse#body() body implementations} created by
      +     *  {@linkplain BodySubscribers##streaming-body body subscribers} may need to be
      +     *  properly closed, read, or cancelled for the associated resources to
      +     *  be reclaimed and for the associated request to {@linkplain HttpClient##closing
      +     *  run to completion}.
      +     *
            * @since 11
            */
           public static class BodyHandlers {
      @@ -633,8 +640,14 @@ public static BodyHandler<Path> ofFileDownload(Path directory,
                *
                * @apiNote See {@link BodySubscribers#ofInputStream()} for more
                * information.
      +         * <p>
      +         * To ensure that all resources associated with the
      +         * corresponding exchange are properly released the caller must
      +         * eventually obtain and close the {@linkplain BodySubscribers#ofInputStream()
      +         * returned stream}.
      +         *
      +         * @return a {@linkplain HttpClient##streaming streaming} response body handler
                *
      -         * @return a response body handler
                */
               public static BodyHandler<InputStream> ofInputStream() {
                   return (responseInfo) -> BodySubscribers.ofInputStream();
      @@ -651,7 +664,14 @@ public static BodyHandler<InputStream> ofInputStream() {
                * <p> When the {@code HttpResponse} object is returned, the body may
                * not have been completely received.
                *
      -         * @return a response body handler
      +         * @apiNote
      +         * To ensure that all resources associated with the
      +         * corresponding exchange are properly released the caller must
      +         * eventually obtain and close the {@linkplain BodySubscribers#ofLines(Charset)
      +         * returned stream}.
      +         *
      +         * @return a {@linkplain HttpClient##streaming streaming} response body handler
      +         *
                */
               public static BodyHandler<Stream<String>> ofLines() {
                   return (responseInfo) ->
      @@ -726,10 +746,17 @@ public static BodyHandler<String> ofString() {
                * response bytes can be obtained as they are received. The publisher
                * can and must be subscribed to only once.
                *
      -         * @apiNote See {@link BodySubscribers#ofPublisher()} for more
      +         * @apiNote
      +         * See {@link BodySubscribers#ofPublisher()} for more
                * information.
      +         * <p>
      +         * To ensure that all resources associated with the
      +         * corresponding exchange are properly released the caller must
      +         * subscribe to the publisher and conform to the rules outlined in
      +         * {@linkplain BodySubscribers#ofPublisher()}
      +         *
      +         * @return a {@linkplain HttpClient##streaming publishing} response body handler
                *
      -         * @return a response body handler
                */
               public static BodyHandler<Publisher<List<ByteBuffer>>> ofPublisher() {
                   return (responseInfo) -> BodySubscribers.ofPublisher();
      @@ -840,7 +867,7 @@ public void applyPushPromise(
                * already be completed at this point.
                *
                * @param <T> the push promise response body type
      -         * @param pushPromiseHandler t he body handler to use for push promises
      +         * @param pushPromiseHandler the body handler to use for push promises
                * @param pushPromisesMap a map to accumulate push promises into
                * @return a push promise handler
                */
      @@ -937,6 +964,20 @@ public interface BodySubscriber<T>
            *     .send(request, responseInfo ->
            *        BodySubscribers.mapping(BodySubscribers.ofString(UTF_8), String::getBytes)); }
            *
      +     *  @apiNote
      +     *  <p id="streaming-body">
      +     *  Some {@linkplain HttpResponse#body() body implementations} created by
      +     *  {@linkplain BodySubscriber#getBody() body subscribers} may allow response bytes
      +     *  to be streamed to the caller. These implementations are typically
      +     *  {@link AutoCloseable} and may need to be explicitly closed in order for
      +     *  the resources associated with the request and the client to be {@linkplain
      +     *  HttpClient##closing eventually reclaimed}.
      +     *  Some other implementations are {@linkplain  Publisher publishers} which need to be
      +     *  {@link BodySubscribers#ofPublisher() subscribed} in order for their associated
      +     *  resources to be released and for the associated request to {@linkplain
      +     *  HttpClient##closing run to completion}.
      +     *
      +     *
            * @since 11
            */
           public static class BodySubscribers {
      @@ -1168,7 +1209,7 @@ public static BodySubscriber<Path> ofFile(Path file) {
                * amount of data is delivered in a timely fashion.
                *
                * @param consumer a Consumer of byte arrays
      -         * @return a BodySubscriber
      +         * @return a body subscriber
                */
               public static BodySubscriber<Void>
               ofByteArrayConsumer(Consumer<Optional<byte[]>> consumer) {
      @@ -1199,8 +1240,8 @@ public static BodySubscriber<Path> ofFile(Path file) {
                * while blocking on read. In that case, the request will also be
                * cancelled and the {@code InputStream} will be closed.
                *
      -         * @return a body subscriber that streams the response body as an
      -         *         {@link InputStream}.
      +         * @return a {@linkplain HttpClient##streaming streaming body subscriber}
      +         *         which streams the response body as an {@link InputStream}.
                */
               public static BodySubscriber<InputStream> ofInputStream() {
                   return new ResponseSubscribers.HttpResponseInputStream();
      @@ -1225,8 +1266,8 @@ public static BodySubscriber<InputStream> ofInputStream() {
                * from being reused for subsequent operations.
                *
                * @param charset the character set to use when converting bytes to characters
      -         * @return a body subscriber that streams the response body as a
      -         *         {@link Stream Stream}{@code <String>}.
      +         * @return a {@linkplain HttpClient##streaming streaming body subscriber} which streams
      +         *          the response body as a {@link Stream Stream}{@code <String>}.
                *
                * @see BufferedReader#lines()
                */
      @@ -1268,8 +1309,10 @@ public static BodySubscriber<Stream<String>> ofLines(Charset charset) {
                * HTTP connection to be closed and prevent it from being reused for
                * subsequent operations.
                *
      -         * @return A {@code BodySubscriber} which publishes the response body
      +         * @return A {@linkplain HttpClient##streaming publishing body subscriber}
      +         *         which publishes the response body
                *         through a {@code Publisher<List<ByteBuffer>>}.
      +         *
                */
               public static BodySubscriber<Publisher<List<ByteBuffer>>> ofPublisher() {
                   return ResponseSubscribers.createPublisher();
      @@ -1282,7 +1325,7 @@ public static BodySubscriber<Publisher<List<ByteBuffer>>> ofPublisher() {
                *
                * @param <U> the type of the response body
                * @param value the value to return from HttpResponse.body(), may be {@code null}
      -         * @return a {@code BodySubscriber}
      +         * @return a body subscriber
                */
               public static <U> BodySubscriber<U> replacing(U value) {
                   return new ResponseSubscribers.NullSubscriber<>(Optional.ofNullable(value));
      diff --git a/src/java.net.http/share/classes/java/net/http/package-info.java b/src/java.net.http/share/classes/java/net/http/package-info.java
      index c0df4e04588e..9958fd94da0e 100644
      --- a/src/java.net.http/share/classes/java/net/http/package-info.java
      +++ b/src/java.net.http/share/classes/java/net/http/package-info.java
      @@ -56,8 +56,10 @@
        * UnsupportedOperationException} for their {@link
        * CompletableFuture#obtrudeValue(Object) obtrudeValue}
        * and {@link CompletableFuture#obtrudeException(Throwable)
      - * obtrudeException} methods. Invoking the {@link CompletableFuture#cancel
      - * cancel} method on a {@code CompletableFuture} returned by this API may not
      + * obtrudeException} methods. Unless {@linkplain
      + * HttpClient##cancel otherwise specified}, invoking
      + * the {@link CompletableFuture#cancel cancel} method on a
      + * {@code CompletableFuture} returned by this API may not
        * interrupt the underlying operation, but may be useful to complete,
        * exceptionally, dependent stages that have not already completed.
        *

            dfuchs Daniel Fuchs
            dfuchs Daniel Fuchs
            Jaikiran Pai
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: