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.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<T extends ScriptInvokeService, R> implements ScriptEngine {
@ -51,7 +51,10 @@ public abstract class RuleNodeScriptEngine<T extends ScriptInvokeService, R> 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<T extends ScriptInvokeService, R> imp
return Futures.transformAsync(executeScriptAsync(msg), this::executeToStringTransform, MoreExecutors.directExecutor());
}
@Override
public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) {
return Futures.transformAsync(executeScriptAsync(msg),

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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);
}
});
}

View File

@ -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<UUID, String> scriptIdToHash = new ConcurrentHashMap<>();
protected final Map<String, TbelScript> scriptMap = new ConcurrentHashMap<>();
protected Cache<String, Serializable> compiledScriptsCache;
private final ConcurrentMap<UUID, String> scriptIdToHash = new ConcurrentHashMap<>();
private final ConcurrentMap<String, TbelScript> scriptMap = new ConcurrentHashMap<>();
private Cache<String, Serializable> compiledScriptsCache;
private SandboxedParserConfiguration parserConfig;
private final Optional<TbApiUsageStateClient> 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;
}
}

View File

@ -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();

View File

@ -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;