Skip to content

@DirtiesContext, @Async and @EnableAsync(mode = AdviceMode.ASPECTJ) #30229

Closed as not planned
@kicktipp

Description

@kicktipp

Affects: <6.0.6>


This is my AsyncConfig with Spring Boot. We use Compile Time Weaving with spring-aspects

@Configuration
@EnableAsync(mode = AdviceMode.ASPECTJ)
public class AsyncConfig implements AsyncConfigurer {
    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setThreadNamePrefix("AsyncExecutor-");
        threadPoolTaskExecutor.setCorePoolSize(15);
        threadPoolTaskExecutor.setAwaitTerminationSeconds(10);
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }

    @Bean
    public Executor taskExecutor() {
        var delegatingSecurityContextAsyncTaskExecutor = new DelegatingSecurityContextAsyncTaskExecutor(threadPoolTaskExecutor());
        return delegatingSecurityContextAsyncTaskExecutor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncExceptionHandler();
    }
}

When I have a test like this:

   @Test
   @DirtiesContext
   public void test1() {
         myservice.doSomethingWithAsync()
   }

   @Test
   @DirtiesContext
   public void test2() {
         myservice.doSomethingWithAsync()
   }

I get an error in the second test:

2023-03-29T11:50:09.788+02:00 ERROR 72798 --- [o-auto-6-exec-1] de.kicktipp.core.mailutils.MailHelper : Executor [java.util.concurrent.ThreadPoolExecutor@1294ca02[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 13]] did not accept task: org.springframework.scheduling.aspectj.AbstractAsyncExecutionAspect$AbstractAsyncExecutionAspect$1@2283d58f

Debugging it I found that AsyncExecutionAspectSupport holds a reference to executors in line 76:

private final Map<Method, AsyncTaskExecutor> executors = new ConcurrentHashMap<>(16);

This AsyncExecutionAspectSupport is not reloaded when the context is reloaded. It holds a reference to the old executor from the first context. This executor is of course terminated and should not be used.

Workaround:

import org.springframework.beans.factory.DisposableBean;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
import java.util.Map;

@Component
public class DirtiesContextWithAsyncAspectJFix implements DisposableBean {

    @Override
    public void destroy() {
        try {
            var asyncSupport = AnnotationAsyncExecutionAspect.aspectOf();
            var field = ReflectionUtils.findField(asyncSupport.getClass(), "executors");
            assert field != null;
            field.setAccessible(true);
            @SuppressWarnings("unchecked")
            var executors = (Map<Method, AsyncTaskExecutor>) field.get(asyncSupport);
            executors.clear();
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

AsyncExecutionAspectSupport should reset its executors cache on context shutdown.

Metadata

Metadata

Assignees

No one assigned

    Labels

    in: testIssues in the test modulestatus: duplicateA duplicate of another issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions