GithubHelp home page GithubHelp logo

Comments (20)

mhalbritter avatar mhalbritter commented on April 28, 2024 2

Btw, the tool I used is https://github.com/astrofrog/psrecord.

from spring-boot.

mhalbritter avatar mhalbritter commented on April 28, 2024 1

Yeah, we shouldn't fiddle with the Undertow executors.

The default one produces this plot:

default

As soon as we meddle with it, it starts breaking.

With virtual threads:

VT

With virtual threads and concurrency throttle to 200:

VT-concurrency-200

With platform threads and concurrency throttle to 200:

PT-200

It breaks in all configurations when setting the executor.

I'm going to revert the virtual thread support for Undertow.

from spring-boot.

mhalbritter avatar mhalbritter commented on April 28, 2024 1

Undertow 2.3.10.Final, SimpleAsyncTaskExecutor with 200 concurrency limit and virtual threads enabled:

VT-200

It's better, but there's still a problem somewhere.

from spring-boot.

mhalbritter avatar mhalbritter commented on April 28, 2024 1

This is what 2.3.10 with the default executor looks like:

default

from spring-boot.

mhalbritter avatar mhalbritter commented on April 28, 2024

That's one of the drawbacks when moving from a thread pool to virtual threads per task without a pool. When using thread pools, they usually have an upper bound for the size, limiting the maximum resource consumption. Unpooled virtual threads don't have that. I assume the application you're benchmarking is a simple demo application which just returns some hardcoded data? Because usually, if there's no pool in the webserver, some other pool in the application is limiting the throughput (e.g. the connection pool to the database).

Btw, you don't see the memory usage in VisualVM, as this is direct allocated memory, which is off-heap.

from spring-boot.

Tythor avatar Tythor commented on April 28, 2024

Yes, it's the Spring Boot demo project with a basic GET endpoint with no return value. Sorry, could you explain the cause for this again? Aren't virtual threads still limited by the size of the platform threads in the carrier pool? The Tomcat and Jetty servlets don't seem to have the same issue when using virtual threads. Also, thank you for the explanation about the profiler.

from spring-boot.

mhalbritter avatar mhalbritter commented on April 28, 2024

One possibility could be this:

1. VT1: Start accepting the request
2. VT1: Allocate memory
3. VT1: Block on something (maybe reading http from the TCP socket)
4. VT1: Handle request
5. VT1: Free memory

With enough requests lined up, the block at 3. could be the reason why the memory is exhausted. At this block, the scheduler frees the underlying platform thread, and switches to a new virtual thread, which allocates memory, blocks, a new virtual thread is scheduled, allocates memory, etc etc.

from spring-boot.

mhalbritter avatar mhalbritter commented on April 28, 2024

You could use a SimpleAsyncTaskExecutor with setConcurrencyLimit to limit the maximum allowed number of virtual threads.

from spring-boot.

MetaiR avatar MetaiR commented on April 28, 2024

as long as I remember, there is an issue that talks about virtual threads in Spring Boot here:
#38819

there is one part I saw that says in version 3.3.x the usage of the virtual thread must automatically happen for undertow. so can it be related to this issue?

can u check if this also happens with version 3.2.x ? @Tythor

from spring-boot.

Tythor avatar Tythor commented on April 28, 2024

@MetaiR
Yes, I can confirm the same behavior happens in v3.2.3. Since spring.threads.virtual.enabled=true does not apply to Undertow servlets until v3.3.0, I had to use the undertowDeploymentInfoCustomizer to enable virtual threading.

@mhalbritter
I attempted to use a SimpleAsyncTaskExecutor with virtual threads with a concurrency limit of 10, but the issue still persisted.

@Bean
public UndertowDeploymentInfoCustomizer undertowDeploymentInfoCustomizer() {
    return deploymentInfo -> {
        SimpleAsyncTaskExecutor simpleAsyncTaskExecutor = new SimpleAsyncTaskExecutor();
        simpleAsyncTaskExecutor.setVirtualThreads(true);
        simpleAsyncTaskExecutor.setConcurrencyLimit(10);
        deploymentInfo.setExecutor(simpleAsyncTaskExecutor);
    };
}

However, this led me to try using a non virtual threaded executor instead. Surprisingly, I observed the same behavior, albeit at a much slower growth rate of about 2m for 20GB instead of 30s. These three executors produced similar results:

