Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 11 HTTP client asynchronous execution

I'm trying the new HTTP client API from JDK 11, specifically its asynchronous way of executing requests. But there is something that I'm not sure I understand (sort of an implementation aspect). In the documentation, it says:

Asynchronous tasks and dependent actions of returned CompletableFuture instances are executed on the threads supplied by the client's Executor, where practical.

As I understand this, it means that if I set a custom executor when creating the HttpClient object:

ExecutorService executor = Executors.newFixedThreadPool(3);

HttpClient httpClient = HttpClient.newBuilder()
                      .executor(executor)  // custom executor
                      .build();

then if I send a request asynchronously and add dependent actions on the returned CompletableFuture, the dependent action should execute on the specified executor.

httpClient.sendAsync(request, BodyHandlers.ofString())
          .thenAccept(response -> {
      System.out.println("Thread is: " + Thread.currentThread().getName());
      // do something when the response is received
});

However, in the dependent action above (the consumer in thenAccept), I see that the thread doing it is from the common pool and not the custom executor, since it prints Thread is: ForkJoinPool.commonPool-worker-5.

Is this a bug in the implementation? Or something I'm missing? I notice it says "instances are executed on the threads supplied by the client's Executor, where practical", so is this a case where this is not applied?

Note that I also tried thenAcceptAsync as well and it's the same result.

like image 898
M A Avatar asked Aug 18 '18 10:08

M A


People also ask

Is HttpClient asynchronous?

This example demonstrates a basic asynchronous HTTP request / response exchange. Response content is buffered in memory for simplicity. This example demonstrates an asynchronous HTTP request / response exchange with a full content streaming.

Is Java 11 HttpClient thread-safe?

Once created, an HttpClient instance is immutable, thus automatically thread-safe, and you can send multiple requests with it. By default, the client tries to open an HTTP/2 connection. If the server answers with HTTP/1.1, the client automatically falls back to this version.

Which HttpClient is best in Java?

Alongside the support of HTTP/2, the growth of non-blocking input and output and asynchronous programming was the driver for a new HTTP client in JDK. This was finally made available with Java 11 and is therefore the currently preferred option for HTTP requests in Java without the use of a third-party library.


2 Answers

I just found an updated documentation (the one I initially linked to seems old) where it explains this implementation behavior:

In general, asynchronous tasks execute in either the thread invoking the operation, e.g. sending an HTTP request, or by the threads supplied by the client's executor. Dependent tasks, those that are triggered by returned CompletionStages or CompletableFutures, that do not explicitly specify an executor, execute in the same default executor as that of CompletableFuture, or the invoking thread if the operation completes before the dependent task is registered.

And the default executor of CompletableFuture is the common pool.

I also found the bug ID that introduces this behavior, in which the API developers fully explains it:

2) Dependent tasks run in the common pool The default execution of dependent tasks has been updated to run in the same executor as that of CompletableFuture's defaultExecutor. This is more familiar to developers that already use CF, and reduces the likelihood of the HTTP Client being starved of threads to execute its tasks. This is just default behaviour, both the HTTP Client and CompletableFuture allow more fine-grain control, if needed.

like image 68
M A Avatar answered Oct 14 '22 09:10

M A


Short-version: I think you've identified an implementation detail and that "where practical" is meant to imply that there is no guarantee that the provided executor will be used.

In detail:

I've downloaded the JDK 11 source from here. (jdk11-f729ca27cf9a at the time of this writing).

In src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java, there is the following class:

/**
 * A DelegatingExecutor is an executor that delegates tasks to
 * a wrapped executor when it detects that the current thread
 * is the SelectorManager thread. If the current thread is not
 * the selector manager thread the given task is executed inline.
 */
final static class DelegatingExecutor implements Executor {

This class uses the executor if isInSelectorThread is true, otherwise the task is executed inline. This boils down to:

boolean isSelectorThread() {
    return Thread.currentThread() == selmgr;
}

where selmgr is a SelectorManager. Edit: this class is also contained in HttpClientImpl.java:

// Main loop for this client's selector
private final static class SelectorManager extends Thread {

The upshot: I'm guessing where practical implies that it is implementation dependent and that there is no guarantee that the provided executor will be used.

NOTE: this is different than the default executor, where the builder does not provide an executor. In that case, the code clearly creates a new cached-thread pool. Stated another way, if the builder provides an executor, the identity check for SelectorManager is made.

like image 42
Michael Easter Avatar answered Oct 14 '22 09:10

Michael Easter