Copilot commented on code in PR #7672:
URL: https://github.com/apache/shenyu/pull/7672#discussion_r4291630700


##########
shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/CommonPluginDataSubscriber.java:
##########
@@ -160,26 +162,59 @@ public void applyPluginDataSnapshot(final String 
namespaceId, final List<PluginD
 
     @Override
     public void applySelectorDataSnapshot(final String namespaceId, final 
List<SelectorData> dataList) {
-        Set<String> selectorIds = 
dataList.stream().map(SelectorData::getId).collect(Collectors.toSet());
-        List<SelectorData> stale = 
BaseDataCache.getInstance().getSelectorMap().values().stream()
+        final BaseDataCache cache = BaseDataCache.getInstance();
+        final Set<String> selectorIds = 
dataList.stream().map(SelectorData::getId).collect(Collectors.toSet());
+        final List<SelectorData> stale = 
cache.getSelectorMap().values().stream()
                 .flatMap(List::stream)
                 .filter(data -> namespaceId.equals(data.getNamespaceId()))
                 .filter(data -> !selectorIds.contains(data.getId()))
                 .collect(Collectors.toList());
-        dataList.forEach(this::onSelectorSubscribe);
-        stale.forEach(this::unSelectorSubscribe);
+        // Merge and sort once per affected plugin bucket instead of per 
selector row.
+        if (!dataList.isEmpty()) {
+            cache.refreshSelectorData(dataList);
+        }
+        if (!stale.isEmpty()) {
+            cache.removeSelectDataBatch(stale);

Review Comment:
   Both snapshot methods delete all stale rows before invoking any handlers. If 
an incoming handler throws during InitialSyncApplication, execution stops 
before the stale removal callbacks and match-cache invalidation. A retry cannot 
rediscover those deleted rows, so cleanup remains skipped even after a 
successful retry—for example, Divide's selector upstream entries and gRPC's 
rule handles remain cached. Previously, an incoming-handler failure occurred 
before stale deletion. Keep pending removals retryable, or finish the cleanup 
and invalidation before propagating the failure. Add failure-then-retry tests 
for both snapshot methods.



##########
shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/CommonPluginDataSubscriber.java:
##########
@@ -160,26 +162,59 @@ public void applyPluginDataSnapshot(final String 
namespaceId, final List<PluginD
 
     @Override
     public void applySelectorDataSnapshot(final String namespaceId, final 
List<SelectorData> dataList) {
-        Set<String> selectorIds = 
dataList.stream().map(SelectorData::getId).collect(Collectors.toSet());
-        List<SelectorData> stale = 
BaseDataCache.getInstance().getSelectorMap().values().stream()
+        final BaseDataCache cache = BaseDataCache.getInstance();
+        final Set<String> selectorIds = 
dataList.stream().map(SelectorData::getId).collect(Collectors.toSet());
+        final List<SelectorData> stale = 
cache.getSelectorMap().values().stream()
                 .flatMap(List::stream)
                 .filter(data -> namespaceId.equals(data.getNamespaceId()))
                 .filter(data -> !selectorIds.contains(data.getId()))
                 .collect(Collectors.toList());
-        dataList.forEach(this::onSelectorSubscribe);
-        stale.forEach(this::unSelectorSubscribe);
+        // Merge and sort once per affected plugin bucket instead of per 
selector row.
+        if (!dataList.isEmpty()) {
+            cache.refreshSelectorData(dataList);
+        }
+        if (!stale.isEmpty()) {
+            cache.removeSelectDataBatch(stale);
+        }
+        // Preserve per-item plugin handler callbacks and the initial-sync 
failure semantics
+        // that the previous per-row subscribe path provided.
+        dataList.forEach(data -> runGuarded(data, () -> 
Optional.ofNullable(handlerMap.get(data.getPluginName()))
+                .ifPresent(handler -> handler.handlerSelector(data))));
+        stale.forEach(data -> runGuarded(data, () -> {
+            // the concurrent handler map rejects null keys, and a dangling 
selector carries no plugin name
+            if (StringUtils.isNotBlank(data.getPluginName())) {
+                Optional.ofNullable(handlerMap.get(data.getPluginName()))
+                        .ifPresent(handler -> handler.removeSelector(data));
+            }
+        }));
+        invalidateSelectorMatchCacheByPlugins(collectAffectedPlugins(dataList, 
stale, SelectorData::getPluginName));
     }
 
     @Override
     public void applyRuleDataSnapshot(final String namespaceId, final 
List<RuleData> dataList) {
-        Set<String> ruleIds = 
dataList.stream().map(RuleData::getId).collect(Collectors.toSet());
-        List<RuleData> stale = 
BaseDataCache.getInstance().getRuleMap().values().stream()
+        final BaseDataCache cache = BaseDataCache.getInstance();
+        final Set<String> ruleIds = 
dataList.stream().map(RuleData::getId).collect(Collectors.toSet());
+        final List<RuleData> stale = cache.getRuleMap().values().stream()
                 .flatMap(List::stream)
                 .filter(data -> namespaceId.equals(data.getNamespaceId()))
                 .filter(data -> !ruleIds.contains(data.getId()))
                 .collect(Collectors.toList());
-        dataList.forEach(this::onRuleSubscribe);
-        stale.forEach(this::unRuleSubscribe);
+        // Merge and sort once per affected selector bucket instead of per 
rule row.
+        if (!dataList.isEmpty()) {
+            cache.refreshRuleData(dataList);
+        }
+        if (!stale.isEmpty()) {
+            cache.removeRuleDataBatch(stale);
+        }
+        dataList.forEach(data -> runGuarded(data, () -> 
Optional.ofNullable(handlerMap.get(data.getPluginName()))
+                .ifPresent(handler -> handler.handlerRule(data))));
+        stale.forEach(data -> runGuarded(data, () -> {
+            if (StringUtils.isNotBlank(data.getPluginName())) {
+                Optional.ofNullable(handlerMap.get(data.getPluginName()))
+                        .ifPresent(handler -> handler.removeRule(data));
+            }
+        }));
+        invalidateRuleMatchCacheByPlugins(collectAffectedPlugins(dataList, 
stale, RuleData::getPluginName));

Review Comment:
   A stale rule can have a blank pluginName because BaseDataCache keys rules by 
selectorId. The batch removes that rule, but collectAffectedPlugins drops its 
blank name, leaving positive matches for its id under the former plugin 
untouched. The previous unRuleSubscribe path used the id-based cross-plugin 
invalidation fallback. Preserve that fallback for stale rules whose plugin name 
is unavailable.



##########
shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/CommonPluginDataSubscriber.java:
##########
@@ -160,26 +162,59 @@ public void applyPluginDataSnapshot(final String 
namespaceId, final List<PluginD
 
     @Override
     public void applySelectorDataSnapshot(final String namespaceId, final 
List<SelectorData> dataList) {
-        Set<String> selectorIds = 
dataList.stream().map(SelectorData::getId).collect(Collectors.toSet());
-        List<SelectorData> stale = 
BaseDataCache.getInstance().getSelectorMap().values().stream()
+        final BaseDataCache cache = BaseDataCache.getInstance();
+        final Set<String> selectorIds = 
dataList.stream().map(SelectorData::getId).collect(Collectors.toSet());
+        final List<SelectorData> stale = 
cache.getSelectorMap().values().stream()
                 .flatMap(List::stream)
                 .filter(data -> namespaceId.equals(data.getNamespaceId()))
                 .filter(data -> !selectorIds.contains(data.getId()))
                 .collect(Collectors.toList());
-        dataList.forEach(this::onSelectorSubscribe);
-        stale.forEach(this::unSelectorSubscribe);
+        // Merge and sort once per affected plugin bucket instead of per 
selector row.
+        if (!dataList.isEmpty()) {
+            cache.refreshSelectorData(dataList);

Review Comment:
   Both snapshot paths now call helpers that copy the entire selector/rule map 
and later replace its reference. An incremental subscribe or unsubscribe that 
completes after the copy can be overwritten at publication, even in an 
unrelated plugin or selector bucket. The previous per-row snapshot writes used 
live-map computes and preserved those unrelated updates; the existing 
refresh-path race does not make this new usage safe. Use grouped computes on 
the live map, or coordinate publication with all writers. Add deterministic 
snapshot-versus-incremental tests for both selectors and rules.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to