deploymentInfo.setExecutor(new SimpleAsyncTaskExecutor());
deploymentInfo.setExecutor(Executors.newThreadPerTaskExecutor(Thread.ofPlatform().factory()));
deploymentInfo.setExecutor(Executors.newThreadPerTaskExecutor(Executors.defaultThreadFactory()));

However, when using

deploymentInfo.setExecutor(Executors.newCachedThreadPool());

the memory growth disappeared. Looking into the implementation, the main difference is that the cachedThreadPool executor has a keepAliveTime of 60s, while the others have a keepAliveTime of 0s.

Reducing the keepAliveTime to as low as 1s did not cause the memory growth in my tests. But setting it to 0s reproduced the same issue.

deploymentInfo.setExecutor(new ThreadPoolExecutor(0, Integer.MAX_VALUE, 1L, TimeUnit.SECONDS, new SynchronousQueue<>()));

So it looks like this issue is not unique to virtual threads, but perhaps with the way Undertow is handling executors?

from spring-boot.

mhalbritter avatar mhalbritter commented on April 28, 2024

Yeah, looks like something is wrong here, but it doesn't seem to be in Spring Boot. Please open an issue on the Undertow tracker, and feel free to drop the link in this issue. Thanks!

from spring-boot.

mhalbritter avatar mhalbritter commented on April 28, 2024

We decided to reopen the issue. The out of box experience with virtual threads in undertow is bad, and if we (or the undertow team) can't fix the problems with it, we might remove virtual threads support for undertow again.

from spring-boot.

Tythor avatar Tythor commented on April 28, 2024

Okay, I was about to suggest disabling the auto configuration for spring.threads.virtual.enabled=true in v3.3.x. This issue would likely be a problem that most users will not be aware of. Thanks!
cff1b33

from spring-boot.

wilkinsona avatar wilkinsona commented on April 28, 2024

It looks like Undertow may have a memory leak. @Tythor, can you please retry your scenario with Undertow downgraded to 2.3.10.Final and see if it helps?

from spring-boot.

Tythor avatar Tythor commented on April 28, 2024

Hi @wilkinsona, downgrading Undertow to 2.3.10.Final on Spring Boot v3.3.0-M2 did significantly slow the memory growth. However, I am still seeing suspicious behavior when providing an executor in the UndertowDeploymentInfoCustomizer. Although I'm not sure if it's due to a memory leak or just inefficient memory consumption within Undertow. Perhaps @mhalbritter may be able to diagnose the issue better with his plots. I also tried using 2.3.11.Final and encountered the same issue as the one in 2.3.12.Final.

from spring-boot.

Tythor avatar Tythor commented on April 28, 2024

Thank you @mhalbritter, these plots are very similar to the results I observed as well.

from spring-boot.

cassiusvm avatar cassiusvm commented on April 28, 2024

Hello,

I am using Spring Boot 3.2.4 and Undertow with OpenJDK by Corretto, I don't have this issue.

My application is an API REST, it is running by several days.

from spring-boot.

wilkinsona avatar wilkinsona commented on April 28, 2024

@cassiusvm How have you configured Undertow to use virtual threads?

from spring-boot.

cassiusvm avatar cassiusvm commented on April 28, 2024

@wilkinsona Right, using the @configuration tip in a Docker image jelastic maven 3.9.5 OpenJDK 21.

My bad, I said Correto.

from spring-boot.

time4tea avatar time4tea commented on April 28, 2024

I realise its not a spring issue per-se, but hoping that this is useful.

When configuring with

Xnio.getInstance().setExternalExecutorService(Executors.newVirtualThreadPerTaskExecutor())

The service will quickly blow up.. I think due to use of ThreadLocal buffer strategy.

image

In DefaultByteBufferPool, the following line might cause a problem with virtual threads:

ThreadLocalData get() {
            return localsByThread.get(Thread.currentThread());
        }

It is possible to make it used unpooled buffers, with

io.undertow.Undertow.builder().setByteBufferPool(XnioByteBufferPool(Pool.DIRECT))

but the performance of this is worse (by 50%) than the default, so no point.

Anyhow, hope thats of some use if somebody is looking into this.

from spring-boot.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.