From 69964a2413933c8b97d4dafeb1746dee47d28429 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 7 Jul 2025 17:07:30 +0300 Subject: [PATCH] Make script compilation errors unrecoverable during rule node initialization --- .../service/script/RuleNodeScriptEngine.java | 8 ++++--- .../server/actors/TbActorMailbox.java | 5 +++-- .../script/api/TbScriptException.java | 21 +++++++++++++++++-- .../script/api/js/NashornJsInvokeService.java | 7 ++++++- .../api/tbel/DefaultTbelInvokeService.java | 16 ++++++++------ .../common/util/RecoveryAware.java} | 4 ++-- .../rule/engine/api/TbNodeException.java | 7 ++----- 7 files changed, 47 insertions(+), 21 deletions(-) rename common/{message/src/main/java/org/thingsboard/server/common/msg/TbActorError.java => util/src/main/java/org/thingsboard/common/util/RecoveryAware.java} (89%) diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java index 8f19aeb0a3..d99f1654f3 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java @@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.script.api.ScriptInvokeService; import org.thingsboard.script.api.ScriptType; +import org.thingsboard.script.api.TbScriptException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; @@ -32,7 +33,6 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutionException; - @Slf4j public abstract class RuleNodeScriptEngine implements ScriptEngine { @@ -51,7 +51,10 @@ public abstract class RuleNodeScriptEngine imp if (e instanceof ExecutionException) { t = e.getCause(); } - throw new IllegalArgumentException("Can't compile script: " + t.getMessage(), t); + if (t instanceof TbScriptException scriptException) { + throw scriptException; + } + throw new RuntimeException("Unexpected error when creating script engine: " + t.getMessage(), t); } } @@ -81,7 +84,6 @@ public abstract class RuleNodeScriptEngine imp return Futures.transformAsync(executeScriptAsync(msg), this::executeToStringTransform, MoreExecutors.directExecutor()); } - @Override public ListenableFuture executeFilterAsync(TbMsg msg) { return Futures.transformAsync(executeScriptAsync(msg), diff --git a/common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java b/common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java index 6cd28fa98d..c20726d765 100644 --- a/common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java +++ b/common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java @@ -19,8 +19,8 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.common.util.RecoveryAware; import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.TbActorError; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorStopReason; @@ -35,6 +35,7 @@ import java.util.function.Supplier; @Getter @RequiredArgsConstructor public final class TbActorMailbox implements TbActorCtx { + private static final boolean HIGH_PRIORITY = true; private static final boolean NORMAL_PRIORITY = false; @@ -100,7 +101,7 @@ public final class TbActorMailbox implements TbActorCtx { if (t instanceof TbActorException && t.getCause() != null) { t = t.getCause(); } - return t instanceof TbActorError && ((TbActorError) t).isUnrecoverable(); + return t instanceof RecoveryAware recoveryAware && recoveryAware.isUnrecoverable(); } private void enqueue(TbActorMsg msg, boolean highPriority) { diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/TbScriptException.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/TbScriptException.java index 77888db255..347490b3fb 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/TbScriptException.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/TbScriptException.java @@ -16,13 +16,24 @@ package org.thingsboard.script.api; import lombok.Getter; +import org.thingsboard.common.util.RecoveryAware; +import java.io.Serial; import java.util.UUID; -public class TbScriptException extends RuntimeException { +public class TbScriptException extends RuntimeException implements RecoveryAware { + + @Serial private static final long serialVersionUID = -1958193538782818284L; - public static enum ErrorCode {COMPILATION, TIMEOUT, RUNTIME, OTHER} + public enum ErrorCode { + + COMPILATION, + TIMEOUT, + RUNTIME, + OTHER + + } @Getter private final UUID scriptId; @@ -37,4 +48,10 @@ public class TbScriptException extends RuntimeException { this.errorCode = errorCode; this.body = body; } + + @Override + public boolean isUnrecoverable() { + return errorCode == ErrorCode.COMPILATION; + } + } diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java index 3507dd87e6..4aedab8081 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import delight.nashornsandbox.NashornSandbox; import delight.nashornsandbox.NashornSandboxes; +import delight.nashornsandbox.exceptions.ScriptCPUAbuseException; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import lombok.Getter; @@ -153,8 +154,12 @@ public class NashornJsInvokeService extends AbstractJsInvokeService { } scriptInfoMap.put(scriptId, scriptInfo); return scriptId; - } catch (Exception e) { + } catch (ScriptException e) { throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, jsScript, e); + } catch (ScriptCPUAbuseException e) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.TIMEOUT, jsScript, e); + } catch (Exception e) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.OTHER, jsScript, e); } }); } diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java index 25a7ede547..e2195a6bbf 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java @@ -27,6 +27,7 @@ import jakarta.annotation.PreDestroy; import lombok.Getter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.mvel2.CompileException; import org.mvel2.ExecutionContext; import org.mvel2.MVEL; import org.mvel2.ParserContext; @@ -52,11 +53,11 @@ import java.io.Serializable; import java.nio.charset.StandardCharsets; import java.util.Calendar; import java.util.Collections; -import java.util.Map; import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -66,9 +67,9 @@ import java.util.concurrent.locks.ReentrantLock; @Service public class DefaultTbelInvokeService extends AbstractScriptInvokeService implements TbelInvokeService { - protected final Map scriptIdToHash = new ConcurrentHashMap<>(); - protected final Map scriptMap = new ConcurrentHashMap<>(); - protected Cache compiledScriptsCache; + private final ConcurrentMap scriptIdToHash = new ConcurrentHashMap<>(); + private final ConcurrentMap scriptMap = new ConcurrentHashMap<>(); + private Cache compiledScriptsCache; private SandboxedParserConfiguration parserConfig; private final Optional apiUsageStateClient; @@ -204,8 +205,10 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem lock.unlock(); } return scriptId; - } catch (Exception e) { + } catch (CompileException e) { throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, scriptBody, e); + } catch (Exception e) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.OTHER, scriptBody, e); } }); } @@ -246,7 +249,7 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem } } - private Serializable compileScript(String scriptBody) { + private static Serializable compileScript(String scriptBody) throws CompileException { return MVEL.compileExpression(scriptBody, new ParserContext()); } @@ -269,4 +272,5 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem protected StatsType getStatsType() { return StatsType.TBEL_INVOKE; } + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorError.java b/common/util/src/main/java/org/thingsboard/common/util/RecoveryAware.java similarity index 89% rename from common/message/src/main/java/org/thingsboard/server/common/msg/TbActorError.java rename to common/util/src/main/java/org/thingsboard/common/util/RecoveryAware.java index 8c322d8eb5..e1553bec36 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorError.java +++ b/common/util/src/main/java/org/thingsboard/common/util/RecoveryAware.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg; +package org.thingsboard.common.util; -public interface TbActorError { +public interface RecoveryAware { boolean isUnrecoverable(); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java index 874a37792d..7d5a7443d3 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java @@ -16,12 +16,9 @@ package org.thingsboard.rule.engine.api; import lombok.Getter; -import org.thingsboard.server.common.msg.TbActorError; +import org.thingsboard.common.util.RecoveryAware; -/** - * Created by ashvayka on 19.01.18. - */ -public class TbNodeException extends Exception implements TbActorError { +public class TbNodeException extends Exception implements RecoveryAware { @Getter private final boolean unrecoverable;