From d478c407cd6180c95edc392d7b34ad5dbc93d30f Mon Sep 17 00:00:00 2001 From: Vasiliy Mikhailov Date: Wed, 24 Jun 2026 06:20:31 +0300 Subject: [PATCH] Fix FlowRuleComparator violating the Comparator contract FlowRuleComparator.compare returned 0 (equal) for rules that are not equal: - when one limitApp is null and the other is not (the early o1.getLimitApp() == null check returned 0 regardless of o2), and - when both limitApps are non-null, non-default and different (the final else returned 0). Returning 0 for unequal elements breaks the Comparator contract and can drop rules when they are stored in a sorted structure. Handle the null cases explicitly and compare two different non-default limitApps with String.compareTo. --- .../slots/block/flow/FlowRuleComparator.java | 10 ++++- .../block/flow/FlowRuleComparatorTest.java | 38 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java index e6caaff3d0..1045e2374e 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java @@ -37,9 +37,15 @@ public int compare(FlowRule o1, FlowRule o2) { return -1; } - if (o1.getLimitApp() == null) { + if (o1.getLimitApp() == null && o2.getLimitApp() == null) { return 0; } + if (o1.getLimitApp() == null) { + return 1; + } + if (o2.getLimitApp() == null) { + return -1; + } if (o1.getLimitApp().equals(o2.getLimitApp())) { return 0; @@ -50,7 +56,7 @@ public int compare(FlowRule o1, FlowRule o2) { } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(o2.getLimitApp())) { return -1; } else { - return 0; + return o1.getLimitApp().compareTo(o2.getLimitApp()); } } diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparatorTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparatorTest.java index 808bf515c8..1143964576 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparatorTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparatorTest.java @@ -42,4 +42,42 @@ private void assertOrderEqual(int size, List expected, List assertEquals(expected.get(i), actual.get(i)); } } + + @Test + public void testNullVsNonNullLimitApp() { + FlowRuleComparator comparator = new FlowRuleComparator(); + + // o1 has null limitApp, o2 has non-null limitApp + FlowRule ruleNull = new FlowRule("abc"); + ruleNull.setLimitApp(null); + FlowRule ruleNonNull = new FlowRule("abc"); + ruleNonNull.setLimitApp("originA"); + + // Comparator should NOT return 0 when one is null and the other is not + int result = comparator.compare(ruleNull, ruleNonNull); + assertNotEquals("null vs non-null should not be equal", 0, result); + + // Symmetry: compare(o2, o1) should be -compare(o1, o2) + int reverseResult = comparator.compare(ruleNonNull, ruleNull); + assertEquals("comparator must be antisymmetric", -result, reverseResult); + } + + @Test + public void testDifferentNonDefaultLimitApps() { + FlowRuleComparator comparator = new FlowRuleComparator(); + + // Two rules with different non-DEFAULT limitApp values + FlowRule ruleA = new FlowRule("abc"); + ruleA.setLimitApp("originA"); + FlowRule ruleB = new FlowRule("abc"); + ruleB.setLimitApp("originB"); + + // Different apps should NOT compare as equal (0) + int result = comparator.compare(ruleA, ruleB); + assertNotEquals("different non-default apps should not be equal", 0, result); + + // Symmetry check + int reverseResult = comparator.compare(ruleB, ruleA); + assertEquals("comparator must be antisymmetric", -result, reverseResult); + } } \ No newline at end of file