This is an automated email from the ASF dual-hosted git repository. dmsolr pushed a commit to branch fix/p2c-singleton-inflight in repository https://gitbox.apache.org/repos/asf/shenyu.git
commit a4dbcd6b9f7bef41c408b3d735f6389618621933 Author: Luke Haochao Zhuang <[email protected]> AuthorDate: Thu Oct 22 15:09:21 2026 +0800 fix: pair P2C inflight accounting for singleton upstream selection AbstractLoadBalancer.select short-circuits a singleton candidate list and returns the only upstream without calling P2cLoadBalancer.doSelect, so the inflight increment is skipped. Divide's P2C completion callback still decrements the selected upstream, so repeated singleton requests drive the inflight counter below its baseline and eventually negative, which P2C then uses in its load calculation. Override select in P2cLoadBalancer to perform the same increment (and lastPicked update) that doSelect does when the candidate list is a singleton, keeping start/completion accounting paired. Add regression tests covering the single increment and the paired select/decrement loop from the issue reproduction. --- .../shenyu/loadbalancer/spi/P2cLoadBalancer.java | 17 ++++++++++- .../loadbalancer/spi/P2cLoadBalancerTest.java | 34 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/spi/P2cLoadBalancer.java b/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/spi/P2cLoadBalancer.java index dd0edb7ceb..ddd0977599 100644 --- a/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/spi/P2cLoadBalancer.java +++ b/shenyu-loadbalancer/src/main/java/org/apache/shenyu/loadbalancer/spi/P2cLoadBalancer.java @@ -22,6 +22,7 @@ import org.apache.shenyu.loadbalancer.entity.Upstream; import org.apache.shenyu.spi.Join; import java.util.List; +import java.util.Objects; import java.util.Random; /** @@ -46,7 +47,21 @@ public class P2cLoadBalancer extends AbstractLoadBalancer { private static final int PICK_TIMES = 3; private final Random random = new Random(); - + + @Override + public Upstream select(final List<Upstream> upstreamList, final LoadBalanceData data) { + Upstream picked = super.select(upstreamList, data); + // AbstractLoadBalancer short-circuits a singleton candidate list before doSelect runs, so the + // inflight increment in doSelect is skipped while the P2C completion accounting (DividePlugin's + // response trigger) still decrements it. Pair the accounting here so repeated singleton + // selections keep inflight at its baseline instead of driving it negative. See #7974. + if (Objects.nonNull(picked) && upstreamList.size() == 1) { + picked.setLastPicked(System.currentTimeMillis()); + picked.getInflight().incrementAndGet(); + } + return picked; + } + @Override protected Upstream doSelect(final List<Upstream> upstreamList, final LoadBalanceData data) { long start = System.currentTimeMillis(); diff --git a/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/spi/P2cLoadBalancerTest.java b/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/spi/P2cLoadBalancerTest.java index 5b760949fc..a5ae73afcb 100644 --- a/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/spi/P2cLoadBalancerTest.java +++ b/shenyu-loadbalancer/src/test/java/org/apache/shenyu/loadbalancer/spi/P2cLoadBalancerTest.java @@ -63,4 +63,38 @@ public class P2cLoadBalancerTest { Upstream upstream1 = p2cLoadBalancer.doSelect(upstreamList, new LoadBalanceData()); Assertions.assertTrue(upstream.getUrl().equals("baidu.com") && upstream1.getUrl().equals("pro.jd.com")); } + + @Test + public void testSingletonSelectionIncrementsInflightOnce() { + Upstream upstream = Upstream.builder() + .url("a:80") + .protocol("http://") + .weight(100) + .status(true) + .build(); + final P2cLoadBalancer p2cLoadBalancer = new P2cLoadBalancer(); + long before = upstream.getInflight().get(); + Upstream picked = p2cLoadBalancer.select(List.of(upstream), new LoadBalanceData()); + Assertions.assertSame(upstream, picked); + Assertions.assertEquals(before + 1, upstream.getInflight().get()); + } + + @Test + public void testSingletonSelectionStaysPairedWithCompletionDecrement() { + Upstream upstream = Upstream.builder() + .url("a:80") + .protocol("http://") + .weight(100) + .status(true) + .build(); + final P2cLoadBalancer p2cLoadBalancer = new P2cLoadBalancer(); + long baseline = upstream.getInflight().get(); + for (int i = 0; i < 2; i++) { + p2cLoadBalancer.select(List.of(upstream), new LoadBalanceData()); + // mirrors DividePlugin's P2C completion callback (responseTrigger) + upstream.getInflight().decrementAndGet(); + } + Assertions.assertEquals(baseline, upstream.getInflight().get()); + Assertions.assertTrue(upstream.getInflight().get() >= 0); + } }
