userM1433372
userM1433372

Reputation: 5487

How to limit the number of Spring @Async tasks with virtual threads enabled

I have an @Async task executing some database related background work. After enabling virtual threads (spring.threads.virtual.enabled: true) the number of Async jobs executed in parallel is no longer limited. The result is an exhausted database connection pool. When virtual threads is disabled only 8 (default) jobs are executed in parallel so less database connections are used.

What would be the recommended approach to solve this problem?

org.apache.tomcat.jdbc.pool.PoolExhaustedException: [task-49889] Timeout: Pool empty. Unable to fetch a connection in 30 seconds, none available[size:100; busy:100; idle:0; lastwait:30000]

@Component
@RequiredArgsConstructor
public class TestTask {

    private final MyRepository myRepository;
    @Async
    public void execute() {
        // some database calls.
        myRepository.doSomething();
    }
}

When executing 200 tasks the pool is exhausted.

IntStream.range(1, 200).forEach(value -> testTask.execute());

I'm using Java 21 with Spring Boot 3.2.1.

Upvotes: 6

Views: 3274

Answers (2)

M. Deinum
M. Deinum

Reputation: 124441

You can configure concurrency in this case through the spring.task.execution.simple.concurrency-limit property. Default it isn't set and thus means unbounded.

Setting it in your application properties.

spring.task.execution.simple.concurrency-limit=8

This will limit it to 8 concurrent tasks (or the number you choose it to be). The auto-configuration takes care of this.

Upvotes: 6

Andrei Lisa
Andrei Lisa

Reputation: 4896

According to documentation about Virtual Threads you have to use Semaphore to Limit Concurrency :

Sometimes there is a need to limit the concurrency of a certain operation. For example, some external service may not be able to handle more than ten concurrent requests. Because platform threads are a precious resource that is usually managed in a pool, thread pools have become so ubiquitious that they're used for this purpose of restricting concurrency, like in the following example:

As an example something like next one approach:

Semaphore sem = new Semaphore(10);
...
Result foo() {
    sem.acquire();
    try {
        return callLimitedService();
    } finally {
        sem.release();
    }
}

Because When using virtual threads, if you want to limit the concurrency of accessing some service, you should use a construct designed specifically for that purpose: the Semaphore class.

Upvotes: 3

Related Questions