|
|
|
@ -15,21 +15,33 @@ |
|
|
|
*/ |
|
|
|
package org.thingsboard.server.service.script; |
|
|
|
|
|
|
|
import com.google.common.util.concurrent.FutureCallback; |
|
|
|
import com.google.common.util.concurrent.Futures; |
|
|
|
import com.google.common.util.concurrent.ListenableFuture; |
|
|
|
import delight.nashornsandbox.NashornSandbox; |
|
|
|
import delight.nashornsandbox.NashornSandboxes; |
|
|
|
import jdk.nashorn.api.scripting.NashornScriptEngineFactory; |
|
|
|
import lombok.Getter; |
|
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
|
import org.springframework.scheduling.annotation.Scheduled; |
|
|
|
|
|
|
|
import javax.annotation.Nullable; |
|
|
|
import javax.annotation.PostConstruct; |
|
|
|
import javax.annotation.PreDestroy; |
|
|
|
import javax.script.Invocable; |
|
|
|
import javax.script.ScriptEngine; |
|
|
|
import javax.script.ScriptException; |
|
|
|
import java.util.UUID; |
|
|
|
import java.util.concurrent.Callable; |
|
|
|
import java.util.concurrent.ExecutionException; |
|
|
|
import java.util.concurrent.ExecutorService; |
|
|
|
import java.util.concurrent.Executors; |
|
|
|
import java.util.concurrent.ScheduledExecutorService; |
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
import java.util.concurrent.TimeoutException; |
|
|
|
import java.util.concurrent.atomic.AtomicInteger; |
|
|
|
|
|
|
|
@Slf4j |
|
|
|
public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeService { |
|
|
|
@ -37,9 +49,46 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer |
|
|
|
private NashornSandbox sandbox; |
|
|
|
private ScriptEngine engine; |
|
|
|
private ExecutorService monitorExecutorService; |
|
|
|
private ScheduledExecutorService timeoutExecutorService; |
|
|
|
|
|
|
|
private final AtomicInteger jsPushedMsgs = new AtomicInteger(0); |
|
|
|
private final AtomicInteger jsInvokeMsgs = new AtomicInteger(0); |
|
|
|
private final AtomicInteger jsEvalMsgs = new AtomicInteger(0); |
|
|
|
private final AtomicInteger jsFailedMsgs = new AtomicInteger(0); |
|
|
|
private final AtomicInteger jsTimeoutMsgs = new AtomicInteger(0); |
|
|
|
private final FutureCallback<UUID> evalCallback = new JsStatCallback<UUID>(jsEvalMsgs, jsTimeoutMsgs, jsFailedMsgs); |
|
|
|
private final FutureCallback<Object> invokeCallback = new JsStatCallback<Object>(jsInvokeMsgs, jsTimeoutMsgs, jsFailedMsgs); |
|
|
|
|
|
|
|
@Autowired |
|
|
|
@Getter |
|
|
|
private JsExecutorService jsExecutor; |
|
|
|
|
|
|
|
@Value("${js.local.max_requests_timeout:0}") |
|
|
|
private long maxRequestsTimeout; |
|
|
|
|
|
|
|
@Value("${js.local.stats.enabled:false}") |
|
|
|
private boolean statsEnabled; |
|
|
|
|
|
|
|
@Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms:10000}") |
|
|
|
public void printStats() { |
|
|
|
if (statsEnabled) { |
|
|
|
int pushedMsgs = jsPushedMsgs.getAndSet(0); |
|
|
|
int invokeMsgs = jsInvokeMsgs.getAndSet(0); |
|
|
|
int evalMsgs = jsEvalMsgs.getAndSet(0); |
|
|
|
int failed = jsFailedMsgs.getAndSet(0); |
|
|
|
int timedOut = jsTimeoutMsgs.getAndSet(0); |
|
|
|
if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) { |
|
|
|
log.info("Nashorn JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]", |
|
|
|
pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@PostConstruct |
|
|
|
public void init() { |
|
|
|
if (maxRequestsTimeout > 0) { |
|
|
|
timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(); |
|
|
|
} |
|
|
|
if (useJsSandbox()) { |
|
|
|
sandbox = NashornSandboxes.create(); |
|
|
|
monitorExecutorService = Executors.newWorkStealingPool(getMonitorThreadPoolSize()); |
|
|
|
@ -59,6 +108,9 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer |
|
|
|
if (monitorExecutorService != null) { |
|
|
|
monitorExecutorService.shutdownNow(); |
|
|
|
} |
|
|
|
if (timeoutExecutorService != null) { |
|
|
|
timeoutExecutorService.shutdownNow(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
protected abstract boolean useJsSandbox(); |
|
|
|
@ -69,34 +121,49 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer |
|
|
|
|
|
|
|
@Override |
|
|
|
protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String jsScript) { |
|
|
|
try { |
|
|
|
if (useJsSandbox()) { |
|
|
|
sandbox.eval(jsScript); |
|
|
|
} else { |
|
|
|
engine.eval(jsScript); |
|
|
|
jsPushedMsgs.incrementAndGet(); |
|
|
|
ListenableFuture<UUID> result = jsExecutor.executeAsync(() -> { |
|
|
|
try { |
|
|
|
if (useJsSandbox()) { |
|
|
|
sandbox.eval(jsScript); |
|
|
|
} else { |
|
|
|
engine.eval(jsScript); |
|
|
|
} |
|
|
|
scriptIdToNameMap.put(scriptId, functionName); |
|
|
|
return scriptId; |
|
|
|
} catch (Exception e) { |
|
|
|
log.warn("Failed to compile JS script: {}", e.getMessage(), e); |
|
|
|
throw new ExecutionException(e); |
|
|
|
} |
|
|
|
scriptIdToNameMap.put(scriptId, functionName); |
|
|
|
} catch (Exception e) { |
|
|
|
log.warn("Failed to compile JS script: {}", e.getMessage(), e); |
|
|
|
return Futures.immediateFailedFuture(e); |
|
|
|
}); |
|
|
|
if (maxRequestsTimeout > 0) { |
|
|
|
result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); |
|
|
|
} |
|
|
|
return Futures.immediateFuture(scriptId); |
|
|
|
Futures.addCallback(result, evalCallback); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) { |
|
|
|
try { |
|
|
|
Object result; |
|
|
|
if (useJsSandbox()) { |
|
|
|
result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args); |
|
|
|
} else { |
|
|
|
result = ((Invocable) engine).invokeFunction(functionName, args); |
|
|
|
jsPushedMsgs.incrementAndGet(); |
|
|
|
ListenableFuture<Object> result = jsExecutor.executeAsync(() -> { |
|
|
|
try { |
|
|
|
if (useJsSandbox()) { |
|
|
|
return sandbox.getSandboxedInvocable().invokeFunction(functionName, args); |
|
|
|
} else { |
|
|
|
return ((Invocable) engine).invokeFunction(functionName, args); |
|
|
|
} |
|
|
|
} catch (Exception e) { |
|
|
|
onScriptExecutionError(scriptId); |
|
|
|
throw new ExecutionException(e); |
|
|
|
} |
|
|
|
return Futures.immediateFuture(result); |
|
|
|
} catch (Exception e) { |
|
|
|
onScriptExecutionError(scriptId); |
|
|
|
return Futures.immediateFailedFuture(e); |
|
|
|
}); |
|
|
|
|
|
|
|
if (maxRequestsTimeout > 0) { |
|
|
|
result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); |
|
|
|
} |
|
|
|
Futures.addCallback(result, invokeCallback); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
protected void doRelease(UUID scriptId, String functionName) throws ScriptException { |
|
|
|
|