Skip to content

Commit 6580c4e

Browse files
committed
8267140: Support closing the HttpClient by making it auto-closable
Reviewed-by: jpai
1 parent b5ea140 commit 6580c4e

File tree

12 files changed

+2399
-152
lines changed

12 files changed

+2399
-152
lines changed

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

Lines changed: 187 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.io.IOException;
2929
import java.io.UncheckedIOException;
3030
import java.net.InetAddress;
31+
import java.net.http.HttpResponse.BodyHandlers;
3132
import java.nio.channels.Selector;
3233
import java.net.Authenticator;
3334
import java.net.CookieHandler;
@@ -38,13 +39,16 @@
3839
import java.security.AccessController;
3940
import java.security.PrivilegedAction;
4041
import java.time.Duration;
42+
import java.util.Objects;
4143
import java.util.Optional;
4244
import java.util.concurrent.CompletableFuture;
4345
import java.util.concurrent.Executor;
4446
import javax.net.ssl.SSLContext;
4547
import javax.net.ssl.SSLParameters;
4648
import java.net.http.HttpResponse.BodyHandler;
4749
import java.net.http.HttpResponse.PushPromiseHandler;
50+
import java.util.concurrent.Flow.Subscription;
51+
4852
import jdk.internal.net.http.HttpClientBuilderImpl;
4953

