enhancement #I821F1 初始化时增加 Chain 引用判断,避免死循环而导致堆栈溢出

This commit is contained in:
luoyi 2023-09-20 22:55:31 +08:00
parent 4e0348ff39
commit 7fdb5829f5
16 changed files with 282 additions and 14 deletions

View File

@ -15,8 +15,8 @@ import com.yomahub.liteflow.exception.ELParseException;
import com.yomahub.liteflow.exception.FlowSystemException;
import com.yomahub.liteflow.flow.FlowBus;
import com.yomahub.liteflow.flow.element.Chain;
import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.flow.element.Condition;
import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.log.LFLog;
import com.yomahub.liteflow.log.LFLoggerManager;
@ -108,12 +108,15 @@ public class LiteFlowChainELBuilder {
return this;
}
/**
* <p>原来逻辑从 FlowBus 中获取相应的 chain如果 EL 表达式中出现嵌套引用 chain那么在构建 Condition 的时候可能会出现 chain 死循环引用情况</p>
* <p>故删掉从 FlowBus 中获取的逻辑直接使用新的 {@link LiteFlowChainELBuilder} 对象</p>
*
* @param chainId
* @return LiteFlowChainELBuilder
*/
public LiteFlowChainELBuilder setChainId(String chainId) {
if (FlowBus.containChain(chainId)) {
this.chain = FlowBus.getChain(chainId);
} else {
this.chain.setChainId(chainId);
}
this.chain.setChainId(chainId);
return this;
}

View File

@ -15,9 +15,7 @@ import com.yomahub.liteflow.enums.InnerChainTypeEnum;
import com.yomahub.liteflow.exception.*;
import com.yomahub.liteflow.flow.FlowBus;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.flow.element.Chain;
import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.flow.element.Rollbackable;
import com.yomahub.liteflow.flow.element.*;
import com.yomahub.liteflow.flow.entity.CmpStep;
import com.yomahub.liteflow.flow.id.IdGeneratorHolder;
import com.yomahub.liteflow.log.LFLog;
@ -195,6 +193,9 @@ public class FlowExecutor {
}
}
// 检查构建生成的 chain 的有效性
checkValidOfChain();
// 执行钩子
if (isStart) {
FlowInitHook.executeHook();
@ -214,6 +215,56 @@ public class FlowExecutor {
}
}
/**
* 检查 chain 的有效性同时重新构建 FlowBus chain将调用的子 chain 连起来
* @throws CyclicDependencyException
*/
private void checkValidOfChain() {
// 存储已经构建完的有效的 chain 对应 Id
Set<String> validChainIdSet = new HashSet<>();
// 遍历所有解析的 chain
for (Chain rootChain : FlowBus.getChainMap().values()) {
// 不存在 validChainIdSet 中的 chain说明还未检查
if (!validChainIdSet.contains(rootChain.getChainId())) {
// rootChain 相关联的 chain ID
Set<String> associatedChainIdSet = new HashSet<>();
// 检查 chain 的有效性是否存在死循环情况
checkValidOfChain(rootChain, associatedChainIdSet);
// 检查完当前 chain 能走到这里说明当前相关的 chain 是有效的
validChainIdSet.addAll(associatedChainIdSet);
}
}
}
/**
* 检查 chain 的有效性
* @param currentChain 当前遍历到的 chain 节点
* @throws CyclicDependencyException
*/
private void checkValidOfChain(Chain currentChain, Set<String> associatedChainIdSet) {
// 判断 completedChainIdSet 中是否已经存在对应的 chain
if (associatedChainIdSet.add(currentChain.getChainId())) {
// Set 中不存在则说明可能是父 chain 或者子 chain 未引用自身又或者子 chain 未引用其父 chain继续判断其子 chain
for (Condition condition : currentChain.getConditionList()) {
// 遍历所有 executable 列表
for (Executable executable : condition.getExecutableList()) {
// 只需判断 chain因为只有 chain 才会存在死循环依赖情况
if (executable instanceof Chain) {
// 能执行到此处必能从 FlowBus 中获取到对应的 chain故无需做非空判断
Chain childrenChain = FlowBus.getChainMap().get(executable.getId());
// 递归检查 chain 有效性
checkValidOfChain(childrenChain, associatedChainIdSet);
// 重新构建 chain condition 列表
((Chain) executable).setConditionList(childrenChain.getConditionList());
}
}
}
} else {
// chain 重复说明子 chain 中引用了自身或其父 chain存在死循环情况
throw new CyclicDependencyException(StrUtil.format("There is a circular dependency in the chain[{}], please check carefully.", currentChain.getChainId()));
}
}
// 此方法就是从原有的配置源主动拉取新的进行刷新
// 和FlowBus.refreshFlowMetaData的区别就是一个为主动拉取一个为被动监听到新的内容进行刷新
public void reloadRule() {

View File

@ -13,7 +13,10 @@ import com.yomahub.liteflow.flow.FlowBus;
import org.dom4j.Document;
import org.dom4j.Element;
import java.util.*;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Pattern;
@ -237,8 +240,10 @@ public class ParserHelper {
// 构建chainBuilder
String chainId = Optional.ofNullable(chainNode.get(ID)).orElse(chainNode.get(NAME)).textValue();
String el = chainNode.get(VALUE).textValue();
LiteFlowChainELBuilder chainELBuilder = LiteFlowChainELBuilder.createChain().setChainId(chainId);
chainELBuilder.setEL(el).build();
LiteFlowChainELBuilder.createChain()
.setChainId(chainId)
.setEL(el)
.build();
}
/**
@ -250,8 +255,10 @@ public class ParserHelper {
String chainId = Optional.ofNullable(e.attributeValue(ID)).orElse(e.attributeValue(NAME));
String text = e.getText();
String el = RegexUtil.removeComments(text);
LiteFlowChainELBuilder chainELBuilder = LiteFlowChainELBuilder.createChain().setChainId(chainId);
chainELBuilder.setEL(el).build();
LiteFlowChainELBuilder.createChain()
.setChainId(chainId)
.setEL(el)
.build();
}
/**

View File

@ -0,0 +1,37 @@
package com.yomahub.liteflow.test.subflow.endlessLoop;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.test.BaseTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.TestPropertySource;
import javax.annotation.Resource;
/**
* 测试多文件情况下 chain 死循环逻辑
*
* @author luo yi
* @since 2.11.0
*/
@TestPropertySource(value = "classpath:/subflow/endlessLoop/application-subInDifferentConfig1.properties")
@SpringBootTest(classes = FlowInDifferentConfigELSpringbootTest.class)
@EnableAutoConfiguration
@ComponentScan({ "com.yomahub.liteflow.test.subflow.cmp1", "com.yomahub.liteflow.test.subflow.cmp2" })
public class FlowInDifferentConfigELSpringbootTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
// 测试 chain 死循环
@Test
public void testChainEndlessLoop() {
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "it's a request");
Assertions.assertFalse(response.isSuccess());
}
}

