Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

超时设置的线程重入问题修复 #314

Merged
merged 2 commits into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/main/java/com/ql/util/express/ExecuteTimeout.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
37 changes: 19 additions & 18 deletions src/main/java/com/ql/util/express/ExpressRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ public void clearExpressCache() {
public Object executeByExpressName(String name, IExpressContext<String, Object> context, List<String> 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);
}

/**
Expand All @@ -593,7 +593,7 @@ public Object executeByExpressName(String name, IExpressContext<String, Object>
*/
public Object execute(InstructionSet instructionSet, IExpressContext<String, Object> context,
List<String> errorList, boolean isTrace, boolean isCatchException) throws Exception {
return executeReentrant(instructionSet, context, errorList, isTrace, isCatchException);
return executeReentrant(instructionSet, context, errorList, isTrace, isCatchException, -1);
}

/**
Expand All @@ -604,19 +604,13 @@ public Object execute(InstructionSet instructionSet, IExpressContext<String, Obj
* @param errorList 输出的错误信息List
* @param isCache 是否使用Cache中的指令集
* @param isTrace 是否输出详细的执行指令信息
* @param timeoutMillis 超时毫秒时间
* @param timeoutMillis 超时毫秒时间, -1 表示不给本次执行单独设置超时时间, 但是可能会有全局超时时间, 参考 {@link QLExpressTimer#setTimeout(long)}
* @return
* @throws Exception
*/
public Object execute(String expressString, IExpressContext<String, Object> context, List<String> 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);
}

/**
Expand All @@ -632,27 +626,34 @@ public Object execute(String expressString, IExpressContext<String, Object> cont
*/
public Object execute(String expressString, IExpressContext<String, Object> context, List<String> errorList,
boolean isCache, boolean isTrace) throws Exception {
return executeInner(expressString, context, errorList, isCache, isTrace, -1);
}

private Object executeInner(String expressString, IExpressContext<String, Object> context, List<String> 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<String, Object> iExpressContext,
List<String> errorList, boolean isTrace, boolean isCatchException) throws Exception {
private Object executeReentrant(InstructionSet parseResult, IExpressContext<String, Object> context, List<String> 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);
}
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/com/ql/util/express/InstructionSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -191,7 +192,10 @@ public void executeInnerOriginalInstruction(RunEnvironment environment, List<Str
Instruction instruction = null;
try {
while (environment.programPoint < this.instructionList.length) {
QLExpressTimer.assertTimeOut();
if (environment.isExecuteTimeout()) {
throw new QLTimeoutException("运行QLExpress脚本的下一条指令超过了限定时间:"
+ environment.getExecuteTimeOut().getTimeoutMillis() + "ms");
}
instruction = this.instructionList[environment.programPoint];
instruction.execute(environment, errorList);
}
Expand Down
43 changes: 24 additions & 19 deletions src/main/java/com/ql/util/express/InstructionSetRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ private InstructionSetRunner() {

public static Object executeOuter(ExpressRunner runner, InstructionSet instructionSet, ExpressLoader loader,
IExpressContext<String, Object> iExpressContext, List<String> 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();
}
Expand All @@ -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<String, Object> iExpressContext, List<String> 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<String> 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()) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/ql/util/express/QLambda.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

/**
Expand Down
23 changes: 21 additions & 2 deletions src/main/java/com/ql/util/express/RunEnvironment.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -37,6 +46,8 @@ public void clear() {

instructionSet = null;
context = null;

executeTimeOut = null;
}

public InstructionSet getInstructionSet() {
Expand Down Expand Up @@ -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;
}
}
52 changes: 12 additions & 40 deletions src/main/java/com/ql/util/express/config/QLExpressTimer.java
Original file line number Diff line number Diff line change
@@ -1,63 +1,35 @@
package com.ql.util.express.config;

import com.ql.util.express.exception.QLTimeoutException;

/**
* @author [email protected]
* @since 2019/6/17 4:12 PM
*/
public class QLExpressTimer {
private static final ThreadLocal<Boolean> NEED_TIMER = ThreadLocal.withInitial(() -> false);
private static final ThreadLocal<Long> TIME_OUT_MILLIS = new ThreadLocal<Long>() {};
private static final ThreadLocal<Long> START_TIME = new ThreadLocal<Long>() {};
private static final ThreadLocal<Long> END_TIME = new ThreadLocal<Long>() {};

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);
globalTimeoutMillis = 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());
}
}

/**
* 断言是否超时
* 设置全局脚本超时时间, 默认 -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;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ql.util.express.instruction;

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;
Expand Down Expand Up @@ -28,12 +29,12 @@ 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);

InstructionSetContext fetchInstructionSetContext(boolean isExpandToParent, ExpressRunner expressRunner,
IExpressContext<String, Object> parent, ExpressLoader expressLoader, boolean isSupportDynamicFieldName);
IExpressContext<String, Object> parent, ExpressLoader expressLoader, boolean isSupportDynamicFieldName);

void resetCache();

Expand Down
Loading