I think @sbordet's answer is incorrect and this error does not occur because your requests per second is exceeding MAX_CONCURRENT_STREAMS
, but because the number of open HTTP streams (per HTTP 2 connection?) exceeds that number.
For example, I have a server at work that has a MAX_CONCURRENT_STREAMS
setting of 128:
$ curl -iv -H "Content-Type: application/json" https://example.local
...
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
But I seem to be able to hit it with up to ~1000 requests per second without getting any errors back:
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class TooManyConcurrentStreams1 {
private static final int CONCURRENCY = 1000;
public static void main(String[] args) {
final var counter = new AtomicInteger();
final var singletonHttpClient = newHttpClient();
final var singletonRequest = newRequest();
final var responses = new ArrayList<CompletableFuture<HttpResponse<Void>>>(CONCURRENCY);
for (int i = 0; i < CONCURRENCY; i++) {
responses.add(singletonHttpClient.sendAsync(singletonRequest, BodyHandlers.discarding()));
}
for (CompletableFuture<HttpResponse<Void>> response : responses) {
response.thenAccept(x -> {});
response.join();
System.out.println(counter.incrementAndGet());
}
singletonHttpClient.executor().ifPresent(executor -> {
if (executor instanceof ExecutorService executorService) {
executorService.shutdown();
}
});
}
public static HttpRequest newRequest() {
return HttpRequest.newBuilder()
.uri(Constants.TEST_URI)
.header("Content-Type", Constants.CONTENT_TYPE)
.header("Accept", Constants.CONTENT_TYPE)
.POST(HttpRequest.BodyPublishers.ofString(Constants.BODY))
.build();
}
public static HttpClient newHttpClient() {
return HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.executor(Executors.newFixedThreadPool(CONCURRENCY))
.build();
}
}
When I increase CONCURRENCY
to an absurd number like 2000,
I get this error, and not java.io.IOException: too many concurrent streams
:
Exception in thread "main" java.util.concurrent.CompletionException: java.net.SocketException: Connection reset
at java.base/java.util.concurrent.CompletableFuture.encodeRelay(CompletableFuture.java:368)
at java.base/java.util.concurrent.CompletableFuture.completeRelay(CompletableFuture.java:377)
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1152)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2162)
at java.net.http/jdk.internal.net.http.Stream.completeResponseExceptionally(Stream.java:1153)
at java.net.http/jdk.internal.net.http.Stream.cancelImpl(Stream.java:1238)
at java.net.http/jdk.internal.net.http.Stream.connectionClosing(Stream.java:1212)
at java.net.http/jdk.internal.net.http.Http2Connection.shutdown(Http2Connection.java:710)
at java.net.http/jdk.internal.net.http.Http2Connection$Http2TubeSubscriber.processQueue(Http2Connection.java:1323)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$LockingRestartableTask.run(SequentialScheduler.java:205)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:149)
at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:230)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
However, I can reproduce your error with this code (I hit this error first, and then found your question here!)
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class TooManyConcurrentStreams2 {
public static void main(String[] args) {
final var singletonHttpClient = newHttpClient();
final var singletonRequest = newRequest();
final var counter = new AtomicInteger();
final var scheduler = Executors.newScheduledThreadPool(2);
scheduler.schedule(scheduler::shutdown, 1, TimeUnit.HOURS);
scheduler.scheduleAtFixedRate(() -> {
final var batchSize = counter.incrementAndGet();
final var responses = new ArrayList<CompletableFuture<HttpResponse<Void>>>(batchSize);
try {
for (int i = 0; i < batchSize; i++) {
responses.add(
singletonHttpClient.sendAsync(
singletonRequest,
BodyHandlers.discarding()
)
);
}
for (CompletableFuture<HttpResponse<Void>> response : responses) {
response.thenAccept(x -> {
});
response.join();
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("batchSize = " + batchSize);
}, 0, 500, TimeUnit.MILLISECONDS);
}
public static HttpRequest newRequest() {
return HttpRequest.newBuilder()
.uri(Constants.TEST_URI)
.header("Content-Type", Constants.CONTENT_TYPE)
.header("Accept", Constants.CONTENT_TYPE)
.POST(HttpRequest.BodyPublishers.ofString(Constants.BODY))
.build();
}
public static HttpClient newHttpClient() {
return HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
}
}
This one fails on 128th (!) execution of my once per 500ms runnable:
java.util.concurrent.CompletionException: java.io.IOException: too many concurrent streams
at java.base/java.util.concurrent.CompletableFuture.encodeRelay(CompletableFuture.java:368)
at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1189)
at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2309)
at java.net.http/jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:453)
at java.net.http/jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:341)
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.io.IOException: too many concurrent streams
So the problem is not number of requests per second, but something else, which seems to be the number of concurrent open streams per http connection/client.
We can verify this by NOT sharing the same http client (and request) for all batch requests:
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class TooManyConcurrentStreams2 {
public static void main(String[] args) {
final var counter = new AtomicInteger();
final var scheduler = Executors.newScheduledThreadPool(2);
scheduler.schedule(scheduler::shutdown, 1, TimeUnit.HOURS);
scheduler.scheduleAtFixedRate(() -> {
final var httpClient = newHttpClient();
final var request = newRequest();
final var batchSize = counter.incrementAndGet();
final var responses = new ArrayList<CompletableFuture<HttpResponse<Void>>>(batchSize);
try {
for (int i = 0; i < batchSize; i++) {
responses.add(
httpClient.sendAsync(
request,
BodyHandlers.discarding()
)
);
}
for (CompletableFuture<HttpResponse<Void>> response : responses) {
response.thenAccept(x -> {
});
response.join();
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("batchSize = " + batchSize);
}, 0, 500, TimeUnit.MILLISECONDS);
}
public static HttpRequest newRequest() {
return HttpRequest.newBuilder()
.uri(Constants.TEST_URI)
.header("Content-Type", Constants.CONTENT_TYPE)
.header("Accept", Constants.CONTENT_TYPE)
.POST(HttpRequest.BodyPublishers.ofString(Constants.BODY))
.build();
}
public static HttpClient newHttpClient() {
return HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
}
}
For me, this one fails at 143rd try with this error message:
java.util.concurrent.CompletionException: java.lang.InternalError: java.net.SocketException: Too many open files
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:315)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:320)
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1159)
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.InternalError: java.net.SocketException: Too many open files
at java.net.http/jdk.internal.net.http.PlainHttpConnection.<init>(PlainHttpConnection.java:293)
at java.net.http/jdk.internal.net.http.AsyncSSLConnection.<init>(AsyncSSLConnection.java:49)
at java.net.http/jdk.internal.net.http.HttpConnection.getSSLConnection(HttpConnection.java:293)
at java.net.http/jdk.internal.net.http.HttpConnection.getConnection(HttpConnection.java:279)
at java.net.http/jdk.internal.net.http.Http2Connection.createAsync(Http2Connection.java:369)
at java.net.http/jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:128)
at java.net.http/jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:93)
at java.net.http/jdk.internal.net.http.Exchange.establishExchange(Exchange.java:343)
at java.net.http/jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:475)
at java.net.http/jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:380)
at java.net.http/jdk.internal.net.http.Exchange.responseAsync(Exchange.java:372)
at java.net.http/jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:408)
at java.net.http/jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:341)
at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150)
... 5 more
This one is most likely due to my laptop's relatively low ulimit
of 12544.