FINERACT-2449: Replace unbounded SimpleAsyncTaskExecutor with ThreadPoolTaskExecutor

This commit is contained in:
DeathGun44
2026-02-20 09:22:35 +00:00
committed by Adam Saghy
parent 9c9c90d2af
commit 045e3fe6ab
2 changed files with 362 additions and 12 deletions
@@ -19,34 +19,68 @@
package org.apache.fineract.infrastructure.core.config;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor;
@Configuration
public class SpringConfig {
private static final int AWAIT_TERMINATION_SECONDS = 60;
private static final int DEFAULT_QUEUE_CAPACITY = 100;
@Bean(name = "fineractEventExecutor")
public ThreadPoolTaskExecutor fineractEventExecutor(ThreadPoolTaskExecutorBuilder builder,
@Value("${spring.task.execution.pool.core-size:-1}") int configuredCore,
@Value("${spring.task.execution.pool.max-size:-1}") int configuredMax,
@Value("${spring.task.execution.pool.queue-capacity:-1}") int configuredQueueCapacity) {
int cpus = Runtime.getRuntime().availableProcessors();
int smartCore = cpus * 2;
int smartMax = cpus * 5;
int coreSize = configuredCore > 0 ? configuredCore : smartCore;
int rawMaxSize = configuredMax > 0 ? configuredMax : smartMax;
int queueCapacity = configuredQueueCapacity >= 0 ? configuredQueueCapacity : DEFAULT_QUEUE_CAPACITY;
int finalMaxSize = Math.max(rawMaxSize, coreSize);
ThreadPoolTaskExecutor executor = builder.threadNamePrefix("FineractEvent-").corePoolSize(coreSize).maxPoolSize(finalMaxSize)
.queueCapacity(queueCapacity).build();
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(AWAIT_TERMINATION_SECONDS);
return executor;
}
@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster() {
SimpleApplicationEventMulticaster saem = new SimpleApplicationEventMulticaster();
saem.setTaskExecutor(new SimpleAsyncTaskExecutor());
return saem;
@DependsOn("overrideSecurityContextHolderStrategy")
public SimpleApplicationEventMulticaster applicationEventMulticaster(
@Qualifier("fineractEventExecutor") ThreadPoolTaskExecutor taskExecutor) {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(new DelegatingSecurityContextAsyncTaskExecutor(taskExecutor));
return multicaster;
}
// The application events (for importing) rely on the inheritable thread local security context strategy
// This is NOT compatible with threadpools so if we use threadpools the below will need to be reworked
@Bean
public MethodInvokingFactoryBean overrideSecurityContextHolderStrategy() {
MethodInvokingFactoryBean mifb = new MethodInvokingFactoryBean();
mifb.setTargetClass(SecurityContextHolder.class);
mifb.setTargetMethod("setStrategyName");
mifb.setArguments("MODE_INHERITABLETHREADLOCAL");
return mifb;
MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();
factoryBean.setTargetClass(SecurityContextHolder.class);
factoryBean.setTargetMethod("setStrategyName");
factoryBean.setArguments(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
return factoryBean;
}
@Bean