Browse Source

Merge branch 'master' of https://github.com/thingsboard/thingsboard into CopyToView

pull/2278/head
vlad 7 years ago
parent
commit
958291549c
  1. 107
      application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java
  2. 46
      application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java
  3. 5
      application/src/main/resources/thingsboard.yml
  4. 9
      pom.xml

107
application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java

@ -15,21 +15,33 @@
*/ */
package org.thingsboard.server.service.script; 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.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
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.Getter;
import lombok.extern.slf4j.Slf4j; 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.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import javax.script.Invocable; import javax.script.Invocable;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
import javax.script.ScriptException; import javax.script.ScriptException;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; 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 @Slf4j
public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeService { public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeService {
@ -37,9 +49,46 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
private NashornSandbox sandbox; private NashornSandbox sandbox;
private ScriptEngine engine; private ScriptEngine engine;
private ExecutorService monitorExecutorService; 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 @PostConstruct
public void init() { public void init() {
if (maxRequestsTimeout > 0) {
timeoutExecutorService = Executors.newSingleThreadScheduledExecutor();
}
if (useJsSandbox()) { if (useJsSandbox()) {
sandbox = NashornSandboxes.create(); sandbox = NashornSandboxes.create();
monitorExecutorService = Executors.newWorkStealingPool(getMonitorThreadPoolSize()); monitorExecutorService = Executors.newWorkStealingPool(getMonitorThreadPoolSize());
@ -59,6 +108,9 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
if (monitorExecutorService != null) { if (monitorExecutorService != null) {
monitorExecutorService.shutdownNow(); monitorExecutorService.shutdownNow();
} }
if (timeoutExecutorService != null) {
timeoutExecutorService.shutdownNow();
}
} }
protected abstract boolean useJsSandbox(); protected abstract boolean useJsSandbox();
@ -69,34 +121,49 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
@Override @Override
protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String jsScript) { protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String jsScript) {
try { jsPushedMsgs.incrementAndGet();
if (useJsSandbox()) { ListenableFuture<UUID> result = jsExecutor.executeAsync(() -> {
sandbox.eval(jsScript); try {
} else { if (useJsSandbox()) {
engine.eval(jsScript); 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) { if (maxRequestsTimeout > 0) {
log.warn("Failed to compile JS script: {}", e.getMessage(), e); result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
return Futures.immediateFailedFuture(e);
} }
return Futures.immediateFuture(scriptId); Futures.addCallback(result, evalCallback);
return result;
} }
@Override @Override
protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) { protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) {
try { jsPushedMsgs.incrementAndGet();
Object result; ListenableFuture<Object> result = jsExecutor.executeAsync(() -> {
if (useJsSandbox()) { try {
result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args); if (useJsSandbox()) {
} else { return sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
result = ((Invocable) engine).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); if (maxRequestsTimeout > 0) {
return Futures.immediateFailedFuture(e); result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
} }
Futures.addCallback(result, invokeCallback);
return result;
} }
protected void doRelease(UUID scriptId, String functionName) throws ScriptException { protected void doRelease(UUID scriptId, String functionName) throws ScriptException {

46
application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java

@ -0,0 +1,46 @@
/**
* Copyright © 2016-2019 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.script;
import com.google.common.util.concurrent.FutureCallback;
import lombok.AllArgsConstructor;
import javax.annotation.Nullable;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@AllArgsConstructor
public class JsStatCallback<T> implements FutureCallback<T> {
private final AtomicInteger jsSuccessMsgs;
private final AtomicInteger jsTimeoutMsgs;
private final AtomicInteger jsFailedMsgs;
@Override
public void onSuccess(@Nullable T result) {
jsSuccessMsgs.incrementAndGet();
}
@Override
public void onFailure(Throwable t) {
if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
jsTimeoutMsgs.incrementAndGet();
} else {
jsFailedMsgs.incrementAndGet();
}
}
}

5
application/src/main/resources/thingsboard.yml

@ -464,6 +464,11 @@ js:
max_cpu_time: "${LOCAL_JS_SANDBOX_MAX_CPU_TIME:3000}" max_cpu_time: "${LOCAL_JS_SANDBOX_MAX_CPU_TIME:3000}"
# Maximum allowed JavaScript execution errors before JavaScript will be blacklisted # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted
max_errors: "${LOCAL_JS_SANDBOX_MAX_ERRORS:3}" max_errors: "${LOCAL_JS_SANDBOX_MAX_ERRORS:3}"
# JS Eval max request timeout. 0 - no timeout
max_requests_timeout: "${LOCAL_JS_MAX_REQUEST_TIMEOUT:0}"
stats:
enabled: "${TB_JS_LOCAL_STATS_ENABLED:false}"
print_interval_ms: "${TB_JS_LOCAL_STATS_PRINT_INTERVAL_MS:10000}"
# Remote JavaScript environment properties # Remote JavaScript environment properties
remote: remote:
# JS Eval request topic # JS Eval request topic

9
pom.xml

@ -115,6 +115,15 @@
<activeByDefault>true</activeByDefault> <activeByDefault>true</activeByDefault>
</activation> </activation>
</profile> </profile>
<!-- download sources under target/dependencies -->
<!-- mvn package -Pdownload-dependencies -Dclassifier=sources dependency:copy-dependencies -->
<profile>
<id>download-dependencies</id>
<properties>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</properties>
</profile>
</profiles> </profiles>
<build> <build>

Loading…
Cancel
Save