Skip to content

[ISSUE #7981] Fix dynamic match cache eligibility - #7990

Closed
stone-98 wants to merge 2 commits into
apache:masterfrom
stone-98:codex/fix-7981-selector-cache
Closed

stone-98 wants to merge 2 commits into
apache:masterfrom
stone-98:codex/fix-7981-selector-cache

Conversation

@stone-98

Copy link
Copy Markdown

Fixes #7981

Summary

  • cache selector matches only when every enabled candidate is determined by URI conditions
  • apply the same eligibility rule to rule match caching
  • add regression coverage for dynamic header-based selector and rule candidates

Testing

  • ./mvnw -pl shenyu-plugin/shenyu-plugin-base -am -Dtest=AbstractShenyuPluginTest -Dsurefire.failIfNoSpecifiedTests=false test (16 tests)
  • ./mvnw -pl shenyu-plugin/shenyu-plugin-base -am test (299 tests)

Make sure that:

  • You have read the contribution guidelines.
  • You submit test cases (unit or integration tests) that back your changes.
  • Your local test passed ./mvnw clean install -Dmaven.javadoc.skip=true.

@Aias00

Aias00 commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

pls add my wechat: aias00

@Aias00 Aias00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed commit e7e1e83.

The candidate-set eligibility check is a sound conservative fix for #7981: a URI-only generic match is no longer cached when another enabled candidate depends on request headers. Applying the same guard to selector/rule and positive/negative cache writes is consistent. I found no blocking regression in the validation performed.

Validation:

  • Ran ./mvnw -pl shenyu-plugin/shenyu-plugin-base -am test -Drat.skip=true -Djacoco.skip=true: BUILD SUCCESS, including all 299 shenyu-plugin-base tests and Checkstyle.
  • Re-ran the original in-process reproduction: a VIP request correctly selects the more specific selector after an ordinary request; the dynamic selector negative-cache scenario also behaves correctly.

Two non-blocking suggestions:

  1. Add assertions that URI-only candidate sets still populate/reuse the match cache, and that a disabled header-based candidate does not prevent caching. The new tests cover correct dynamic matching but do not protect these eligibility boundaries.
  2. Document the deliberate performance tradeoff: one enabled dynamic candidate disables final-result caching for the whole candidate set. This is preferable to incorrect routing, but mixed configurations may perform more matching work.

#7976 and #7978 remain separate pre-existing issues and are not regressions introduced here.

Review limitation: independent reviewer agents could not start because of a service-side model compatibility error. This is a comment review, not a formal approval. No GitHub CI check results were available when reviewed.

@Aias00 Aias00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the more correct of the two open fixes, but it duplicates #7984 - please consolidate

Same root cause as #7977/#7981, and there are now two PRs touching the same four call sites in AbstractShenyuPlugin:

#7984 (approved) this PR
negative (empty) cache gated on a path-determined candidate set gated on a path-determined candidate set
positive (matched) cache still unconditional gated
matchRestful included in the predicate not considered
selector type scoping only CUSTOM_FLOW selectors are inspected every enabled selector is inspected

Your new test selectorCacheShouldNotShadowDynamicCandidate demonstrates something #7984 does not fix: the positive entry is equally unsound. Request 1 (X-Tenant: b) uniquely matches generic, that result is cached under the path, and request 2 (X-Tenant: a) then gets generic from the cache and never evaluates tenant. So caching a positive match is only safe when the candidate set is path-determined too - which is what canCacheSelectorMatch adds. That makes this PR the stronger fix, and it means merging #7984 alone would leave the bug half-open.

Two things to add before this is equivalent-or-better than #7984, then one of the two PRs should be closed:

  1. matchRestful. isPathDeterminedSelectorSet / isPathDeterminedRuleSet in #7984 also reject a candidate when getMatchRestful() is true. A restful selector's URI condition is a template, so it matches many paths; caching by exact path is a different (narrower) key than what was evaluated. Include it for the same reason.
  2. Selector type scoping. filterSelector only evaluates conditions for SelectorTypeEnum.CUSTOM_FLOW; for other types it returns true regardless of the condition list. Your canCacheSelectorMatch inspects every enabled selector's conditions, so a full-flow selector that happens to carry a header condition (never evaluated) disables caching for the whole plugin. Harmless but pessimistic; scoping to CUSTOM_FLOW keeps the previous hit rate.

Also worth a comment in hasOnlyUriConditions about the empty-condition case: CollectionUtils.isNotEmpty(conditions) returns false, which disables caching. That matches filterSelector behaviour for CUSTOM_FLOW (empty conditions never match), but it reads like an oversight.

CI: no checks reported on this head yet, so this review is based on the diff only.

@Aias00

Aias00 commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Thanks for completing the guards — matchRestful, the CUSTOM_FLOW scope and the rule path are all in now, which is exactly what was missing in the first revision.

However, #7984 has converged to the same change. It gates positive and negative entries for both the selector and the rule cache, and additionally requires the selector set to be path-determined for the rule cache. The two PRs touch the same four call sites in AbstractShenyuPlugin and are functionally equivalent, so they conflict textually and only one should land.

I have approved #7984 as the one to take: it has the stronger positive/negative control coverage (...StillAppliesToPathOnlyCandidates for all four branches) and uses Objects.equals rather than an Integer comparison.

Before it is merged, please consider porting your two genuinely better tests over to #7984:

With those two ported, #7984 becomes a superset of this PR, so closing this one as a duplicate is the cleanest path. Ping me if you would rather it were the other way round — happy to re-review either direction.

@Aias00

Aias00 commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Closing in favour of #7984, which is the one we are landing. Thank you for the completeness of this revision — the matchRestful branch, the CUSTOM_FLOW scope and the rule-cache path were exactly the missing pieces, and the review of this PR is what drove #7984 to cover positive entries as well.

Before closing, I have asked the #7984 author to port the two things here that are genuinely better than what #7984 has:

  • restfulSelectorCandidateShouldPreventResultCaching / restfulRuleCandidateShouldPreventResultCaching — the only coverage of the matchRestful branch.
  • the verify(candidate, never()).getConditionList() assertion, which proves the cache is actually hit rather than merely populated.

So your test work is not lost. Thanks again.

@Aias00 Aias00 closed this Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] URI-only positive match cache shadows more specific selectors with dynamic conditions

2 participants