diff --git a/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java b/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java
index 78f15d868..d3fefd8cd 100644
--- a/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java
+++ b/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java
@@ -32,6 +32,7 @@
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
@@ -1148,15 +1149,21 @@ public HttpResponse call() throws Exception {
/**
* {@link Beta}
* Executes this request asynchronously using {@link #executeAsync(Executor)} in a single separate
- * thread using {@link Executors#newFixedThreadPool(int)}.
+ * thread using {@link Executors#newFixedThreadPool(int)}. The executor is shut down after the
+ * request has been submitted.
*
* @return A future for accessing the results of the asynchronous request.
* @since 1.13
*/
@Beta
public Future executeAsync() {
- return executeAsync(
- Executors.newFixedThreadPool(1, new ThreadFactoryBuilder().setDaemon(true).build()));
+ ExecutorService executor =
+ Executors.newFixedThreadPool(1, new ThreadFactoryBuilder().setDaemon(true).build());
+ try {
+ return executeAsync(executor);
+ } finally {
+ executor.shutdown();
+ }
}
/**
diff --git a/google-http-client/src/test/java/com/google/api/client/http/HttpRequestTest.java b/google-http-client/src/test/java/com/google/api/client/http/HttpRequestTest.java
index 085b9f563..b25dec1cf 100644
--- a/google-http-client/src/test/java/com/google/api/client/http/HttpRequestTest.java
+++ b/google-http-client/src/test/java/com/google/api/client/http/HttpRequestTest.java
@@ -41,7 +41,9 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@@ -1209,6 +1211,42 @@ public void testExecuteAsync()
assertNotNull(futureResponse.get(10, TimeUnit.MILLISECONDS));
}
+ @Test
+ public void testExecuteAsync_defaultExecutorDoesNotLeakThreads() throws Exception {
+ Set threadsBeforeTest = getThreadIds();
+ HttpTransport transport = new MockHttpTransport();
+
+ for (int i = 0; i < 20; i++) {
+ HttpRequest request =
+ transport.createRequestFactory().buildGetRequest(HttpTesting.SIMPLE_GENERIC_URL);
+ assertNotNull(request.executeAsync().get(10, TimeUnit.SECONDS));
+ }
+
+ long timeout = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10);
+ while (hasNewExecutorThread(threadsBeforeTest) && System.currentTimeMillis() < timeout) {
+ Thread.sleep(10);
+ }
+ assertFalse(hasNewExecutorThread(threadsBeforeTest));
+ }
+
+ private static Set getThreadIds() {
+ Set threadIds = new HashSet();
+ for (Thread thread : Thread.getAllStackTraces().keySet()) {
+ threadIds.add(thread.getId());
+ }
+ return threadIds;
+ }
+
+ private static boolean hasNewExecutorThread(Set threadsBeforeTest) {
+ for (Thread thread : Thread.getAllStackTraces().keySet()) {
+ if (!threadsBeforeTest.contains(thread.getId())
+ && thread.getName().matches("pool-\\d+-thread-\\d+")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Test
public void testExecute_redirects() throws Exception {
class MyTransport extends MockHttpTransport {