Java 11 HTTP client asynchronous execution

  • A+
Category:Languages

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.

 


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.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: