alibaba / alibaba/Sentinel

[BUG] 一个关于 SentinelResourceAspect 的疑似 BUG

Open
#3,597 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
23.1k
Forks
8.1k
PR merge metrics
No merged PRs in 30d

Description

使用注解和抛出异常两种方式对同样的资源和规则进行流量控制时,执行结果不一样,以下是使用抛出异常方式的代码:

```
package com.alibaba.csp.sentinel.demo.test;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;

import java.util.ArrayList;
import java.util.List;

public class SentinelFlowThread {

public static void main(String[] args) throws InterruptedException {
initFlowRule();

for (int i = 0; i < 4; i++) {
// for (int i = 0; i < 1; i++) {
final int j = i;
new Thread(() -> {
Entry entry = null;
Object[] argArr = {"1"};
try {
if (j == 2) {
Thread.sleep(1000);
}
if (j == 3) {
Thread.sleep(4000);
}
// entry = SphU.entry("doSomething");
entry = SphU.entry("doSomething", 0, EntryType.OUT, argArr);
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 执行完成");
} catch (BlockException e) {
System.out.println(Thread.currentThread().getName() + " 被限流");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (entry != null) {
// entry.exit();
entry.exit(1, argArr);
}
}
}, "Thread-" + i).start();
}

Thread.sleep(10000);
}

private static void initFlowRule() {
List rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("doSomething");
rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
rule.setCount(2);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}

}
```

执行结果为:

INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: C:\Users\Administrator\logs\csp\
INFO: Sentinel log name use pid is: false
INFO: Sentinel log level is: INFO
Thread-2 被限流
Thread-0 执行完成
Thread-1 执行完成
Thread-3 执行完成

以下是使用注解方式的代码:

```
package com.alibaba.csp.sentinel.demo.test;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;

import java.util.ArrayList;
import java.util.List;

public class TestAnnotation {

@SentinelResource(value = "doSomething", blockHandler = "usingAnnotationMethodBlockHandler")
public void usingAnnotationMethod() throws InterruptedException {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 执行完成");
}

public void usingAnnotationMethodBlockHandler(BlockException e) {
System.out.println(Thread.currentThread().getName() + " 被限流");
}

public static void main(String[] args) throws InterruptedException {
initFlowRule();

TestAnnotation testAnnotation = new TestAnnotation();

for (int i = 0; i < 4; i++) {
// for (int i = 0; i < 1; i++) {
final int j = i;
new Thread(() -> {
try {
if (j == 2) {
Thread.sleep(1000);
}
if (j == 3) {
Thread.sleep(4000);
}
testAnnotation.usingAnnotationMethod();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "Thread-" + i).start();
}

Thread.sleep(10000);
}

private static void initFlowRule() {
List rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("doSomething");
rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
rule.setCount(2);
// rule.setCount(3);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}

}
```

执行结果为:

INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: C:\Users\Administrator\logs\csp\
INFO: Sentinel log name use pid is: false
INFO: Sentinel log level is: INFO
Thread-0 被限流
Thread-1 被限流
Thread-3 被限流
Thread-2 执行完成

然后,我把 SentinelResourceAspect 类进行修改,改完如下:

```
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.csp.sentinel.annotation.aspectj;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import java.lang.reflect.Method;

/**
* Aspect for methods with {@link SentinelResource} annotation.
*
* @author Eric Zhao
*/
@Aspect
public class SentinelResourceAspect extends AbstractSentinelAspectSupport {

// @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
// @Pointcut("execution(* *(..)) && @annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
@Pointcut("call(* *(..)) && @annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")
public void sentinelResourceAnnotationPointcut() {
}

@Around("sentinelResourceAnnotationPointcut()")
public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {

// System.out.println(Thread.currentThread().getName());

Method originMethod = resolveMethod(pjp);

SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);
if (annotation == null) {
// Should not go through here.
throw new IllegalStateException("Wrong state for SentinelResource annotation");
}
String resourceName = getResourceName(annotation.value(), originMethod);
EntryType entryType = annotation.entryType();
int resourceType = annotation.resourceType();
Entry entry = null;
try {
entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs());
return pjp.proceed();
} catch (BlockException ex) {
return handleBlockException(pjp, annotation, ex);
} catch (Throwable ex) {
Class[] exceptionsToIgnore = annotation.exceptionsToIgnore();
// The ignore list will be checked first.
if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) {
throw ex;
}
if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) {
traceException(ex);
return handleFallback(pjp, annotation, ex);
}

// No fallback function can handle the exception, so throw it out.
throw ex;
} finally {
if (entry != null) {
entry.exit(1, pjp.getArgs());
}
}
}
}
```

也就是把 pointcut 改为 @Pointcut("execution(* *(..)) && @annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)") 或者 @Pointcut("call(* *(..)) && @annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)"),接着再执行使用注解方式的 TestAnnotation,执行结果为:

INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: C:\Users\Administrator\logs\csp\
INFO: Sentinel log name use pid is: false
INFO: Sentinel log level is: INFO
Thread-2 被限流
Thread-1 执行完成
Thread-0 执行完成
Thread-3 执行完成

与使用抛出异常方式定义资源 SentinelFlowThread 的执行结果已经一样,这说明 AspectJ 在以方法为目标时会存在 call 和 execution 两种 join point,见官方文档 [pointcuts](https://eclipse.dev/aspectj/doc/released/progguide/semantics-pointcuts.html) 和 [joinPoints](https://eclipse.dev/aspectj/doc/released/progguide/semantics-joinPoints.html),因此,SentinelResourceAspect 的 invokeResourceWithSentinel 方法将会被同一个线程执行两次,导致 Sentinel 统计线程数时不准确,恳请 Sentinel 的贡献者们排查这是不是一个 BUG?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.