Skip route refresh after the context starts shutting down#4160
Open
PreAgile wants to merge 1 commit into
Open
Conversation
CachingRouteLocator listens for RefreshRoutesEvent and delegates to its RouteLocator on every event. RefreshRoutesEvent can be published during the destroy phase of the ApplicationContext -- for example, ConsulServiceRegistry.deregister() runs as a destroy callback and emits one, at which point any bean lookup performed transitively by the delegate fails with BeanCreationNotAllowedException, as in the original report (RouteDefinitionRouteLocator requesting `defaultValidator` to validate RouteDefinitions). AbstractApplicationContext.isActive() cannot guard this path because active is only set to false at the very end of doClose(), after destroyBeans() has already run. The first signal that close has begun is ContextClosedEvent, which doClose() publishes before destroyBeans(). Register a one-shot ContextClosedEvent listener on the ApplicationEventPublisher when it is a ConfigurableApplicationContext, flip a volatile flag from it, and short-circuit onApplicationEvent(RefreshRoutesEvent) when the flag is set. CachingRouteLocatorShutdownTests reproduces the issue deterministically by registering a DisposableBean whose destroy() publishes a RefreshRoutesEvent -- the same timing exercised by the Consul registry shutdown path. Existing CachingRouteLocatorTests keep passing because they wire a lambda as the event publisher, so the new instanceof branch is skipped and the guard remains inactive, preserving the original behaviour for non-context publishers. Fixes spring-cloudgh-2471 Signed-off-by: Meyon Soo Kim <39788337+PreAgile@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stops
CachingRouteLocatorfrom delegating to the wrappedRouteLocatoronce theApplicationContexthas begun closing. The listener was firing onRefreshRoutesEventinstances published during the destroy phase (typically byConsulServiceRegistry.deregister()), which led the delegate path to ask theBeanFactoryfor beans that were already being destroyed and produced theBeanCreationNotAllowedExceptionreported in gh-2471.Why
isActive()is not enoughThe first instinct is
AbstractApplicationContext.isActive(), butactiveonly flips tofalseat the very end ofdoClose()— afterdestroyBeans()has already run. The first signal available isContextClosedEvent, published in step 1 ofdoClose(), before any destroy callback fires.The fix
When the event publisher is a
ConfigurableApplicationContext, register a smallContextClosedEventlistener that flips avolatile boolean contextClosingflag, and short-circuitonApplicationEvent(RefreshRoutesEvent)when the flag is set.+12 / -2 in
CachingRouteLocator.java. No interfaces added. The existingCachingRouteLocatorTestskeep passing because they wire a lambda publisher, so the newinstanceofbranch is skipped and the guard stays inactive — the original behaviour is preserved for non-context publishers.Reproduction
CachingRouteLocatorShutdownTestsmakes the race deterministic by registering aDisposableBeanwhosedestroy()publishes aRefreshRoutesEvent— the same timing the Consul registry exercises:Without the guard the test fails (
expected: 1 but was: 2); with the guard it passes. The broader Route/Routing test set inspring-cloud-gateway-server-webflux(219 tests) stays green.Happy to open a 4.3.x backport once this lands.
Fixes gh-2471