blacklisted Script error should show initial exception

This commit is contained in:
vparomskiy 2018-09-11 16:33:39 +03:00
parent 831f6201db
commit 5ea053b71b
2 changed files with 54 additions and 39 deletions

View File

@ -17,13 +17,13 @@ package org.thingsboard.server.service.script;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import delight.nashornsandbox.NashornSandbox; import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes; import delight.nashornsandbox.NashornSandboxes;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory; import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -45,10 +45,9 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
private NashornSandbox sandbox; private NashornSandbox sandbox;
private ScriptEngine engine; private ScriptEngine engine;
private ExecutorService monitorExecutorService; private ExecutorService monitorExecutorService;
private ListeningExecutorService evalExecutorService;
private final Map<UUID, String> functionsMap = new ConcurrentHashMap<>(); private final Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
private final Map<BlackListKey, AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>(); private final Map<BlackListKey, BlackListInfo> blackListedFunctions = new ConcurrentHashMap<>();
private final Map<String, ScriptInfo> scriptKeyToInfo = new ConcurrentHashMap<>(); private final Map<String, ScriptInfo> scriptKeyToInfo = new ConcurrentHashMap<>();
private final Map<UUID, ScriptInfo> scriptIdToInfo = new ConcurrentHashMap<>(); private final Map<UUID, ScriptInfo> scriptIdToInfo = new ConcurrentHashMap<>();
@ -58,7 +57,6 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
if (useJsSandbox()) { if (useJsSandbox()) {
sandbox = NashornSandboxes.create(); sandbox = NashornSandboxes.create();
monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize()); monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize());
evalExecutorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
sandbox.setExecutor(monitorExecutorService); sandbox.setExecutor(monitorExecutorService);
sandbox.setMaxCPUTime(getMaxCpuTime()); sandbox.setMaxCPUTime(getMaxCpuTime());
sandbox.allowNoBraces(false); sandbox.allowNoBraces(false);
@ -74,9 +72,6 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
if (monitorExecutorService != null) { if (monitorExecutorService != null) {
monitorExecutorService.shutdownNow(); monitorExecutorService.shutdownNow();
} }
if (evalExecutorService != null) {
evalExecutorService.shutdownNow();
}
} }
protected abstract boolean useJsSandbox(); protected abstract boolean useJsSandbox();
@ -124,10 +119,28 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
public ListenableFuture<Object> invokeFunction(UUID scriptId, EntityId entityId, Object... args) { public ListenableFuture<Object> invokeFunction(UUID scriptId, EntityId entityId, Object... args) {
String functionName = functionsMap.get(scriptId); String functionName = functionsMap.get(scriptId);
if (functionName == null) { if (functionName == null) {
return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!")); String message = "No compiled script found for scriptId: [" + scriptId + "]!";
log.warn(message);
return Futures.immediateFailedFuture(new RuntimeException(message));
} }
if (!isBlackListed(scriptId)) {
BlackListInfo blackListInfo = blackListedFunctions.get(new BlackListKey(scriptId, entityId));
if (blackListInfo != null && blackListInfo.getCount() >= getMaxErrors()) {
RuntimeException throwable = new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!", blackListInfo.getCause());
throwable.printStackTrace();
return Futures.immediateFailedFuture(throwable);
}
try { try {
return invoke(functionName, args);
} catch (Exception e) {
BlackListKey blackListKey = new BlackListKey(scriptId, entityId);
blackListedFunctions.computeIfAbsent(blackListKey, key -> new BlackListInfo()).incrementWithReason(e);
return Futures.immediateFailedFuture(e);
}
}
private ListenableFuture<Object> invoke(String functionName, Object... args) throws ScriptException, NoSuchMethodException {
Object result; Object result;
if (useJsSandbox()) { if (useJsSandbox()) {
result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args); result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
@ -135,15 +148,6 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
result = ((Invocable) engine).invokeFunction(functionName, args); result = ((Invocable) engine).invokeFunction(functionName, args);
} }
return Futures.immediateFuture(result); return Futures.immediateFuture(result);
} catch (Exception e) {
BlackListKey blackListKey = new BlackListKey(scriptId, entityId);
blackListedFunctions.computeIfAbsent(blackListKey, key -> new AtomicInteger(0)).incrementAndGet();
return Futures.immediateFailedFuture(e);
}
} else {
return Futures.immediateFailedFuture(
new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!"));
}
} }
@Override @Override
@ -182,15 +186,6 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
} }
private boolean isBlackListed(UUID scriptId) {
if (blackListedFunctions.containsKey(scriptId)) {
AtomicInteger errorCount = blackListedFunctions.get(scriptId);
return errorCount.get() >= getMaxErrors();
} else {
return false;
}
}
private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) { private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) {
switch (scriptType) { switch (scriptType) {
case RULE_NODE_SCRIPT: case RULE_NODE_SCRIPT:
@ -233,13 +228,33 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
@EqualsAndHashCode @EqualsAndHashCode
@Getter @Getter
@RequiredArgsConstructor
private static class BlackListKey { private static class BlackListKey {
private final UUID scriptId; private final UUID scriptId;
private final EntityId entityId; private final EntityId entityId;
public BlackListKey(UUID scriptId, EntityId entityId) { }
this.scriptId = scriptId;
this.entityId = entityId; @Data
private static class BlackListInfo {
private final AtomicInteger count;
private Exception ex;
BlackListInfo() {
this.count = new AtomicInteger(0);
}
void incrementWithReason(Exception e) {
count.incrementAndGet();
ex = e;
}
int getCount() {
return count.get();
}
Exception getCause() {
return ex;
} }
} }
} }

View File

@ -171,10 +171,10 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
if (e.getCause() instanceof ScriptException) { if (e.getCause() instanceof ScriptException) {
throw (ScriptException)e.getCause(); throw (ScriptException)e.getCause();
} else { } else {
throw new ScriptException("Failed to execute js script: " + e.getMessage()); throw new ScriptException(e);
} }
} catch (Exception e) { } catch (Exception e) {
throw new ScriptException("Failed to execute js script: " + e.getMessage()); throw new ScriptException(e);
} }
} }