Repository navigation
fix(plugin): evict cached fault-tolerance handles when explicitly cleared (#7914) - #7925
Conversation
Aias00
left a comment
There was a problem hiding this comment.
The fix itself is correct and the three handlers are now consistent in intent. There is one blocker, though: the build is red because of a Checkstyle violation in the new test, so this cannot be merged as is.
Blocking — CI is red on this PR (not on master)
Both failures come from the same violation introduced by this PR:
pr_build -> FAILURE
[ERROR] Resilience4JHandlerTest.java:65:9: Distance between variable 'cache' declaration
and its first usage is 5, but allowed 3. Consider making that variable final ...
[ERROR] Failed to execute goal maven-checkstyle-plugin:3.4.0:check (validate)
on project shenyu-plugin-resilience4j: You have 1 Checkstyle violation.
build (shenyu-integrated-test-k8s-gateway-api-http) -> FAILURE
(same violation; the reactor stops at shenyu-plugin-resilience4j)
testHandlerRuleExplicitClearEvictsPreviousRule declares the supplier at the top of the method and only touches it five statements later:
Supplier<CommonHandleCache<String, Resilience4JHandle>> cache = Resilience4JHandler.CACHED_HANDLE;
ruleData.setSelectorId("1");
ruleData.setId("clear-test");
ruleData.setHandle("{\"urlPath\":\"test\"}");
resilience4JHandler.handlerRule(ruleData);
Assertions.assertNotNull(cache.get().obtainHandle("1_clear-test"));Either mark it final (final Supplier<CommonHandleCache<String, Resilience4JHandle>> cache = ... — the rule is configured with ignoreFinal, which is why the message suggests it) or move the declaration down to just above its first use. The latter reads better here since the three setXxx calls do not depend on it.
What I verified as correct
- Eviction is the right lever, because all three plugins fail open when the handle is absent.
RateLimiterPlugin:75,SentinelPlugin:60andResilience4JPlugin:67all doif (Objects.isNull(handle)) { return chain.execute(exchange); }. So removing the cached handle stops enforcement without turning the request into a 500 — which is exactly the semantics "an explicitly cleared handle" should have. I checked all three rather than assuming, since a plugin that dereferenced the handle would have made this fix a 500-generator. - Sentinel delegating to
removeRule(ruleData)is the strongest part of the change.removeRulealready does cache eviction plusFlowRuleManager/DegradeRuleManagerfiltering insidesynchronized (RULE_UPDATE_LOCK), so the clear path shares the same lock as the update path instead of being a second, unlocked mutation of the global Sentinel rule sets. Re-implementing the eviction inline would have raced againsthandlerRuleShouldNotLoseConcurrentUpdates. Resilience4JHandlertears down the registry before the blank check, so a cleared handle drops the circuit breaker / bulkhead / rate limiter instances as well as the cached handle — matching whatremoveRuledoes.- The Sentinel NPE was real before this change:
GsonUtils.fromJson("")returnsnull, so the oldsentinelHandle.checkData()threw on the sync thread.SentinelPlugin:63also callscheckData(), so a stale entry could have kept failing per request. - Global test state is handled:
SentinelRuleHandleTest's@BeforeEachclears both Sentinel rule managers, soassertEquals(1, FlowRuleManager.getRules().size())is not order-dependent.
Non-blocking
nulland""now behave differently, in all three handlers. The blank branch lives insideOptional.ofNullable(ruleData.getHandle()).ifPresent(...), so""and" "evict while anullhandle silently does nothing. Whether a cleared handle reaches the plugin as""or asnulldepends on the admin column (''vsNULL) and on the serializer, which makes the fix's coverage depend on data shape rather than on intent. Since the issue is about a cleared handle, I would treatnullas a clear too — a rule whose handle disappeared should not keep enforcing the previous one — or add a test that pins the current choice deliberately.removeRulestill has the same defect the PR is fixing, for the other two handlers.RateLimiterPluginDataHandler.removeRuleandResilience4JHandler.removeRuleboth wrapremoveHandleinifPresent(...), so deleting a rule whose handle is alreadynullleaves the cached handle enforcing forever. The new blank branch avoids this by callingCACHED_HANDLE.get().removeHandle(...)directly, which is right — but it means the two paths now disagree. FixingremoveRulethe same way would make this class of bug disappear and would lethandlerRule's blank branch simply delegate to it, like Sentinel does.Resilience4JHandler's new import,org.apache.commons.lang3.StringUtils, is inserted afterorg.apache.shenyu.common.enums.PluginEnum. The other two files putorg.apache.commonsfirst; Checkstyle does not enforce the order here, but the file now groups imports differently from its siblings.Resilience4JHandler.handlerRulecallsResilience4JRegistryFactory.remove(key)on every update, so each rule change resets that key's breaker/bulkhead/limiter state. That is pre-existing and out of scope, but the new clear branch makes it worth a one-line comment that the reset is intentional rather than an accident of the clear path.- The RateLimiter and Resilience4J tests leave their
clear-testkey cached when they finish (the last assertion re-caches it). Harmless because the key is test-specific, but an@AfterEachremoveHandle("1_clear-test")would keep the static caches as clean as the Sentinel test's rule managers.
|
All addressed. The blocker is gone: the On the non-blocking points, I went with your lean:
Plus the smaller items:
|
Aias00
left a comment
There was a problem hiding this comment.
Review: #7925 fix(plugin): evict cached fault-tolerance handles on rule/selector delete (re-review)
Verdict: approved — Checkstyle violation resolved, null and blank handle evictions unified across all fault-tolerance plugins, and all CI suites are green.
What was verified in this update
- Checkstyle compliance:
- Fixed variable distance violation in
Resilience4JHandlerTest.testHandlerRuleExplicitClearEvictsPreviousRuleby declaringfinal Supplier<...> cachedirectly adjacent to usage.
- Fixed variable distance violation in
- Unified null/blank handle eviction semantics:
- Across
RateLimiterPluginDataHandler,Resilience4JHandler, andSentinelRuleHandle,StringUtils.isBlank(ruleData.getHandle())now covers both empty strings andnullvalues consistently, immediately delegating toremoveRule(ruleData). removeRule(...)unbindsremoveHandlefromOptional.ofNullable(ruleData.getHandle()), ensuring that deleting a rule with a null handle reliably cleans up cached handles without leaking state.
- Across
- Comprehensive test assertions & cleanup:
- Added unit tests for null handle evictions:
handlerRuleNullHandleEvictsPreviousRule&removeRuleWithoutHandleStillEvictsin RateLimiter.testHandlerRuleNullHandleEvictsPreviousRule&testRemoveRuleWithoutHandleStillEvictsin Resilience4J.handlerRuleNullHandleEvictsCacheAndEnforcementin Sentinel.
- Added
@AfterEachteardown in test classes to keep static handle caches clean.
- Added unit tests for null handle evictions:
- CI Status:
- All 18 checks succeeded (including
pr_build, Checkstyle, rat_check, and CodeQL), 0 failures.
- All 18 checks succeeded (including
Fixes #7914.
Summary
Admin accepts a rule update whose
handleis an explicit empty string, and Gson parses that input tonull. The fault-tolerance handlers then diverge:Resilience4Jremoves the registry but leaves the previous cached handle in place (the next request NPEs inResilience4JBuilderre-parsing the blank raw handle),RateLimiterkeeps the previous handle enforcing inCACHED_HANDLE, andSentinelNPEs inhandlerRulebefore updating anything.Modifications
nullhandle (omitted field of a selective update) keeps its existing semantics.Resilience4JHandler/RateLimiterPluginDataHandler: evict the cached handle; both plugins already pass through when the cached handle is absent, so the stale enforcement and theResilience4JBuilderNPE are both gone.SentinelRuleHandle: delegate toremoveRule, which evicts the cached handle and strips the rule's flow/degrade entries from the Sentinel rule managers; the parse /checkData/ reload path only runs for non-blank handles.Verifying this change
Each handler test: valid handle cached →
handlerRule("")evicts it (fails on the previous code with the issue's stale handle /NullPointerException), whitespace also clears, and a subsequent valid update re-caches; Sentinel additionally asserts the flow/degrade rules for the key are stripped.Regression green:
Resilience4JPluginTest3/3,RateLimiterPluginTest4/4,SentinelPluginTest5/5.Checkstyle passes:
clean.