5054
/**
@@ -61,7 +65,11 @@
6165
* and can be used to send multiple requests.
6266
*
6367
* <p> An {@code HttpClient} provides configuration information, and resource
64-
* sharing, for all requests sent through it.
68+
* sharing, for all requests sent through it. An {@code HttpClient} instance
69+
* typically manages its own pools of connections, which it may then reuse
70+
* as and when necessary. Connection pools are typically not shared between
71+
* {@code HttpClient} instances. Creating a new client for each operation,
72+
* though possible, will usually prevent reusing such connections.
6573
*
6674
* <p> A {@link BodyHandler BodyHandler} must be supplied for each {@link
6775
* HttpRequest} sent. The {@code BodyHandler} determines how to handle the
@@ -119,7 +127,29 @@
119127
* proxying) and a {@code URL} string of the form {@code "socket://host:port"}
120128
* where host and port specify the proxy's address.
121129
*
122-
* @implNote If an explicit {@linkplain HttpClient.Builder#executor(Executor)
130+
* @apiNote
131+
* Resources allocated by the {@code HttpClient} may be
132+
* reclaimed early by {@linkplain #close() closing} the client.
133+
*
134+
* @implNote
135+
* <p id="closing">
136+
* The JDK built-in implementation of the {@code HttpClient} overrides
137+
* {@link #close()}, {@link #shutdown()}, {@link #shutdownNow()},
138+
* {@link #awaitTermination(Duration)}, and {@link #isTerminated()} to
139+
* provide a best effort implementation. Failing to close, cancel, or
140+
* read returned streams to exhaustion, such as streams provided when using
141+
* {@link BodyHandlers#ofInputStream()}, {@link BodyHandlers#ofLines()}, or
142+
* {@link BodyHandlers#ofPublisher()}, may prevent requests submitted
143+
* before an {@linkplain #shutdown() orderly shutdown}
144+
* to run to completion. Likewise, failing to
145+
* {@linkplain Subscription#request(long) request data} or {@linkplain
146+
* Subscription#cancel() cancel subscriptions} from a custom {@linkplain
147+
* java.net.http.HttpResponse.BodySubscriber BodySubscriber} may stop
148+
* delivery of data and {@linkplain #awaitTermination(Duration) stall an
149+
* orderly shutdown}.
150+
*
151+
* <p>
152+
* If an explicit {@linkplain HttpClient.Builder#executor(Executor)
123153
* executor} has not been set for an {@code HttpClient}, and a security manager
124154
* has been installed, then the default executor will execute asynchronous and
125155
* dependent tasks in a context that is granted no permissions. Custom
@@ -132,7 +162,7 @@
132162
*
133163
* @since 11
134164
*/
135-
public abstract class HttpClient {
165+
public abstract class HttpClient implements AutoCloseable {
136166

137167
/**
138168
* Creates an HttpClient.
@@ -599,7 +629,8 @@ public enum Redirect {
599629
* @param request the request
600630
* @param responseBodyHandler the response body handler
601631
* @return the response
602-
* @throws IOException if an I/O error occurs when sending or receiving
632+
* @throws IOException if an I/O error occurs when sending or receiving, or
633+
* the client has {@linkplain ##closing shut down}
603634
* @throws InterruptedException if the operation is interrupted
604635
* @throws IllegalArgumentException if the {@code request} argument is not
605636
* a request that could have been validly built as specified by {@link
@@ -646,7 +677,8 @@ public enum Redirect {
646677
*
647678
* <p> The returned completable future completes exceptionally with:
648679
* <ul>
649-
* <li>{@link IOException} - if an I/O error occurs when sending or receiving</li>
680+
* <li>{@link IOException} - if an I/O error occurs when sending or receiving,
681+
* or the client has {@linkplain ##closing shut down}.</li>
650682
* <li>{@link SecurityException} - If a security manager has been installed
651683
* and it denies {@link java.net.URLPermission access} to the
652684
* URL in the given request, or proxy if one is configured.
@@ -730,4 +762,154 @@ public enum Redirect {
730762
public WebSocket.Builder newWebSocketBuilder() {
731763
throw new UnsupportedOperationException();
732764
}
765+
766+
/**
767+
* Initiates an orderly shutdown in which requests previously
768+
* submitted with {@code send} or {@code sendAsync}
769+
* are run to completion, but no new request will be accepted.
770+
* Running a request to completion may involve running several
771+
* operations in the background, including {@linkplain ##closing
772+
* waiting for responses to be delivered}, which will all have to
773+
* run to completion until the request is considered completed.
774+
*
775+
* Invocation has no additional effect if already shut down.
776+
*
777+
* <p>This method does not wait for previously submitted request
778+
* to complete execution. Use {@link #awaitTermination(Duration)
779+
* awaitTermination} or {@link #close() close} to do that.
780+
*
781+
* @implSpec
782+
* The default implementation of this method does nothing. Subclasses should
783+
* override this method to implement the appropriate behavior.
784+
*
785+
* @see ##closing Implementation Note on closing the HttpClient
786+
*
787+
* @since 21
788+
*/
789+
public void shutdown() { }
790+
791+
/**
792+
* Blocks until all operations have completed execution after a shutdown
793+
* request, or the {@code duration} elapses, or the current thread is
794+
* {@linkplain Thread#interrupt() interrupted}, whichever happens first.
795+
* Operations are any tasks required to run a request previously
796+
* submitted with {@code send} or {@code sendAsync} to completion.
797+
*
798+
* <p> This method does not wait if the duration to wait is less than or
799+
* equal to zero. In this case, the method just tests if the thread has
800+
* terminated.
801+
*
802+
* @implSpec
803+
* The default implementation of this method checks for null arguments, but
804+
* otherwise does nothing and returns true.
805+
* Subclasses should override this method to implement the proper behavior.
806+
*
807+
* @param duration the maximum time to wait
808+
* @return {@code true} if this client terminated and
809+
* {@code false} if the timeout elapsed before termination
810+
* @throws InterruptedException if interrupted while waiting
811+
*
812+
* @see ##closing Implementation Note on closing the HttpClient
813+
*
814+
* @since 21
815+
*/
816+
public boolean awaitTermination(Duration duration) throws InterruptedException {
817+
Objects.requireNonNull(duration);
818+
return true;
819+
}
820+
821+
/**
822+
* Returns {@code true} if all operations have completed following
823+
* a shutdown.
824+
* Operations are any tasks required to run a request previously
825+
* submitted with {@code send} or {@code sendAsync} to completion.
826+
* <p> Note that {@code isTerminated} is never {@code true} unless
827+
* either {@code shutdown} or {@code shutdownNow} was called first.
828+
*
829+
* @implSpec
830+
* The default implementation of this method does nothing and returns false.
831+
* Subclasses should override this method to implement the proper behavior.
832+
*
833+
* @return {@code true} if all tasks have completed following a shutdown
834+
*
835+
* @see ##closing Implementation Note on closing the HttpClient
836+
*
837+
* @since 21
838+
*/
839+
public boolean isTerminated() {
840+
return false;
841+
}
842+
843+
/**
844+
* This method attempts to initiate an immediate shutdown.
845+
* An implementation of this method may attempt to
846+
* interrupt operations that are actively running.
847+
* Operations are any tasks required to run a request previously
848+
* submitted with {@code send} or {@code sendAsync} to completion.
849+
* The behavior of actively running operations when interrupted
850+
* is undefined. In particular, there is no guarantee that
851+
* interrupted operations will terminate, or that code waiting
852+
* on these operations will ever be notified.
853+
*
854+
* @implSpec
855+
* The default implementation of this method simply calls {@link #shutdown()}.
856+
* Subclasses should override this method to implement the appropriate
857+
* behavior.
858+
*
859+
* @see ##closing Implementation Note on closing the HttpClient
860+
*
861+
* @since 21
862+
*/
863+
public void shutdownNow() {
864+
shutdown();
865+
}
866+
867+
/**
868+
* Initiates an orderly shutdown in which requests previously
869+
* submitted to {@code send} or {@code sendAsync}
870+
* are run to completion, but no new request will be accepted.
871+
* Running a request to completion may involve running several
872+
* operations in the background, including {@linkplain ##closing
873+
* waiting for responses to be delivered}.
874+
* This method waits until all operations have completed execution
875+
* and the client has terminated.
876+
*
877+
* <p> If interrupted while waiting, this method may attempt to stop all
878+
* operations by calling {@link #shutdownNow()}. It then continues to wait
879+
* until all actively executing operations have completed.
880+
* The interrupt status will be re-asserted before this method returns.
881+
*
882+
* <p> If already terminated, invoking this method has no effect.
883+
*
884+
* @implSpec
885+
* The default implementation invokes {@code shutdown()} and waits for tasks
886+
* to complete execution with {@code awaitTermination}.
887+
*
888+
* @see ##closing Implementation Note on closing the HttpClient
889+
*
890+
* @since 21
891+
*/
892+
@Override
893+
public void close() {
894+
boolean terminated = isTerminated();
895+
if (!terminated) {
896+
shutdown();
897+
boolean interrupted = false;
898+
while (!terminated) {
899+
try {
900+
terminated = awaitTermination(Duration.ofDays(1L));
901+
} catch (InterruptedException e) {
902+
if (!interrupted) {
903+
interrupted = true;
904+
shutdownNow();
905+
if (isTerminated()) break;
906+
}
907+
}
908+
}
909+
if (interrupted) {
910+
Thread.currentThread().interrupt();
911+
}
912+
}
913+
}
914+
733915
}

src/java.net.http/share/classes/jdk/internal/net/http/HttpClientFacade.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -167,6 +167,31 @@ public WebSocket.Builder newWebSocketBuilder() {
167167
}
168168
}
169169

170+
@Override
171+
public boolean isTerminated() {
172+
return impl.isTerminated();
173+
}
174+
175+
@Override
176+
public void shutdown() {
177+
impl.shutdown();
178+
}
179+
180+
@Override
181+
public void shutdownNow() {
182+
impl.shutdownNow();
183+
}
184+
185+
@Override
186+
public boolean awaitTermination(Duration duration) throws InterruptedException {
187+
return impl.awaitTermination(duration);
188+
}
189+
190+
@Override
191+
public void close() {
192+
impl.close();
193+
}
194+
170195
@Override
171196
public String toString() {
172197
// Used by tests to get the client's id.

0 commit comments

Comments
 (0)