'Edited: Retrofit and CompletableFuture, is there a way to not rely on the underlying connection pool?

Original question:

OkHttp, is there a way to not rely on a connection pool?

Hey OkHttp users and/or contributors!

Is there a way to use an OkHttpClient without any pool of connections ? That is, in other words, is it possibile to fire one or a few HTTP request from the same thread in which myClient.execute() method is invoked?

I tried to find a clear answer online but this is all I found:

  • by default a connection pool is used, and as such my understanding is that an HTTP request will be always fired from one of the threads that are instantiated and kept alive by the OkHttp client
  • It’s possible to configure a custom ExecutorService at the client level. Theoretically, I could define a single threaded executor, but that would anyway come with a dedicated extra thread (and not the parent one in my invocation stack). moreover, such a “solution” would be impractical as it would cause queuing issues under a non negligible load, assuming a singleton instance of the client is being used.

I could not find a way to take over parallelism at the Request level. The only workaround I can currently think of is as follows:

  • single threaded executor setup
  • Multiple client instances, each for any request (or set of requests of the same functional flow)

The above would anyway not solve my problem completely, as there is anyway an extra thread per request/s. Moreover, based on the documentation I found online the shared best practice when using OkHttp is to share a singleton client instance amongst multiple threads.

Thanks a lot!

Ps: for context, the reason I’m asking is that ~a customer~ a friend of mine has a logger implementation that relies on ThreadLocals to correlate HTTP requests logs, produced by the client instance via an application interceptor. They’re replacing their existing, vintage integration with my company payments API with a brand new SDK we’re offering, that arguably deals with concurrency differently from what they were used.

Edits

This reply shed a light on the original question I had: Yes, it's possible to use OkHttp synchronously on the main thread, by just using the execute() method offered by the Call type.

The behavior I'm experiencing (all calls submitted to a thread pool) is due to the fact that I'm using Retrofit along with CompletableFuture<T>s declared as response types of my REST API endpoints. For instance:

@GET("/payments/{id}")
    CompletableFuture<ApiResponse<PaymentDetail>> getPayment(@Path("id") String paymentId);

Actually, in my particular case I've also a custom call adapter that adapts a CompletableFuture<T> into a CompletableFuture<ApiResponse<T>> for an improved error handling, but this should not be relevant in this scope.

My outstanding questions are, eventually:

  1. When using Retrofit's CompletableFuture adapter, how that affects Okhttp multithreading ? In other words, is my understanding correct that when dealing with responsens of CompletableFuture in Retrofit, we always submit the HTTP request to one of the threads that's available in the OkhttpClient Dispatcher ?
  2. Am I right in saying that regardless of the synchronous or asynchronous model used on Retrofit/OkHttp, OkHttp's connection pool is always being used ?
  3. Is it right to say that when using a synchronous model, OkHttp's Dispatcher (even the default one) is irrelevant ? If so, is there a way to not have any thread pool provisioned by OkHttp ?


Solution 1:[1]

If you use Call.execute(), almost all work happens on the thread that makes that call. This includes all interceptors.

Solution 2:[2]

I did some specific tests to answer my questions.

  1. Does the use of CompletableFuture on Retrofit mean that we're relying on the internal OkHttp Thread pool ?

Yes, as per my tests. enter image description here

Wrt the above screenshot, we can see that one of OkHttp threads is used. That said, I still do have doubts in cases where there are requests fired in the context of interceptors. In my tests, in those cases requests are fired from the main thread.

  1. Is the connection pool always used, irregardless of sync or async calls ?

Yes. I can see the connection pool being invoked in both cases. below a sample in a sync use case: enter image description here

  1. Right to say that the Dispatcher is only relevant with async flows ?

No, whilst I'd say its executor service is only used in case of async flows, the dispatcher also keeps track of sync calls in flight. Async flows are managed in the context of a private boolean promoteAndExecute() method:

for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      asyncCall.executeOn(executorService());
    }

The same dispatcher keeps track of sync calls in a runningSyncCalls instance variable. That being said, I think that if one plans to use OKhttp/Retrofit in a synchronous fashion only, it makes sense to limit the amount of threads in the Dispatcher used. The default one is probably a good fit already

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jesse Wilson
Solution 2