Make script compilation errors unrecoverable during rule node initialization

This commit is contained in:
Dmytro Skarzhynets 2025-07-07 17:07:30 +03:00
parent 12863e5274
commit 69964a2413
No known key found for this signature in database
GPG Key ID: 2B51652F224037DF
7 changed files with 47 additions and 21 deletions

View File

@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.script.api.ScriptInvokeService; import org.thingsboard.script.api.ScriptInvokeService;
import org.thingsboard.script.api.ScriptType; 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.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -32,7 +33,6 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@Slf4j @Slf4j
public abstract class RuleNodeScriptEngine<T extends ScriptInvokeService, R> implements ScriptEngine { public abstract class RuleNodeScriptEngine<T extends ScriptInvokeService, R> implements ScriptEngine {
@ -51,7 +51,10 @@ public abstract class RuleNodeScriptEngine<T extends ScriptInvokeService, R> imp
if (e instanceof ExecutionException) { if (e instanceof ExecutionException) {
t = e.getCause(); 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<T extends ScriptInvokeService, R> imp
return Futures.transformAsync(executeScriptAsync(msg), this::executeToStringTransform, MoreExecutors.directExecutor()); return Futures.transformAsync(executeScriptAsync(msg), this::executeToStringTransform, MoreExecutors.directExecutor());
} }
@Override @Override
public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) { public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) {
return Futures.transformAsync(executeScriptAsync(msg), return Futures.transformAsync(executeScriptAsync(msg),

View File

@ -19,8 +19,8 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EntityType; 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.MsgType;
import org.thingsboard.server.common.msg.TbActorError;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbActorStopReason; import org.thingsboard.server.common.msg.TbActorStopReason;
@ -35,6 +35,7 @@ import java.util.function.Supplier;
@Getter @Getter
@RequiredArgsConstructor @RequiredArgsConstructor
public final class TbActorMailbox implements TbActorCtx { public final class TbActorMailbox implements TbActorCtx {
private static final boolean HIGH_PRIORITY = true; private static final boolean HIGH_PRIORITY = true;
private static final boolean NORMAL_PRIORITY = false; private static final boolean NORMAL_PRIORITY = false;
@ -100,7 +101,7 @@ public final class TbActorMailbox implements TbActorCtx {
if (t instanceof TbActorException && t.getCause() != null) { if (t instanceof TbActorException && t.getCause() != null) {
t = t.getCause(); t = t.getCause();
} }
return t instanceof TbActorError && ((TbActorError) t).isUnrecoverable(); return t instanceof RecoveryAware recoveryAware && recoveryAware.isUnrecoverable();
} }
private void enqueue(TbActorMsg msg, boolean highPriority) { private void enqueue(TbActorMsg msg, boolean highPriority) {

View File

@ -16,13 +16,24 @@
package org.thingsboard.script.api; package org.thingsboard.script.api;
import lombok.Getter; import lombok.Getter;
import org.thingsboard.common.util.RecoveryAware;
import java.io.Serial;
import java.util.UUID; import java.util.UUID;
public class TbScriptException extends RuntimeException { public class TbScriptException extends RuntimeException implements RecoveryAware {
@Serial
private static final long serialVersionUID = -1958193538782818284L; private static final long serialVersionUID = -1958193538782818284L;
public static enum ErrorCode {COMPILATION, TIMEOUT, RUNTIME, OTHER} public enum ErrorCode {
COMPILATION,
TIMEOUT,
RUNTIME,
OTHER
}
@Getter @Getter
private final UUID scriptId; private final UUID scriptId;
@ -37,4 +48,10 @@ public class TbScriptException extends RuntimeException {
this.errorCode = errorCode; this.errorCode = errorCode;
this.body = body; this.body = body;
} }
@Override
public boolean isUnrecoverable() {
return errorCode == ErrorCode.COMPILATION;
}
} }

View File

@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import delight.nashornsandbox.NashornSandbox; import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes; import delight.nashornsandbox.NashornSandboxes;
import delight.nashornsandbox.exceptions.ScriptCPUAbuseException;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.Getter; import lombok.Getter;
@ -153,8 +154,12 @@ public class NashornJsInvokeService extends AbstractJsInvokeService {
} }
scriptInfoMap.put(scriptId, scriptInfo); scriptInfoMap.put(scriptId, scriptInfo);
return scriptId; return scriptId;
} catch (Exception e) { } catch (ScriptException e) {
throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, jsScript, 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);
} }
}); });
} }

View File

@ -27,6 +27,7 @@ import jakarta.annotation.PreDestroy;
import lombok.Getter; import lombok.Getter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.mvel2.CompileException;
import org.mvel2.ExecutionContext; import org.mvel2.ExecutionContext;
import org.mvel2.MVEL; import org.mvel2.MVEL;
import org.mvel2.ParserContext; import org.mvel2.ParserContext;
@ -52,11 +53,11 @@ import java.io.Serializable;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -66,9 +67,9 @@ import java.util.concurrent.locks.ReentrantLock;
@Service @Service
public class DefaultTbelInvokeService extends AbstractScriptInvokeService implements TbelInvokeService { public class DefaultTbelInvokeService extends AbstractScriptInvokeService implements TbelInvokeService {
protected final Map<UUID, String> scriptIdToHash = new ConcurrentHashMap<>(); private final ConcurrentMap<UUID, String> scriptIdToHash = new ConcurrentHashMap<>();
protected final Map<String, TbelScript> scriptMap = new ConcurrentHashMap<>(); private final ConcurrentMap<String, TbelScript> scriptMap = new ConcurrentHashMap<>();
protected Cache<String, Serializable> compiledScriptsCache; private Cache<String, Serializable> compiledScriptsCache;
private SandboxedParserConfiguration parserConfig; private SandboxedParserConfiguration parserConfig;
private final Optional<TbApiUsageStateClient> apiUsageStateClient; private final Optional<TbApiUsageStateClient> apiUsageStateClient;
@ -204,8 +205,10 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem
lock.unlock(); lock.unlock();
} }
return scriptId; return scriptId;
} catch (Exception e) { } catch (CompileException e) {
throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, scriptBody, 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()); return MVEL.compileExpression(scriptBody, new ParserContext());
} }
@ -269,4 +272,5 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem
protected StatsType getStatsType() { protected StatsType getStatsType() {
return StatsType.TBEL_INVOKE; return StatsType.TBEL_INVOKE;
} }
} }

View File

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.common.msg; package org.thingsboard.common.util;
public interface TbActorError { public interface RecoveryAware {
boolean isUnrecoverable(); boolean isUnrecoverable();

View File

@ -16,12 +16,9 @@
package org.thingsboard.rule.engine.api; package org.thingsboard.rule.engine.api;
import lombok.Getter; import lombok.Getter;
import org.thingsboard.server.common.msg.TbActorError; import org.thingsboard.common.util.RecoveryAware;
/** public class TbNodeException extends Exception implements RecoveryAware {
* Created by ashvayka on 19.01.18.
*/
public class TbNodeException extends Exception implements TbActorError {
@Getter @Getter
private final boolean unrecoverable; private final boolean unrecoverable;