jetty / jetty.project

Eclipse Jetty® - Web Container & Clients - supports HTTP/2, HTTP/1.1, HTTP/1.0, websocket, servlets, and more
https://eclipse.dev/jetty
Other
3.83k stars 1.91k forks source link

Does Jetty 11 support Virtual Threads? #8634

Closed cowwoc closed 1 year ago

cowwoc commented 1 year ago

Jetty version 11.0.2

Java version 19

Question I noticed that you explicitly added virtual thread support in Jetty 10 but did not see a corresponding update in Jetty 11. Is such an update forthcoming or does Jetty 11 already support virtual threads as well as Jetty 10 does?

Currently I am just invoking HttpClient.setExecutor(Executors.newVirtualThreadPerTaskExecutor()). Is this the right way to go? Or can I do even better?

Thank you.

sbordet commented 1 year ago

Jetty 11 supports virtual thread: https://github.com/eclipse/jetty.project/blob/jetty-11.0.12/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java.

Currently I am just invoking HttpClient.setExecutor(Executors.newVirtualThreadPerTaskExecutor()). Is this the right way to go? Or can I do even better?

Jetty's HttpClient contract is that listeners invoked by the implementation should not block. Therefore, it's not necessary to configure HttpClient with a virtual thread executor. Rather, if you have blocking application logic, the application spawns a virtual thread.

cowwoc commented 1 year ago

Jetty's HttpClient contract is that listeners invoked by the implementation should not block. Therefore, it's not necessary to configure HttpClient with a virtual thread executor. Rather, if you have blocking application logic, the application spawns a virtual thread.

Hmm. I'm still confused. It looks like VirtualThreads is used by AdaptiveExecutionStrategy which is used by ManagedSelector.doStart(). Does that mean that Jetty only uses virtual threads for starting components because they may block?

And so, as an end-user, I do not need to change anything for Jetty to use virtual threads correctly?

Thank you.

sbordet commented 1 year ago

AdaptiveExecutionStrategy only uses virtual threads for tasks of type InvocationType.BLOCKING.

A typical case of such as task is invoking a Servlet since it may use blocking APIs (e.g. InputStream or OutputStream).

Jetty's HttpClient does not return blocking tasks to the AdaptiveExecutionStrategy, so virtual threads are never used because the tasks never block. The contract with client application is the opposite of that of a Servlet: client application code must not block. If it does, it should spawn threads to keep the system scalable. If you know your code is blocking, run it in a virtual thread.

As an end-user of Jetty's HttpClient you typically don't need to do anything, assuming your application is already non-blocking. Jetty's HttpClient won't use virtual threads by default, unless you configure them explicitly via HttpClient.setExecutor(Executors.newVirtualThreadPerTaskExecutor()), but as I said there is no need to if the application is non-blocking already.

cowwoc commented 1 year ago

Got it. Thank you.

When Loom came out I made the move from async to sync because my former implementation was very hard to read and suffered from a bunch of deadlocks.

Now, the application is blocking but only ever invoke HttpClient.send(). I only use event listeners for logging, and the implementation never blocks. Given this, I assume there is no way that the application would block Jetty. Is that correct?

cowwoc commented 1 year ago

PS: I just noticed Request.timeout() will provide a total timeout for the request/response conversation, so I'll move to that now.

Does this work for Request.send() (synchronous implementation) as well?

sbordet commented 1 year ago

That is correct.

To be more precise you must not call any blocking API from within HttpClient listeners such as BeginListener, CompleteListener, etc.

Logging is technically blocking, but only if the file system is blocking, which is a case that can be ignored, and currently (Java 19) even virtual threads will block when doing file system blocking operations.

Since you use blocking Request.send(), you obtain the response in your application code, and then HttpClient is out of the picture. The next line you can call blocking code, it is your application.

What you must not do is something like:

request.send(result -> {
  // some blocking call here.
});

Request.timeout() works for both blocking and non-blocking.

cowwoc commented 1 year ago

Thank you!