enhancement #I821F1 初始化时增加 Chain 引用判断,避免死循环而导致堆栈溢出
This commit is contained in:
parent
4e0348ff39
commit
7fdb5829f5
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
liteflow.rule-source=subflow/endlessLoop/flow.el.json
|
|
@ -0,0 +1,2 @@
|
|||
liteflow.rule-source=subflow/endlessLoop/flow-sub1.el.xml,subflow/endlessLoop/flow-sub2.el.yml
|
||||
liteflow.support-multiple-type=true
|
|
@ -0,0 +1 @@
|
|||
liteflow.rule-source=subflow/endlessLoop/flow.el.xml
|
|
@ -0,0 +1 @@
|
|||
liteflow.rule-source=subflow/endlessLoop/flow.el.yml
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<flow>
|
||||
<chain name="chain1">
|
||||
THEN(a, b);
|
||||
</chain>
|
||||
</flow>
|
|
@ -0,0 +1,6 @@
|
|||
flow:
|
||||
chain:
|
||||
- name: chain2
|
||||
value: "THEN(c, d, chain3);"
|
||||
- name: chain3
|
||||
value: "THEN(a, chain2);"
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"flow": {
|
||||
"chain": [
|
||||
{
|
||||
"name": "chain7",
|
||||
"value": "THEN(a, chain8);"
|
||||
},
|
||||
{
|
||||
"name": "chain8",
|
||||
"value": "THEN(b, chain9);"
|
||||
},
|
||||
{
|
||||
"name": "chain9",
|
||||
"value": "WHEN(c, chain7);"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,8 @@
|
|||
flow:
|
||||
chain:
|
||||
- name: chain4
|
||||
value: "THEN(a, chain5);"
|
||||
- name: chain5
|
||||
value: "THEN(b, chain6);"
|
||||
- name: chain6
|
||||
value: "THEN(c, chain5);"
|
Loading…
Reference in New Issue