deduplicate JS scripts in JsScriptEngine

This commit is contained in:
vparomskiy 2018-08-28 16:54:31 +03:00
parent 79cf2ffa7f
commit 142a422b7a
2 changed files with 45 additions and 16 deletions

View File

@ -22,6 +22,7 @@ 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.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -42,9 +43,10 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
private ScriptEngine engine; private ScriptEngine engine;
private ExecutorService monitorExecutorService; private ExecutorService monitorExecutorService;
private Map<UUID, String> functionsMap = new ConcurrentHashMap<>(); private final Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
private final Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
private Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>(); private final Map<String, Pair<UUID, AtomicInteger>> scriptToId = new ConcurrentHashMap<>();
private final Map<UUID, AtomicInteger> scriptIdToCount = new ConcurrentHashMap<>();
@PostConstruct @PostConstruct
public void init() { public void init() {
@ -78,8 +80,12 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
@Override @Override
public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) { public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) {
UUID scriptId = UUID.randomUUID(); Pair<UUID, AtomicInteger> deduplicated = deduplicate(scriptType, scriptBody);
String functionName = "invokeInternal_" + scriptId.toString().replace('-','_'); UUID scriptId = deduplicated.getLeft();
AtomicInteger duplicateCount = deduplicated.getRight();
if(duplicateCount.compareAndSet(0, 1)) {
String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_');
String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
try { try {
if (useJsSandbox()) { if (useJsSandbox()) {
@ -89,9 +95,13 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
} }
functionsMap.put(scriptId, functionName); functionsMap.put(scriptId, functionName);
} catch (Exception e) { } catch (Exception e) {
duplicateCount.decrementAndGet();
log.warn("Failed to compile JS script: {}", e.getMessage(), e); log.warn("Failed to compile JS script: {}", e.getMessage(), e);
return Futures.immediateFailedFuture(e); return Futures.immediateFailedFuture(e);
} }
} else {
duplicateCount.incrementAndGet();
}
return Futures.immediateFuture(scriptId); return Futures.immediateFuture(scriptId);
} }
@ -122,6 +132,13 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
@Override @Override
public ListenableFuture<Void> release(UUID scriptId) { public ListenableFuture<Void> release(UUID scriptId) {
AtomicInteger count = scriptIdToCount.get(scriptId);
if(count != null) {
if(count.decrementAndGet() > 0) {
return Futures.immediateFuture(null);
}
}
String functionName = functionsMap.get(scriptId); String functionName = functionsMap.get(scriptId);
if (functionName != null) { if (functionName != null) {
try { try {
@ -156,4 +173,16 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
throw new RuntimeException("No script factory implemented for scriptType: " + scriptType); throw new RuntimeException("No script factory implemented for scriptType: " + scriptType);
} }
} }
private Pair<UUID, AtomicInteger> deduplicate(JsScriptType scriptType, String scriptBody) {
Pair<UUID, AtomicInteger> precomputed = Pair.of(UUID.randomUUID(), new AtomicInteger());
Pair<UUID, AtomicInteger> pair = scriptToId.computeIfAbsent(deduplicateKey(scriptType, scriptBody), i -> precomputed);
AtomicInteger duplicateCount = scriptIdToCount.computeIfAbsent(pair.getLeft(), i -> pair.getRight());
return Pair.of(pair.getLeft(), duplicateCount);
}
private String deduplicateKey(JsScriptType scriptType, String scriptBody) {
return scriptType + "_" + scriptBody;
}
} }

View File

@ -45,7 +45,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
try { try {
this.scriptId = this.sandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, script, argNames).get(); this.scriptId = this.sandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, script, argNames).get();
} catch (Exception e) { } catch (Exception e) {
throw new IllegalArgumentException("Can't compile script: " + e.getMessage()); throw new IllegalArgumentException("Can't compile script: " + e.getMessage(), e);
} }
} }