From fd27456c986042403cb8342369c01d761d4c63d0 Mon Sep 17 00:00:00 2001 From: DQinYuan <932087612@qq.com> Date: Sun, 25 Feb 2024 16:08:16 +0800 Subject: [PATCH 1/2] fix timer thread reentrant --- .../com/ql/util/express/ExecuteTimeOut.java | 28 ++++++++++ .../com/ql/util/express/ExpressRunner.java | 37 ++++++------- .../com/ql/util/express/InstructionSet.java | 6 ++- .../ql/util/express/InstructionSetRunner.java | 43 ++++++++------- .../java/com/ql/util/express/QLambda.java | 3 +- .../com/ql/util/express/RunEnvironment.java | 23 +++++++- .../util/express/config/QLExpressTimer.java | 50 +++++------------- .../instruction/IOperateDataCache.java | 11 +--- .../instruction/OperateDataCacheImpl.java | 18 +++---- .../instruction/OperateDataCacheManager.java | 15 ++---- .../detail/InstructionCallMacro.java | 2 +- .../InstructionCallSelfDefineFunction.java | 4 +- .../detail/InstructionNewVirClass.java | 2 +- .../opdata/OperateDataVirClass.java | 18 +++---- .../ql/util/express/ExecuteTimeOutTest.java | 23 ++++++++ .../express/bugfix/ExecuteReentrantTest.java | 30 +++++++++++ .../express/config/QLExpressTimerTest.java | 52 +++++++++++++++++++ .../util/express/test/DynamicFieldTest.java | 8 ++- 18 files changed, 244 insertions(+), 129 deletions(-) create mode 100644 src/main/java/com/ql/util/express/ExecuteTimeOut.java create mode 100644 src/test/java/com/ql/util/express/ExecuteTimeOutTest.java create mode 100644 src/test/java/com/ql/util/express/config/QLExpressTimerTest.java diff --git a/src/main/java/com/ql/util/express/ExecuteTimeOut.java b/src/main/java/com/ql/util/express/ExecuteTimeOut.java new file mode 100644 index 000000000..d59a62783 --- /dev/null +++ b/src/main/java/com/ql/util/express/ExecuteTimeOut.java @@ -0,0 +1,28 @@ +package com.ql.util.express; + +/** + * Author: DQinYuan + */ +public class ExecuteTimeOut { + /** + * 表示不限制时间的实例 + */ + public static final ExecuteTimeOut NO_TIMEOUT = new ExecuteTimeOut(-1); + + private final long timeoutMillis; + + private final long endTime; + + public ExecuteTimeOut(long timeoutMillis) { + this.timeoutMillis = timeoutMillis; + this.endTime = timeoutMillis != -1? System.currentTimeMillis() + timeoutMillis: -1; + } + + public boolean isExpired() { + return endTime != -1 && System.currentTimeMillis() > endTime; + } + + public long getTimeoutMillis() { + return timeoutMillis; + } +} diff --git a/src/main/java/com/ql/util/express/ExpressRunner.java b/src/main/java/com/ql/util/express/ExpressRunner.java index 4983384be..07598520e 100644 --- a/src/main/java/com/ql/util/express/ExpressRunner.java +++ b/src/main/java/com/ql/util/express/ExpressRunner.java @@ -577,7 +577,7 @@ public void clearExpressCache() { public Object executeByExpressName(String name, IExpressContext context, List errorList, boolean isTrace, boolean isCatchException) throws Exception { return InstructionSetRunner.executeOuter(this, this.loader.getInstructionSet(name), this.loader, context, - errorList, isTrace, isCatchException, false); + errorList, isTrace, isCatchException, false, -1); } /** @@ -593,7 +593,7 @@ public Object executeByExpressName(String name, IExpressContext */ public Object execute(InstructionSet instructionSet, IExpressContext context, List errorList, boolean isTrace, boolean isCatchException) throws Exception { - return executeReentrant(instructionSet, context, errorList, isTrace, isCatchException); + return executeReentrant(instructionSet, context, errorList, isTrace, isCatchException, -1); } /** @@ -604,19 +604,13 @@ public Object execute(InstructionSet instructionSet, IExpressContext context, List errorList, boolean isCache, boolean isTrace, long timeoutMillis) throws Exception { - //设置超时毫秒时间 - QLExpressTimer.setTimer(timeoutMillis); - try { - return this.execute(expressString, context, errorList, isCache, isTrace); - } finally { - QLExpressTimer.reset(); - } + return this.executeInner(expressString, context, errorList, isCache, isTrace, timeoutMillis); } /** @@ -632,27 +626,34 @@ public Object execute(String expressString, IExpressContext cont */ public Object execute(String expressString, IExpressContext context, List errorList, boolean isCache, boolean isTrace) throws Exception { + return executeInner(expressString, context, errorList, isCache, isTrace, -1); + } + + private Object executeInner(String expressString, IExpressContext context, List errorList, + boolean isCache, boolean isTrace, long timeoutMillis) throws Exception { InstructionSet parseResult; if (isCache) { parseResult = getInstructionSetFromLocalCache(expressString); } else { parseResult = this.parseInstructionSet(expressString); } - return executeReentrant(parseResult, context, errorList, isTrace, false); + + return executeReentrant(parseResult, context, errorList, isTrace, false, timeoutMillis); } - private Object executeReentrant(InstructionSet sets, IExpressContext iExpressContext, - List errorList, boolean isTrace, boolean isCatchException) throws Exception { + private Object executeReentrant(InstructionSet parseResult, IExpressContext context, List errorList, + boolean isTrace, boolean isCatchException, long timeoutMillis) throws Exception { try { int reentrantCount = threadReentrantCount.get() + 1; threadReentrantCount.set(reentrantCount); return reentrantCount > 1 ? - // 线程重入 - InstructionSetRunner.execute(this, sets, this.loader, iExpressContext, errorList, isTrace, - isCatchException, true, false) : - InstructionSetRunner.executeOuter(this, sets, this.loader, iExpressContext, errorList, isTrace, - isCatchException, false); + // 线程重入 + InstructionSetRunner.execute(this, parseResult, this.loader, context, errorList, isTrace, + isCatchException, true, false, + new ExecuteTimeOut(timeoutMillis)) : + InstructionSetRunner.executeOuter(this, parseResult, this.loader, context, errorList, isTrace, + isCatchException, false, timeoutMillis); } finally { threadReentrantCount.set(threadReentrantCount.get() - 1); } diff --git a/src/main/java/com/ql/util/express/InstructionSet.java b/src/main/java/com/ql/util/express/InstructionSet.java index a8451c4b0..942b3c736 100644 --- a/src/main/java/com/ql/util/express/InstructionSet.java +++ b/src/main/java/com/ql/util/express/InstructionSet.java @@ -8,6 +8,7 @@ import com.ql.util.express.config.QLExpressTimer; import com.ql.util.express.exception.QLException; +import com.ql.util.express.exception.QLTimeoutException; import com.ql.util.express.instruction.FunctionInstructionSet; import com.ql.util.express.instruction.OperateDataCacheManager; import com.ql.util.express.instruction.detail.Instruction; @@ -191,7 +192,10 @@ public void executeInnerOriginalInstruction(RunEnvironment environment, List iExpressContext, List errorList, boolean isTrace, - boolean isCatchException, boolean isSupportDynamicFieldName) throws Exception { + boolean isCatchException, boolean isSupportDynamicFieldName, long timeoutMills) throws Exception { try { - //开始计时 - QLExpressTimer.startTimer(); - OperateDataCacheManager.push(runner); - return execute(runner, instructionSet, loader, iExpressContext, errorList, isTrace, isCatchException, true, - isSupportDynamicFieldName); + return execute(runner, instructionSet, loader, iExpressContext, errorList, isTrace, isCatchException, + true, isSupportDynamicFieldName, + timeoutMills != -1? + // 优先使用参数传入 + new ExecuteTimeOut(timeoutMills): + // 如果参数未传入, 则看一下是否有全局设置 + QLExpressTimer.getTimeout() != -1? + new ExecuteTimeOut(QLExpressTimer.getTimeout()): + ExecuteTimeOut.NO_TIMEOUT); } finally { OperateDataCacheManager.resetCache(); } @@ -29,32 +33,33 @@ public static Object executeOuter(ExpressRunner runner, InstructionSet instructi /** * 批量执行指令集合,指令集间可以共享 变量和函数 * - * @param runner - * @param instructionSet - * @param loader - * @param iExpressContext - * @param errorList - * @param isTrace - * @param isCatchException - * @param isReturnLastData - * @param isSupportDynamicFieldName + * @param runner 解释器 + * @param instructionSet 指令集 + * @param loader 加载器 + * @param iExpressContext 上下文 + * @param errorList 错误列表 + * @param isTrace 打印跟踪日志 + * @param isCatchException 捕获异常 + * @param isReturnLastData 返回最后一个数据 + * @param isSupportDynamicFieldName 日支持动态字段名 + * @param executeTimeOut 脚本运行的结束时限, -1 表示没有限制 * @return * @throws Exception */ public static Object execute(ExpressRunner runner, InstructionSet instructionSet, ExpressLoader loader, IExpressContext iExpressContext, List errorList, boolean isTrace, - boolean isCatchException, boolean isReturnLastData, boolean isSupportDynamicFieldName) + boolean isCatchException, boolean isReturnLastData, boolean isSupportDynamicFieldName, ExecuteTimeOut executeTimeOut) throws Exception { InstructionSetContext context = OperateDataCacheManager.fetchInstructionSetContext(true, runner, iExpressContext, loader, isSupportDynamicFieldName); - return execute(instructionSet, context, errorList, isTrace, isCatchException, isReturnLastData); + return execute(instructionSet, context, errorList, isTrace, isCatchException, isReturnLastData, executeTimeOut); } public static Object execute(InstructionSet set, InstructionSetContext context, List errorList, - boolean isTrace, boolean isCatchException, boolean isReturnLastData) throws Exception { + boolean isTrace, boolean isCatchException, boolean isReturnLastData, ExecuteTimeOut executeTimeOut) throws Exception { RunEnvironment environment; Object result = null; - environment = OperateDataCacheManager.fetRunEnvironment(set, context, isTrace); + environment = OperateDataCacheManager.fetRunEnvironment(set, context, isTrace, executeTimeOut); try { CallResult tempResult = set.execute(environment, context, errorList, isReturnLastData); if (tempResult.isExit()) { diff --git a/src/main/java/com/ql/util/express/QLambda.java b/src/main/java/com/ql/util/express/QLambda.java index 49b6153f7..3076276f7 100644 --- a/src/main/java/com/ql/util/express/QLambda.java +++ b/src/main/java/com/ql/util/express/QLambda.java @@ -35,7 +35,8 @@ public Object call(Object... params) throws Exception { operateDataLocalVar.setObject(context, params.length > i ? params[i] : null); } - return InstructionSetRunner.execute(functionSet, context, errorList, environment.isTrace(), false, true); + return InstructionSetRunner.execute(functionSet, context, errorList, environment.isTrace(), + false, true, environment.getExecuteTimeOut()); } /** diff --git a/src/main/java/com/ql/util/express/RunEnvironment.java b/src/main/java/com/ql/util/express/RunEnvironment.java index cf740bc22..10be1ed43 100644 --- a/src/main/java/com/ql/util/express/RunEnvironment.java +++ b/src/main/java/com/ql/util/express/RunEnvironment.java @@ -14,17 +14,26 @@ public final class RunEnvironment { private InstructionSet instructionSet; private InstructionSetContext context; - public RunEnvironment(InstructionSet instructionSet, InstructionSetContext instructionSetContext, boolean isTrace) { + /** + * 脚本运行时间限制 + */ + private ExecuteTimeOut executeTimeOut; + + public RunEnvironment(InstructionSet instructionSet, InstructionSetContext instructionSetContext, + boolean isTrace, ExecuteTimeOut executeTimeOut) { dataContainer = new OperateData[INIT_DATA_LENGTH]; this.instructionSet = instructionSet; this.context = instructionSetContext; this.isTrace = isTrace; + this.executeTimeOut = executeTimeOut; } - public void initial(InstructionSet instructionSet, InstructionSetContext instructionSetContext, boolean isTrace) { + public void initial(InstructionSet instructionSet, InstructionSetContext instructionSetContext, + boolean isTrace, ExecuteTimeOut executeTimeOut) { this.instructionSet = instructionSet; this.context = instructionSetContext; this.isTrace = isTrace; + this.executeTimeOut = executeTimeOut; } public void clear() { @@ -37,6 +46,8 @@ public void clear() { instructionSet = null; context = null; + + executeTimeOut = null; } public InstructionSet getInstructionSet() { @@ -150,4 +161,12 @@ public void ensureCapacity(int minCapacity) { this.dataContainer = tempList; } } + + public boolean isExecuteTimeout() { + return executeTimeOut != null && executeTimeOut.isExpired(); + } + + public ExecuteTimeOut getExecuteTimeOut() { + return executeTimeOut; + } } diff --git a/src/main/java/com/ql/util/express/config/QLExpressTimer.java b/src/main/java/com/ql/util/express/config/QLExpressTimer.java index b5eeb39f9..4f05af4f2 100644 --- a/src/main/java/com/ql/util/express/config/QLExpressTimer.java +++ b/src/main/java/com/ql/util/express/config/QLExpressTimer.java @@ -7,57 +7,31 @@ * @since 2019/6/17 4:12 PM */ public class QLExpressTimer { - private static final ThreadLocal NEED_TIMER = ThreadLocal.withInitial(() -> false); - private static final ThreadLocal TIME_OUT_MILLIS = new ThreadLocal() {}; - private static final ThreadLocal START_TIME = new ThreadLocal() {}; - private static final ThreadLocal END_TIME = new ThreadLocal() {}; - private QLExpressTimer() { - throw new IllegalStateException("Utility class"); - } + private static long globalTimeoutMillis = -1; /** - * 设置超时时间 + * 设置全局脚本超时时间, 默认 -1, 表示不限制时间 * * @param timeoutMillis 超时时间 + * @deprecated 原 api 命名不合理, 推荐替换为 {@link #setTimeout(long)} */ + @Deprecated public static void setTimer(long timeoutMillis) { - NEED_TIMER.set(true); - TIME_OUT_MILLIS.set(timeoutMillis); - } - - /** - * 开始计时 - */ - public static void startTimer() { - if (NEED_TIMER.get()) { - long currentTimeMillis = System.currentTimeMillis(); - START_TIME.set(currentTimeMillis); - END_TIME.set(currentTimeMillis + TIME_OUT_MILLIS.get()); - } + globalTimeoutMillis = timeoutMillis; } /** - * 断言是否超时 + * 设置全局脚本超时时间, 默认 -1, 表示不限制时间 * - * @throws QLTimeoutException + * @param timeoutMillis 超时时间 + * @since 3.3.3 */ - public static void assertTimeOut() throws QLTimeoutException { - if (NEED_TIMER.get() && System.currentTimeMillis() > END_TIME.get()) { - throw new QLTimeoutException("运行QLExpress脚本的下一条指令将超过限定时间:" + TIME_OUT_MILLIS.get() + "ms"); - } - } - - public static boolean hasExpired() { - return NEED_TIMER.get() && System.currentTimeMillis() > END_TIME.get(); + public static void setTimeout(long timeoutMillis) { + globalTimeoutMillis = timeoutMillis; } - public static void reset() { - if (NEED_TIMER.get()) { - START_TIME.remove(); - END_TIME.remove(); - NEED_TIMER.remove(); - TIME_OUT_MILLIS.remove(); - } + public static long getTimeout() { + return globalTimeoutMillis; } } diff --git a/src/main/java/com/ql/util/express/instruction/IOperateDataCache.java b/src/main/java/com/ql/util/express/instruction/IOperateDataCache.java index f98c8f041..98c9195b5 100644 --- a/src/main/java/com/ql/util/express/instruction/IOperateDataCache.java +++ b/src/main/java/com/ql/util/express/instruction/IOperateDataCache.java @@ -1,13 +1,6 @@ package com.ql.util.express.instruction; -import com.ql.util.express.CallResult; -import com.ql.util.express.ExpressLoader; -import com.ql.util.express.ExpressRunner; -import com.ql.util.express.IExpressContext; -import com.ql.util.express.InstructionSet; -import com.ql.util.express.InstructionSetContext; -import com.ql.util.express.OperateData; -import com.ql.util.express.RunEnvironment; +import com.ql.util.express.*; import com.ql.util.express.instruction.opdata.OperateDataArrayItem; import com.ql.util.express.instruction.opdata.OperateDataAttr; import com.ql.util.express.instruction.opdata.OperateDataField; @@ -28,7 +21,7 @@ public interface IOperateDataCache { OperateDataKeyValue fetchOperateDataKeyValue(OperateData key, OperateData value); RunEnvironment fetRunEnvironment(InstructionSet instructionSet, InstructionSetContext instructionSetContext, - boolean isTrace); + boolean isTrace, ExecuteTimeOut executeTimeOut); CallResult fetchCallResult(Object returnValue, boolean isExit); diff --git a/src/main/java/com/ql/util/express/instruction/OperateDataCacheImpl.java b/src/main/java/com/ql/util/express/instruction/OperateDataCacheImpl.java index 1ccb00540..ab95b2477 100644 --- a/src/main/java/com/ql/util/express/instruction/OperateDataCacheImpl.java +++ b/src/main/java/com/ql/util/express/instruction/OperateDataCacheImpl.java @@ -1,13 +1,6 @@ package com.ql.util.express.instruction; -import com.ql.util.express.CallResult; -import com.ql.util.express.ExpressLoader; -import com.ql.util.express.ExpressRunner; -import com.ql.util.express.IExpressContext; -import com.ql.util.express.InstructionSet; -import com.ql.util.express.InstructionSetContext; -import com.ql.util.express.OperateData; -import com.ql.util.express.RunEnvironment; +import com.ql.util.express.*; import com.ql.util.express.instruction.opdata.OperateDataArrayItem; import com.ql.util.express.instruction.opdata.OperateDataAttr; import com.ql.util.express.instruction.opdata.OperateDataField; @@ -55,7 +48,8 @@ public OperateDataCacheImpl(int len) { arrayList[i] = new OperateDataArrayItem(null, -1); keyValueList[i] = new OperateDataKeyValue(null, null); callResultList[i] = new CallResult(null, false); - environmentList[i] = new RunEnvironment(null, null, false); + environmentList[i] = new RunEnvironment(null, null, false, + ExecuteTimeOut.NO_TIMEOUT); contextList[i] = new InstructionSetContext(false, null, null, null, false); } } @@ -118,14 +112,14 @@ public InstructionSetContext fetchInstructionSetContext(boolean isExpandToParent @Override public RunEnvironment fetRunEnvironment(InstructionSet instructionSet, InstructionSetContext instructionSetContext, - boolean isTrace) { + boolean isTrace, ExecuteTimeOut executeTimeOut) { RunEnvironment result; if (environmentPoint < length) { result = environmentList[environmentPoint]; - result.initial(instructionSet, instructionSetContext, isTrace); + result.initial(instructionSet, instructionSetContext, isTrace, executeTimeOut); environmentPoint = environmentPoint + 1; } else { - result = new RunEnvironment(instructionSet, instructionSetContext, isTrace); + result = new RunEnvironment(instructionSet, instructionSetContext, isTrace, executeTimeOut); } return result; } diff --git a/src/main/java/com/ql/util/express/instruction/OperateDataCacheManager.java b/src/main/java/com/ql/util/express/instruction/OperateDataCacheManager.java index 90d3799cb..36f8c3c92 100644 --- a/src/main/java/com/ql/util/express/instruction/OperateDataCacheManager.java +++ b/src/main/java/com/ql/util/express/instruction/OperateDataCacheManager.java @@ -2,14 +2,7 @@ import java.util.Stack; -import com.ql.util.express.CallResult; -import com.ql.util.express.ExpressLoader; -import com.ql.util.express.ExpressRunner; -import com.ql.util.express.IExpressContext; -import com.ql.util.express.InstructionSet; -import com.ql.util.express.InstructionSetContext; -import com.ql.util.express.OperateData; -import com.ql.util.express.RunEnvironment; +import com.ql.util.express.*; import com.ql.util.express.instruction.opdata.OperateDataArrayItem; import com.ql.util.express.instruction.opdata.OperateDataAttr; import com.ql.util.express.instruction.opdata.OperateDataField; @@ -56,9 +49,9 @@ public static OperateDataKeyValue fetchOperateDataKeyValue(OperateData key, Oper return getOperateDataCache().fetchOperateDataKeyValue(key, value); } - public static RunEnvironment fetRunEnvironment(InstructionSet instructionSet, - InstructionSetContext instructionSetContext, boolean isTrace) { - return getOperateDataCache().fetRunEnvironment(instructionSet, instructionSetContext, isTrace); + public static RunEnvironment fetRunEnvironment(InstructionSet instructionSet, InstructionSetContext instructionSetContext, + boolean isTrace, ExecuteTimeOut executeTimeOut) { + return getOperateDataCache().fetRunEnvironment(instructionSet, instructionSetContext, isTrace, executeTimeOut); } public static CallResult fetchCallResult(Object returnValue, boolean isExit) { diff --git a/src/main/java/com/ql/util/express/instruction/detail/InstructionCallMacro.java b/src/main/java/com/ql/util/express/instruction/detail/InstructionCallMacro.java index 018fc87be..304fb85b1 100644 --- a/src/main/java/com/ql/util/express/instruction/detail/InstructionCallMacro.java +++ b/src/main/java/com/ql/util/express/instruction/detail/InstructionCallMacro.java @@ -23,7 +23,7 @@ public void execute(RunEnvironment environment, List errorList) throws E Object result = InstructionSetRunner.execute(context.getExpressRunner(), (InstructionSet)functionSet, context.getExpressLoader(), context, errorList, environment.isTrace(), false, false, - environment.getContext().isSupportDynamicFieldName()); + environment.getContext().isSupportDynamicFieldName(), environment.getExecuteTimeOut()); if (result instanceof OperateData) { environment.push((OperateData)result); } else { diff --git a/src/main/java/com/ql/util/express/instruction/detail/InstructionCallSelfDefineFunction.java b/src/main/java/com/ql/util/express/instruction/detail/InstructionCallSelfDefineFunction.java index 0c3b86f38..511c36af7 100644 --- a/src/main/java/com/ql/util/express/instruction/detail/InstructionCallSelfDefineFunction.java +++ b/src/main/java/com/ql/util/express/instruction/detail/InstructionCallSelfDefineFunction.java @@ -58,8 +58,8 @@ public static OperateData executeSelfFunction(RunEnvironment environment, Instru context.addSymbol(operateDataLocalVar.getName(), operateDataLocalVar); operateDataLocalVar.setObject(context, parameters.get(i).getObject(environment.getContext())); } - Object result = InstructionSetRunner.execute(functionSet, context, errorList, environment.isTrace(), false, - true); + Object result = InstructionSetRunner.execute(functionSet, context, errorList, + environment.isTrace(), false, true, environment.getExecuteTimeOut()); return OperateDataCacheManager.fetchOperateData(result, null); } diff --git a/src/main/java/com/ql/util/express/instruction/detail/InstructionNewVirClass.java b/src/main/java/com/ql/util/express/instruction/detail/InstructionNewVirClass.java index d9346cc44..d908a3a60 100644 --- a/src/main/java/com/ql/util/express/instruction/detail/InstructionNewVirClass.java +++ b/src/main/java/com/ql/util/express/instruction/detail/InstructionNewVirClass.java @@ -34,7 +34,7 @@ public void execute(RunEnvironment environment, List errorList) throws E OperateDataVirClass result = new OperateDataVirClass(className); environment.push(result); environment.programPointAddOne(); - result.initialInstance(environment.getContext(), list, errorList, environment.isTrace()); + result.initialInstance(environment, list, errorList, environment.isTrace()); } @Override diff --git a/src/main/java/com/ql/util/express/instruction/opdata/OperateDataVirClass.java b/src/main/java/com/ql/util/express/instruction/opdata/OperateDataVirClass.java index fe6becda5..7f60b53db 100644 --- a/src/main/java/com/ql/util/express/instruction/opdata/OperateDataVirClass.java +++ b/src/main/java/com/ql/util/express/instruction/opdata/OperateDataVirClass.java @@ -2,10 +2,7 @@ import java.util.List; -import com.ql.util.express.InstructionSet; -import com.ql.util.express.InstructionSetContext; -import com.ql.util.express.InstructionSetRunner; -import com.ql.util.express.OperateData; +import com.ql.util.express.*; import com.ql.util.express.exception.QLException; import com.ql.util.express.instruction.OperateDataCacheManager; @@ -31,8 +28,9 @@ public OperateDataVirClass(String name) { super(name, null); } - public void initialInstance(InstructionSetContext parent, OperateData[] parameters, List errorList, - boolean isTrace) throws Exception { + public void initialInstance(RunEnvironment environment, OperateData[] parameters, List errorList, + boolean isTrace) throws Exception { + InstructionSetContext parent = environment.getContext(); this.isTrace = isTrace; this.context = OperateDataCacheManager.fetchInstructionSetContext(false, parent.getExpressRunner(), parent, parent.getExpressLoader(), parent.isSupportDynamicFieldName()); @@ -50,7 +48,8 @@ public void initialInstance(InstructionSetContext parent, OperateData[] paramete this.context.addSymbol(operateDataLocalVar.getName(), operateDataLocalVar); operateDataLocalVar.setObject(context, parameters[i].getObject(parent)); } - InstructionSetRunner.execute(virClassInstructionSet, context, errorList, isTrace, false, false); + InstructionSetRunner.execute(virClassInstructionSet, context, errorList, isTrace, + false, false, environment.getExecuteTimeOut()); } public OperateData callSelfFunction(String functionName, OperateData[] parameters) throws Exception { @@ -71,7 +70,8 @@ public OperateData callSelfFunction(String functionName, OperateData[] parameter tempContext.addSymbol(operateDataLocalVar.getName(), operateDataLocalVar); operateDataLocalVar.setObject(tempContext, parameters[i].getObject(this.context)); } - Object result = InstructionSetRunner.execute(functionSet, tempContext, null, this.isTrace, false, true); + Object result = InstructionSetRunner.execute(functionSet, tempContext, null, + this.isTrace, false, true, ExecuteTimeOut.NO_TIMEOUT); return OperateDataCacheManager.fetchOperateData(result, null); } @@ -89,7 +89,7 @@ public Object getValue(Object name) throws Exception { this.context.isSupportDynamicFieldName()); Object result = InstructionSetRunner.execute(this.context.getExpressRunner(), (InstructionSet)o, this.context.getExpressLoader(), tempContext, null, this.isTrace, false, false, - this.context.isSupportDynamicFieldName()); + this.context.isSupportDynamicFieldName(), ExecuteTimeOut.NO_TIMEOUT); if (result instanceof OperateData) { return ((OperateData)result).getObject(this.context); } else { diff --git a/src/test/java/com/ql/util/express/ExecuteTimeOutTest.java b/src/test/java/com/ql/util/express/ExecuteTimeOutTest.java new file mode 100644 index 000000000..53dd4b534 --- /dev/null +++ b/src/test/java/com/ql/util/express/ExecuteTimeOutTest.java @@ -0,0 +1,23 @@ +package com.ql.util.express; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Author: DQinYuan + */ +public class ExecuteTimeOutTest { + + @Test + public void noTimeoutTest() throws InterruptedException { + assertFalse(ExecuteTimeOut.NO_TIMEOUT.isExpired()); + + ExecuteTimeOut timeOut = new ExecuteTimeOut(20); + Thread.sleep(15); + assertFalse(timeOut.isExpired()); + Thread.sleep(6); + assertTrue(timeOut.isExpired()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/ql/util/express/bugfix/ExecuteReentrantTest.java b/src/test/java/com/ql/util/express/bugfix/ExecuteReentrantTest.java index 020ebec42..cede503cc 100644 --- a/src/test/java/com/ql/util/express/bugfix/ExecuteReentrantTest.java +++ b/src/test/java/com/ql/util/express/bugfix/ExecuteReentrantTest.java @@ -5,6 +5,8 @@ import com.ql.util.express.ExpressRunner; import com.ql.util.express.InstructionSetContext; import com.ql.util.express.OperateData; +import com.ql.util.express.exception.QLException; +import com.ql.util.express.exception.QLTimeoutException; import com.ql.util.express.instruction.op.OperatorBase; import org.junit.Assert; import org.junit.Test; @@ -32,4 +34,32 @@ public OperateData executeInner(InstructionSetContext parent, ArraySwap list) th Object res1 = runner.execute("m = 'aaa'; eval('m')", new DefaultContext<>(), null, false, true); Assert.assertEquals("aaa", res1); } + + @Test + public void executeReentrantTimerTest() throws Exception { + ExpressRunner runner = new ExpressRunner(false, true); + runner.addFunction("eval", new OperatorBase() { + @Override + public OperateData executeInner(InstructionSetContext parent, ArraySwap list) throws Exception { + Thread.sleep(2000); + Object result = runner.execute(list.get(0).toString(), parent, null, + false, true, ((Number) list.get(1).getObject(parent)).longValue()); + return new OperateData(result, result.getClass()); + } + }); + + Object result = runner.execute("a=eval('a=eval(\\'a=1+1\\',10)', 2200)", new DefaultContext<>(), + null, false, true, 4100); + Assert.assertEquals(2, result); + + try { + runner.execute("a=eval('a=eval(\\'a=1+1\\', 10)',1000)", + new DefaultContext<>(), null, false, true, 4100); + Assert.fail(); + } catch (QLException e) { + Assert.assertTrue(e.getCause() instanceof QLTimeoutException); + } catch (Throwable e) { + Assert.fail(); + } + } } diff --git a/src/test/java/com/ql/util/express/config/QLExpressTimerTest.java b/src/test/java/com/ql/util/express/config/QLExpressTimerTest.java new file mode 100644 index 000000000..4ad6393cc --- /dev/null +++ b/src/test/java/com/ql/util/express/config/QLExpressTimerTest.java @@ -0,0 +1,52 @@ +package com.ql.util.express.config; + +import com.ql.util.express.DefaultContext; +import com.ql.util.express.ExpressRunner; +import com.ql.util.express.exception.QLTimeoutException; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Author: DQinYuan + */ +public class QLExpressTimerTest { + + private static long OLD_TIMER; + + @BeforeClass + public static void before() { + OLD_TIMER = QLExpressTimer.getTimeout(); + QLExpressTimer.setTimeout(20); + } + + @AfterClass + public static void after() { + QLExpressTimer.setTimeout(OLD_TIMER); + } + + @Test + public void timerTest() throws Exception { + ExpressRunner runner = new ExpressRunner(); + DefaultContext context = new DefaultContext<>(); + runner.execute("Thread.sleep(5);a=1", context, null, true, false); + runner.execute("Thread.sleep(18);a=5", context, null, true, false); + } + + @Test(expected = QLTimeoutException.class) + public void timerTimeoutTest() throws Exception { + ExpressRunner runner = new ExpressRunner(); + DefaultContext context = new DefaultContext<>(); + runner.execute("Thread.sleep(21);a=1", context, null, true, false); + } + + /** + * execute 传入参数的超时时间优先 + */ + @Test(expected = QLTimeoutException.class) + public void paramFirstTest() throws Exception { + ExpressRunner runner = new ExpressRunner(); + DefaultContext context = new DefaultContext<>(); + runner.execute("Thread.sleep(5);a=1", context, null, true, false, 3); + } +} \ No newline at end of file diff --git a/src/test/java/com/ql/util/express/test/DynamicFieldTest.java b/src/test/java/com/ql/util/express/test/DynamicFieldTest.java index 9b8bd2f01..456cd47a0 100644 --- a/src/test/java/com/ql/util/express/test/DynamicFieldTest.java +++ b/src/test/java/com/ql/util/express/test/DynamicFieldTest.java @@ -3,10 +3,7 @@ import java.util.HashMap; import java.util.Map; -import com.ql.util.express.DefaultContext; -import com.ql.util.express.ExpressRunner; -import com.ql.util.express.InstructionSet; -import com.ql.util.express.InstructionSetRunner; +import com.ql.util.express.*; import org.junit.Assert; import org.junit.Test; @@ -25,7 +22,8 @@ public void testField() throws Exception { Map fee = new HashMap<>(); context.put("费用", fee); InstructionSet set = runner.parseInstructionSet(express); - InstructionSetRunner.executeOuter(runner, set, null, context, null, true, false, true); + InstructionSetRunner.executeOuter(runner, set, null, context, + null, true, false, true, -1); runner.execute(express, context, null, false, true); System.out.println(context.get("费用")); Assert.assertEquals("动态属性错误", "100", fee.get("张三").toString()); From 732680598d314071382d42e0f7353140d7c789aa Mon Sep 17 00:00:00 2001 From: DQinYuan <932087612@qq.com> Date: Sun, 31 Mar 2024 14:49:45 +0800 Subject: [PATCH 2/2] cr comment --- .../{ExecuteTimeOut.java => ExecuteTimeout.java} | 8 ++++---- .../java/com/ql/util/express/ExpressRunner.java | 2 +- .../ql/util/express/InstructionSetRunner.java | 10 +++++----- .../java/com/ql/util/express/RunEnvironment.java | 8 ++++---- .../ql/util/express/config/QLExpressTimer.java | 2 -- .../express/instruction/IOperateDataCache.java | 14 +++++++++++--- .../instruction/OperateDataCacheImpl.java | 5 ++--- .../instruction/OperateDataCacheManager.java | 16 ++++++++++++---- .../instruction/opdata/OperateDataVirClass.java | 11 ++++++++--- ...eTimeOutTest.java => ExecuteTimeoutTest.java} | 6 +++--- 10 files changed, 50 insertions(+), 32 deletions(-) rename src/main/java/com/ql/util/express/{ExecuteTimeOut.java => ExecuteTimeout.java} (62%) rename src/test/java/com/ql/util/express/{ExecuteTimeOutTest.java => ExecuteTimeoutTest.java} (68%) diff --git a/src/main/java/com/ql/util/express/ExecuteTimeOut.java b/src/main/java/com/ql/util/express/ExecuteTimeout.java similarity index 62% rename from src/main/java/com/ql/util/express/ExecuteTimeOut.java rename to src/main/java/com/ql/util/express/ExecuteTimeout.java index d59a62783..7484f54f0 100644 --- a/src/main/java/com/ql/util/express/ExecuteTimeOut.java +++ b/src/main/java/com/ql/util/express/ExecuteTimeout.java @@ -3,19 +3,19 @@ /** * Author: DQinYuan */ -public class ExecuteTimeOut { +public class ExecuteTimeout { /** * 表示不限制时间的实例 */ - public static final ExecuteTimeOut NO_TIMEOUT = new ExecuteTimeOut(-1); + public static final ExecuteTimeout NO_TIMEOUT = new ExecuteTimeout(-1); private final long timeoutMillis; private final long endTime; - public ExecuteTimeOut(long timeoutMillis) { + public ExecuteTimeout(long timeoutMillis) { this.timeoutMillis = timeoutMillis; - this.endTime = timeoutMillis != -1? System.currentTimeMillis() + timeoutMillis: -1; + this.endTime = timeoutMillis != -1 ? System.currentTimeMillis() + timeoutMillis : -1; } public boolean isExpired() { diff --git a/src/main/java/com/ql/util/express/ExpressRunner.java b/src/main/java/com/ql/util/express/ExpressRunner.java index 07598520e..71119fbcc 100644 --- a/src/main/java/com/ql/util/express/ExpressRunner.java +++ b/src/main/java/com/ql/util/express/ExpressRunner.java @@ -651,7 +651,7 @@ private Object executeReentrant(InstructionSet parseResult, IExpressContext iExpressContext, List errorList, boolean isTrace, - boolean isCatchException, boolean isReturnLastData, boolean isSupportDynamicFieldName, ExecuteTimeOut executeTimeOut) + boolean isCatchException, boolean isReturnLastData, boolean isSupportDynamicFieldName, ExecuteTimeout executeTimeOut) throws Exception { InstructionSetContext context = OperateDataCacheManager.fetchInstructionSetContext(true, runner, iExpressContext, loader, isSupportDynamicFieldName); @@ -56,7 +56,7 @@ public static Object execute(ExpressRunner runner, InstructionSet instructionSet } public static Object execute(InstructionSet set, InstructionSetContext context, List errorList, - boolean isTrace, boolean isCatchException, boolean isReturnLastData, ExecuteTimeOut executeTimeOut) throws Exception { + boolean isTrace, boolean isCatchException, boolean isReturnLastData, ExecuteTimeout executeTimeOut) throws Exception { RunEnvironment environment; Object result = null; environment = OperateDataCacheManager.fetRunEnvironment(set, context, isTrace, executeTimeOut); diff --git a/src/main/java/com/ql/util/express/RunEnvironment.java b/src/main/java/com/ql/util/express/RunEnvironment.java index 10be1ed43..2004554a5 100644 --- a/src/main/java/com/ql/util/express/RunEnvironment.java +++ b/src/main/java/com/ql/util/express/RunEnvironment.java @@ -17,10 +17,10 @@ public final class RunEnvironment { /** * 脚本运行时间限制 */ - private ExecuteTimeOut executeTimeOut; + private ExecuteTimeout executeTimeOut; public RunEnvironment(InstructionSet instructionSet, InstructionSetContext instructionSetContext, - boolean isTrace, ExecuteTimeOut executeTimeOut) { + boolean isTrace, ExecuteTimeout executeTimeOut) { dataContainer = new OperateData[INIT_DATA_LENGTH]; this.instructionSet = instructionSet; this.context = instructionSetContext; @@ -29,7 +29,7 @@ public RunEnvironment(InstructionSet instructionSet, InstructionSetContext instr } public void initial(InstructionSet instructionSet, InstructionSetContext instructionSetContext, - boolean isTrace, ExecuteTimeOut executeTimeOut) { + boolean isTrace, ExecuteTimeout executeTimeOut) { this.instructionSet = instructionSet; this.context = instructionSetContext; this.isTrace = isTrace; @@ -166,7 +166,7 @@ public boolean isExecuteTimeout() { return executeTimeOut != null && executeTimeOut.isExpired(); } - public ExecuteTimeOut getExecuteTimeOut() { + public ExecuteTimeout getExecuteTimeOut() { return executeTimeOut; } } diff --git a/src/main/java/com/ql/util/express/config/QLExpressTimer.java b/src/main/java/com/ql/util/express/config/QLExpressTimer.java index 4f05af4f2..fd34c19d4 100644 --- a/src/main/java/com/ql/util/express/config/QLExpressTimer.java +++ b/src/main/java/com/ql/util/express/config/QLExpressTimer.java @@ -1,7 +1,5 @@ package com.ql.util.express.config; -import com.ql.util.express.exception.QLTimeoutException; - /** * @author tianqiao@taobao.com * @since 2019/6/17 4:12 PM diff --git a/src/main/java/com/ql/util/express/instruction/IOperateDataCache.java b/src/main/java/com/ql/util/express/instruction/IOperateDataCache.java index 98c9195b5..2f5e01715 100644 --- a/src/main/java/com/ql/util/express/instruction/IOperateDataCache.java +++ b/src/main/java/com/ql/util/express/instruction/IOperateDataCache.java @@ -1,6 +1,14 @@ package com.ql.util.express.instruction; -import com.ql.util.express.*; +import com.ql.util.express.CallResult; +import com.ql.util.express.ExecuteTimeout; +import com.ql.util.express.ExpressLoader; +import com.ql.util.express.ExpressRunner; +import com.ql.util.express.IExpressContext; +import com.ql.util.express.InstructionSet; +import com.ql.util.express.InstructionSetContext; +import com.ql.util.express.OperateData; +import com.ql.util.express.RunEnvironment; import com.ql.util.express.instruction.opdata.OperateDataArrayItem; import com.ql.util.express.instruction.opdata.OperateDataAttr; import com.ql.util.express.instruction.opdata.OperateDataField; @@ -21,12 +29,12 @@ public interface IOperateDataCache { OperateDataKeyValue fetchOperateDataKeyValue(OperateData key, OperateData value); RunEnvironment fetRunEnvironment(InstructionSet instructionSet, InstructionSetContext instructionSetContext, - boolean isTrace, ExecuteTimeOut executeTimeOut); + boolean isTrace, ExecuteTimeout executeTimeOut); CallResult fetchCallResult(Object returnValue, boolean isExit); InstructionSetContext fetchInstructionSetContext(boolean isExpandToParent, ExpressRunner expressRunner, - IExpressContext parent, ExpressLoader expressLoader, boolean isSupportDynamicFieldName); + IExpressContext parent, ExpressLoader expressLoader, boolean isSupportDynamicFieldName); void resetCache(); diff --git a/src/main/java/com/ql/util/express/instruction/OperateDataCacheImpl.java b/src/main/java/com/ql/util/express/instruction/OperateDataCacheImpl.java index ab95b2477..4d063b07d 100644 --- a/src/main/java/com/ql/util/express/instruction/OperateDataCacheImpl.java +++ b/src/main/java/com/ql/util/express/instruction/OperateDataCacheImpl.java @@ -48,8 +48,7 @@ public OperateDataCacheImpl(int len) { arrayList[i] = new OperateDataArrayItem(null, -1); keyValueList[i] = new OperateDataKeyValue(null, null); callResultList[i] = new CallResult(null, false); - environmentList[i] = new RunEnvironment(null, null, false, - ExecuteTimeOut.NO_TIMEOUT); + environmentList[i] = new RunEnvironment(null, null, false, ExecuteTimeout.NO_TIMEOUT); contextList[i] = new InstructionSetContext(false, null, null, null, false); } } @@ -112,7 +111,7 @@ public InstructionSetContext fetchInstructionSetContext(boolean isExpandToParent @Override public RunEnvironment fetRunEnvironment(InstructionSet instructionSet, InstructionSetContext instructionSetContext, - boolean isTrace, ExecuteTimeOut executeTimeOut) { + boolean isTrace, ExecuteTimeout executeTimeOut) { RunEnvironment result; if (environmentPoint < length) { result = environmentList[environmentPoint]; diff --git a/src/main/java/com/ql/util/express/instruction/OperateDataCacheManager.java b/src/main/java/com/ql/util/express/instruction/OperateDataCacheManager.java index 36f8c3c92..b30825f8e 100644 --- a/src/main/java/com/ql/util/express/instruction/OperateDataCacheManager.java +++ b/src/main/java/com/ql/util/express/instruction/OperateDataCacheManager.java @@ -2,7 +2,15 @@ import java.util.Stack; -import com.ql.util.express.*; +import com.ql.util.express.CallResult; +import com.ql.util.express.ExecuteTimeout; +import com.ql.util.express.ExpressLoader; +import com.ql.util.express.ExpressRunner; +import com.ql.util.express.IExpressContext; +import com.ql.util.express.InstructionSet; +import com.ql.util.express.InstructionSetContext; +import com.ql.util.express.OperateData; +import com.ql.util.express.RunEnvironment; import com.ql.util.express.instruction.opdata.OperateDataArrayItem; import com.ql.util.express.instruction.opdata.OperateDataAttr; import com.ql.util.express.instruction.opdata.OperateDataField; @@ -50,7 +58,7 @@ public static OperateDataKeyValue fetchOperateDataKeyValue(OperateData key, Oper } public static RunEnvironment fetRunEnvironment(InstructionSet instructionSet, InstructionSetContext instructionSetContext, - boolean isTrace, ExecuteTimeOut executeTimeOut) { + boolean isTrace, ExecuteTimeout executeTimeOut) { return getOperateDataCache().fetRunEnvironment(instructionSet, instructionSetContext, isTrace, executeTimeOut); } @@ -59,8 +67,8 @@ public static CallResult fetchCallResult(Object returnValue, boolean isExit) { } public static InstructionSetContext fetchInstructionSetContext(boolean isExpandToParent, - ExpressRunner expressRunner, IExpressContext parent, ExpressLoader expressLoader, - boolean isSupportDynamicFieldName) { + ExpressRunner expressRunner, IExpressContext parent, ExpressLoader expressLoader, + boolean isSupportDynamicFieldName) { return getOperateDataCache().fetchInstructionSetContext(isExpandToParent, expressRunner, parent, expressLoader, isSupportDynamicFieldName); } diff --git a/src/main/java/com/ql/util/express/instruction/opdata/OperateDataVirClass.java b/src/main/java/com/ql/util/express/instruction/opdata/OperateDataVirClass.java index 7f60b53db..5f45cb676 100644 --- a/src/main/java/com/ql/util/express/instruction/opdata/OperateDataVirClass.java +++ b/src/main/java/com/ql/util/express/instruction/opdata/OperateDataVirClass.java @@ -2,7 +2,12 @@ import java.util.List; -import com.ql.util.express.*; +import com.ql.util.express.ExecuteTimeout; +import com.ql.util.express.InstructionSet; +import com.ql.util.express.InstructionSetContext; +import com.ql.util.express.InstructionSetRunner; +import com.ql.util.express.OperateData; +import com.ql.util.express.RunEnvironment; import com.ql.util.express.exception.QLException; import com.ql.util.express.instruction.OperateDataCacheManager; @@ -71,7 +76,7 @@ public OperateData callSelfFunction(String functionName, OperateData[] parameter operateDataLocalVar.setObject(tempContext, parameters[i].getObject(this.context)); } Object result = InstructionSetRunner.execute(functionSet, tempContext, null, - this.isTrace, false, true, ExecuteTimeOut.NO_TIMEOUT); + this.isTrace, false, true, ExecuteTimeout.NO_TIMEOUT); return OperateDataCacheManager.fetchOperateData(result, null); } @@ -89,7 +94,7 @@ public Object getValue(Object name) throws Exception { this.context.isSupportDynamicFieldName()); Object result = InstructionSetRunner.execute(this.context.getExpressRunner(), (InstructionSet)o, this.context.getExpressLoader(), tempContext, null, this.isTrace, false, false, - this.context.isSupportDynamicFieldName(), ExecuteTimeOut.NO_TIMEOUT); + this.context.isSupportDynamicFieldName(), ExecuteTimeout.NO_TIMEOUT); if (result instanceof OperateData) { return ((OperateData)result).getObject(this.context); } else { diff --git a/src/test/java/com/ql/util/express/ExecuteTimeOutTest.java b/src/test/java/com/ql/util/express/ExecuteTimeoutTest.java similarity index 68% rename from src/test/java/com/ql/util/express/ExecuteTimeOutTest.java rename to src/test/java/com/ql/util/express/ExecuteTimeoutTest.java index 53dd4b534..431037f28 100644 --- a/src/test/java/com/ql/util/express/ExecuteTimeOutTest.java +++ b/src/test/java/com/ql/util/express/ExecuteTimeoutTest.java @@ -7,13 +7,13 @@ /** * Author: DQinYuan */ -public class ExecuteTimeOutTest { +public class ExecuteTimeoutTest { @Test public void noTimeoutTest() throws InterruptedException { - assertFalse(ExecuteTimeOut.NO_TIMEOUT.isExpired()); + assertFalse(ExecuteTimeout.NO_TIMEOUT.isExpired()); - ExecuteTimeOut timeOut = new ExecuteTimeOut(20); + ExecuteTimeout timeOut = new ExecuteTimeout(20); Thread.sleep(15); assertFalse(timeOut.isExpired()); Thread.sleep(6);