Commit 5ea053b71b385e53dc92728ea0486e39b3851b7c

Authored by vparomskiy
1 parent 831f6201

blacklisted Script error should show initial exception

... ... @@ -17,13 +17,13 @@ package org.thingsboard.server.service.script;
17 17
18 18 import com.google.common.util.concurrent.Futures;
19 19 import com.google.common.util.concurrent.ListenableFuture;
20   -import com.google.common.util.concurrent.ListeningExecutorService;
21   -import com.google.common.util.concurrent.MoreExecutors;
22 20 import delight.nashornsandbox.NashornSandbox;
23 21 import delight.nashornsandbox.NashornSandboxes;
24 22 import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
  23 +import lombok.Data;
25 24 import lombok.EqualsAndHashCode;
26 25 import lombok.Getter;
  26 +import lombok.RequiredArgsConstructor;
27 27 import lombok.extern.slf4j.Slf4j;
28 28 import org.thingsboard.server.common.data.id.EntityId;
29 29
... ... @@ -45,10 +45,9 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
45 45 private NashornSandbox sandbox;
46 46 private ScriptEngine engine;
47 47 private ExecutorService monitorExecutorService;
48   - private ListeningExecutorService evalExecutorService;
49 48
50 49 private final Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
51   - private final Map<BlackListKey, AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
  50 + private final Map<BlackListKey, BlackListInfo> blackListedFunctions = new ConcurrentHashMap<>();
52 51
53 52 private final Map<String, ScriptInfo> scriptKeyToInfo = new ConcurrentHashMap<>();
54 53 private final Map<UUID, ScriptInfo> scriptIdToInfo = new ConcurrentHashMap<>();
... ... @@ -58,7 +57,6 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
58 57 if (useJsSandbox()) {
59 58 sandbox = NashornSandboxes.create();
60 59 monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize());
61   - evalExecutorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
62 60 sandbox.setExecutor(monitorExecutorService);
63 61 sandbox.setMaxCPUTime(getMaxCpuTime());
64 62 sandbox.allowNoBraces(false);
... ... @@ -74,9 +72,6 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
74 72 if (monitorExecutorService != null) {
75 73 monitorExecutorService.shutdownNow();
76 74 }
77   - if (evalExecutorService != null) {
78   - evalExecutorService.shutdownNow();
79   - }
80 75 }
81 76
82 77 protected abstract boolean useJsSandbox();
... ... @@ -124,26 +119,35 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
124 119 public ListenableFuture<Object> invokeFunction(UUID scriptId, EntityId entityId, Object... args) {
125 120 String functionName = functionsMap.get(scriptId);
126 121 if (functionName == null) {
127   - return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!"));
128   - }
129   - if (!isBlackListed(scriptId)) {
130   - try {
131   - Object result;
132   - if (useJsSandbox()) {
133   - result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
134   - } else {
135   - result = ((Invocable) engine).invokeFunction(functionName, args);
136   - }
137   - return Futures.immediateFuture(result);
138   - } catch (Exception e) {
139   - BlackListKey blackListKey = new BlackListKey(scriptId, entityId);
140   - blackListedFunctions.computeIfAbsent(blackListKey, key -> new AtomicInteger(0)).incrementAndGet();
141   - return Futures.immediateFailedFuture(e);
142   - }
  122 + String message = "No compiled script found for scriptId: [" + scriptId + "]!";
  123 + log.warn(message);
  124 + return Futures.immediateFailedFuture(new RuntimeException(message));
  125 + }
  126 +
  127 + BlackListInfo blackListInfo = blackListedFunctions.get(new BlackListKey(scriptId, entityId));
  128 + if (blackListInfo != null && blackListInfo.getCount() >= getMaxErrors()) {
  129 + RuntimeException throwable = new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!", blackListInfo.getCause());
  130 + throwable.printStackTrace();
  131 + return Futures.immediateFailedFuture(throwable);
  132 + }
  133 +
  134 + try {
  135 + return invoke(functionName, args);
  136 + } catch (Exception e) {
  137 + BlackListKey blackListKey = new BlackListKey(scriptId, entityId);
  138 + blackListedFunctions.computeIfAbsent(blackListKey, key -> new BlackListInfo()).incrementWithReason(e);
  139 + return Futures.immediateFailedFuture(e);
  140 + }
  141 + }
  142 +
  143 + private ListenableFuture<Object> invoke(String functionName, Object... args) throws ScriptException, NoSuchMethodException {
  144 + Object result;
  145 + if (useJsSandbox()) {
  146 + result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
143 147 } else {
144   - return Futures.immediateFailedFuture(
145   - new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!"));
  148 + result = ((Invocable) engine).invokeFunction(functionName, args);
146 149 }
  150 + return Futures.immediateFuture(result);
147 151 }
148 152
149 153 @Override
... ... @@ -182,15 +186,6 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
182 186 }
183 187
184 188
185   - private boolean isBlackListed(UUID scriptId) {
186   - if (blackListedFunctions.containsKey(scriptId)) {
187   - AtomicInteger errorCount = blackListedFunctions.get(scriptId);
188   - return errorCount.get() >= getMaxErrors();
189   - } else {
190   - return false;
191   - }
192   - }
193   -
194 189 private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) {
195 190 switch (scriptType) {
196 191 case RULE_NODE_SCRIPT:
... ... @@ -233,13 +228,33 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
233 228
234 229 @EqualsAndHashCode
235 230 @Getter
  231 + @RequiredArgsConstructor
236 232 private static class BlackListKey {
237 233 private final UUID scriptId;
238 234 private final EntityId entityId;
239 235
240   - public BlackListKey(UUID scriptId, EntityId entityId) {
241   - this.scriptId = scriptId;
242   - this.entityId = entityId;
  236 + }
  237 +
  238 + @Data
  239 + private static class BlackListInfo {
  240 + private final AtomicInteger count;
  241 + private Exception ex;
  242 +
  243 + BlackListInfo() {
  244 + this.count = new AtomicInteger(0);
  245 + }
  246 +
  247 + void incrementWithReason(Exception e) {
  248 + count.incrementAndGet();
  249 + ex = e;
  250 + }
  251 +
  252 + int getCount() {
  253 + return count.get();
  254 + }
  255 +
  256 + Exception getCause() {
  257 + return ex;
243 258 }
244 259 }
245 260 }
... ...
... ... @@ -171,10 +171,10 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
171 171 if (e.getCause() instanceof ScriptException) {
172 172 throw (ScriptException)e.getCause();
173 173 } else {
174   - throw new ScriptException("Failed to execute js script: " + e.getMessage());
  174 + throw new ScriptException(e);
175 175 }
176 176 } catch (Exception e) {
177   - throw new ScriptException("Failed to execute js script: " + e.getMessage());
  177 + throw new ScriptException(e);
178 178 }
179 179 }
180 180
... ...