View File

@ -0,0 +1,37 @@
package com.yomahub.liteflow.test.subflow.endlessLoop;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.test.BaseTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.TestPropertySource;
import javax.annotation.Resource;
/**
* 测试 json 文件情况下 chain 死循环逻辑
*
* @author luo yi
* @since 2.11.0
*/
@TestPropertySource(value = "classpath:/subflow/endlessLoop/application-json.properties")
@SpringBootTest(classes = FlowJsonELSpringBootTest.class)
@EnableAutoConfiguration
@ComponentScan({ "com.yomahub.liteflow.test.subflow.cmp1" })
public class FlowJsonELSpringBootTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
// 测试 chain 死循环
@Test
public void testChainEndlessLoop() {
LiteflowResponse response = flowExecutor.execute2Resp("chain7", "it's a request");
Assertions.assertFalse(response.isSuccess());
}
}

View File

@ -0,0 +1,37 @@
package com.yomahub.liteflow.test.subflow.endlessLoop;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.test.BaseTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.TestPropertySource;
import javax.annotation.Resource;
/**
* 测试 xml 文件情况下 chain 死循环逻辑
*
* @author luo yi
* @since 2.11.0
*/
@TestPropertySource(value = "classpath:/subflow/endlessLoop/application-xml.properties")
@SpringBootTest(classes = FlowXMLELSpringBootTest.class)
@EnableAutoConfiguration
@ComponentScan({ "com.yomahub.liteflow.test.subflow.cmp1" })
public class FlowXMLELSpringBootTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
// 测试 chain 死循环
@Test
public void testChainEndlessLoop() {
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "it's a request");
Assertions.assertFalse(response.isSuccess());
}
}

View File

@ -0,0 +1,37 @@
package com.yomahub.liteflow.test.subflow.endlessLoop;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.test.BaseTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.TestPropertySource;
import javax.annotation.Resource;
/**
* 测试 yml 文件情况下 chain 死循环逻辑
*
* @author luo yi
* @since 2.11.0
*/
@TestPropertySource(value = "classpath:/subflow/endlessLoop/application-yml.properties")
@SpringBootTest(classes = FlowYmlELSpringBootTest.class)
@EnableAutoConfiguration
@ComponentScan({ "com.yomahub.liteflow.test.subflow.cmp1" })
public class FlowYmlELSpringBootTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
// 测试 chain 死循环
@Test
public void testChainEndlessLoop() {
LiteflowResponse response = flowExecutor.execute2Resp("chain5", "it's a request");
Assertions.assertFalse(response.isSuccess());
}
}

View File

@ -0,0 +1 @@
liteflow.rule-source=subflow/endlessLoop/flow.el.json

View File

@ -0,0 +1,2 @@
liteflow.rule-source=subflow/endlessLoop/flow-sub1.el.xml,subflow/endlessLoop/flow-sub2.el.yml
liteflow.support-multiple-type=true

View File

@ -0,0 +1 @@
liteflow.rule-source=subflow/endlessLoop/flow.el.xml

View File

@ -0,0 +1 @@
liteflow.rule-source=subflow/endlessLoop/flow.el.yml

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="chain1">
THEN(a, b);
</chain>
</flow>

View File

@ -0,0 +1,6 @@
flow:
chain:
- name: chain2
value: "THEN(c, d, chain3);"
- name: chain3
value: "THEN(a, chain2);"

View File

@ -0,0 +1,18 @@
{
"flow": {
"chain": [
{
"name": "chain7",
"value": "THEN(a, chain8);"
},
{
"name": "chain8",
"value": "THEN(b, chain9);"
},
{
"name": "chain9",
"value": "WHEN(c, chain7);"
}
]
}
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<flow>
<chain name="chain1">
THEN(a, chain2);
</chain>
<chain name="chain2">
THEN(b, chain3);
</chain>
<chain name="chain3">
THEN(c, chain1);
</chain>
</flow>

View File

@ -0,0 +1,8 @@
flow:
chain:
- name: chain4
value: "THEN(a, chain5);"
- name: chain5
value: "THEN(b, chain6);"
- name: chain6
value: "THEN(c, chain5);"