spring-cloud / spring-cloud/spring-cloud-commons

RefreshScope dispose beans in unpredictable order

Open
#433 4 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement waiting for votes
Dominant language
Java
Stars
751
Forks
744
Avg merge
1d 14h
Merged PRs (30d)
9

Description

This issue was discovered while investigating https://github.com/spring-cloud/spring-cloud-netflix/issues/3174#issuecomment-419349960. To summarise the original tread:

When disposed, RefreshScope starts by clearing its local cache of target beans then proceeds with their disposal. A problem arise when one of these beans makes a call to another refresh-scoped bean in its dispose method - that is the case of Netflix's DiscoveryClient for instance. Since the cache has already been cleared, a new instance of the proxied reference must be created which leads to the BeanCreationNotAllowedException.

RefreshScope extends GenericScope whose shutdown method looks like this: https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/GenericScope.java#L130-L134. As you can see it starts by clearing the local cache...

Furthermore, target beans are NOT disposed in an order that matches their dependencies. In fact, beans are disposed in the order they are returned by the StandardScopeCache which uses a ConcurrentHashMap underneath.

Changing the map implementation to preserve order will not be enough I'm afraid. Proxies are created in dependency order, but targets are created - and stored in the cache - when they are first invoked. To be 100% correct, the exact moment a target bean gets into the cache depends on:

  • RefreshScope is configured with eager initialization (true by default). In this case, target beans are created on ContextRefreshedEvent in the order given by context.getBeanDefinitionNames() (does it reflect the dependency order?). When eager=false, target beans are created at the very last moment on their first use.
  • whether the bean definition is @Lazyor not

A sample test app is available at https://github.com/brenuart/spring-cloud-commons/tree/netflix_gh3174 to illustrate the case. It is currently hosted in my own repo - please tell me if I should commit it elsewhere. You may not find all tests relevant - certainly in the light of the changes you foresee to solve this issue. Hope they will be useful anyway ;-)

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 shutdown method in spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/GenericScope.java and inspect how StandardScopeCache stores targets. Run or adapt the sample test app linked in the issue, including eager and lazy cases. Done means refresh-scoped beans dispose without BeanCreationNotAllowedException and in dependency-safe order.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
backend, cloud
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.