spring-projects / spring-projects/spring-framework

EnableAsync prevents EnableCaching from working in self-injection scenarios [SPR-15915]

Open
#20,469 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

in: core type: bug
Dominant language
Java
Stars
60.2k
Forks
38.8k
Avg merge
5d 2h
Merged PRs (30d)
27

Description

Henri Tremblay opened SPR-15915 and commented

If I use @Async and @CacheEvict on the same class, calling the method with @CacheEvict directly won't evict anymore.

Let's say I have the following class.

public class MyClass {

  private CountDownLatch latch;
  private MyClass meWithAProxy;

  @Autowired
  ApplicationContext applicationContext;

  @PostConstruct
  public void init() {
    meWithAProxy = applicationContext.getBean(MyClass.class);
  }

  public CountDownLatch getLatch() {
    return latch;
  }

  public void setLatch(CountDownLatch latch) {
    this.latch = latch;
  }

  @Async
  public void function1() {
    meWithAProxy.anotherFunction(123);

    if(latch != null) {
      latch.countDown();
    }
  }

  @CacheEvict(cacheNames = "cache", key = "#testId")
  public List<Integer> anotherFunction(int testId) {
    return Collections.emptyList();
  }

}

And I then use it like that:

@Configuration
@EnableCaching
@EnableAsync
public class App {

  public static void main(String[] args) throws InterruptedException {
    ApplicationContext context = new AnnotationConfigApplicationContext(App.class);
    CacheManager cacheManager = context.getBean(CacheManager.class);
    Cache cache = cacheManager.getCache("cache");

    MyClass myClass = context.getBean(MyClass.class);

    cache.put(123, "test"); // value to evict

    myClass.setLatch(new CountDownLatch(1));
    myClass.function1(); // this is correctly called asynchronously
    myClass.getLatch().await();
    assertThat(cache.get(123)).describedAs("Reentrant call failed").isNull(); // and the value is evicted as expected

    cache.put(1, "test"); // new value to evict
    assertThat(cache.get(1)).isNotNull();

    myClass.anotherFunction(1); // direct call
    assertThat(cache.get(1)).describedAs("Direct call failed").isNull(); // fails!
  }

  @Bean
  public MyClass myClass() {
    return new MyClass();
  }

  @Bean
  public TaskExecutor taskExecutor() {
    return new SimpleAsyncTaskExecutor();
  }

  @Bean
  public CacheManager cacheManager() {
    javax.cache.CacheManager cacheManager = Caching.getCachingProvider().getCacheManager();
    cacheManager.createCache("cache", new MutableConfiguration<>().setStoreByValue(false));
    return new JCacheCacheManager(cacheManager);
  }
}

For some reason, the cache interceptor is not there. It seems that the Advisors with the cache are replaced by the async ones but I don't know why.


Affects: 4.3.10

Reference URL: https://stackoverflow.com/questions/45938279/cache-not-refreshing-when-being-called-from-a-asynchrounous-function-in-spring/45963494#45963494

Issue Links:

  • #18488 Scheduled method is not invoked via proxy

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the @EnableAsync and @EnableCaching entry points and reproduce the behavior using the MyClass and App configuration shown in the issue. Trace how the async and cache advisors are applied, then verify that both the asynchronous call and the direct anotherFunction call evict the expected cache entry.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.