diff options
author | liamfallon <liam.fallon@est.tech> | 2020-03-23 17:49:50 +0000 |
---|---|---|
committer | liamfallon <liam.fallon@est.tech> | 2020-03-24 17:40:27 +0000 |
commit | 2f75e9d08d1e47e2b9b39ec21653bc3b4d65d00a (patch) | |
tree | be5374421d8b3f026175ab51d197289ea9dbbe0d /plugins/plugins-executor/plugins-executor-javascript/src/main | |
parent | f7746d758149bc68584c01dc0fe15130c7a866b1 (diff) |
Launch separate threads for Javascript task execution
When a policy is loaded, a separate thread is spawned for each
Javascript script executor. This allows us to precompile the Javascript
scripts and also to have a larger stack available for script execution.
Issue-ID: POLICY-2106
Change-Id: I97323aafb623ba537ac1889b3c9504b345b4f67e
Signed-off-by: liamfallon <liam.fallon@est.tech>
Diffstat (limited to 'plugins/plugins-executor/plugins-executor-javascript/src/main')
4 files changed, 195 insertions, 43 deletions
diff --git a/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptExecutor.java b/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptExecutor.java index c80f58fbe..2394b83d3 100644 --- a/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptExecutor.java +++ b/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptExecutor.java @@ -20,19 +20,28 @@ package org.onap.policy.apex.plugins.executor.javascript; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicReference; + import org.apache.commons.lang3.StringUtils; import org.mozilla.javascript.Context; import org.mozilla.javascript.Script; import org.mozilla.javascript.Scriptable; import org.onap.policy.apex.core.engine.executor.exception.StateMachineException; import org.onap.policy.apex.model.basicmodel.concepts.AxKey; +import org.slf4j.ext.XLogger; +import org.slf4j.ext.XLoggerFactory; /** * The Class JavascriptExecutor is the executor for task logic written in Javascript. * * @author Liam Fallon (liam.fallon@ericsson.com) */ -public class JavascriptExecutor { +public class JavascriptExecutor implements Runnable { + private static final XLogger LOGGER = XLoggerFactory.getXLogger(JavascriptExecutor.class); + public static final int DEFAULT_OPTIMIZATION_LEVEL = 9; // Recurring string constants @@ -41,16 +50,28 @@ public class JavascriptExecutor { // The key of the subject that wants to execute Javascript code final AxKey subjectKey; + private String javascriptCode; private Context javascriptContext; private Script script; + private final BlockingQueue<Object> executionQueue = new LinkedBlockingQueue<>(); + private final BlockingQueue<Boolean> resultQueue = new LinkedBlockingQueue<>(); + + private final Thread executorThread; + private CountDownLatch intializationLatch = new CountDownLatch(1); + private CountDownLatch shutdownLatch = new CountDownLatch(1); + private AtomicReference<StateMachineException> executorException = new AtomicReference<>(null); + /** - * Initializes the Javascripe executor. + * Initializes the Javascript executor. * * @param subjectKey the key of the subject that is requesting Javascript execution */ public JavascriptExecutor(final AxKey subjectKey) { this.subjectKey = subjectKey; + + executorThread = new Thread(this); + executorThread.setName(this.getClass().getSimpleName() + ":" + subjectKey.getId()); } /** @@ -60,10 +81,143 @@ public class JavascriptExecutor { * @throws StateMachineException thrown when instantiation of the executor fails */ public void init(final String javascriptCode) throws StateMachineException { + LOGGER.debug("JavascriptExecutor {} starting ... ", subjectKey.getId()); + + if (executorThread.isAlive()) { + throw new StateMachineException( + "initiation failed, executor " + subjectKey.getId() + " is already running"); + } + if (StringUtils.isEmpty(javascriptCode)) { throw new StateMachineException("no logic specified for " + subjectKey.getId()); } + this.javascriptCode = javascriptCode; + + try { + executorThread.start(); + } catch (Exception e) { + throw new StateMachineException("initiation failed, executor " + subjectKey.getId() + " failed to start", + e); + } + + try { + intializationLatch.await(); + } catch (InterruptedException e) { + LOGGER.debug("JavascriptExecutor {} interrupted on execution thread startup", subjectKey.getId(), e); + Thread.currentThread().interrupt(); + } + + if (executorException.get() != null) { + clearAndThrowExecutorException(); + } + + LOGGER.debug("JavascriptExecutor {} started ... ", subjectKey.getId()); + } + + /** + * Execute a Javascript script. + * + * @param executionContext the execution context to use for script execution + * @return true if execution was successful, false otherwise + * @throws StateMachineException on execution errors + */ + public boolean execute(final Object executionContext) throws StateMachineException { + if (!executorThread.isAlive()) { + throw new StateMachineException("execution failed, executor " + subjectKey.getId() + " is not running"); + } + + executionQueue.add(executionContext); + + boolean result = false; + + try { + result = resultQueue.take(); + } catch (final InterruptedException e) { + LOGGER.debug("JavascriptExecutor {} interrupted on execution result wait", subjectKey.getId(), e); + Thread.currentThread().interrupt(); + } + + if (executorException.get() != null) { + clearAndThrowExecutorException(); + } + + return result; + } + + /** + * Cleans up the executor after processing. + * + * @throws StateMachineException thrown when cleanup of the executor fails + */ + public void cleanUp() throws StateMachineException { + if (!executorThread.isAlive()) { + throw new StateMachineException("cleanup failed, executor " + subjectKey.getId() + " is not running"); + } + + executorThread.interrupt(); + + try { + shutdownLatch.await(); + } catch (InterruptedException e) { + LOGGER.debug("JavascriptExecutor {} interrupted on execution clkeanup wait", subjectKey.getId(), e); + Thread.currentThread().interrupt(); + } + + if (executorException.get() != null) { + clearAndThrowExecutorException(); + } + } + + @Override + public void run() { + LOGGER.debug("JavascriptExecutor {} initializing ... ", subjectKey.getId()); + + try { + initExecutor(); + } catch (StateMachineException sme) { + LOGGER.warn("JavascriptExecutor {} initialization failed", sme); + executorException.set(sme); + intializationLatch.countDown(); + return; + } + + intializationLatch.countDown(); + + LOGGER.debug("JavascriptExecutor {} executing ... ", subjectKey.getId()); + + // Take jobs from the execution queue of the worker and execute them + while (!executorThread.isInterrupted()) { + try { + Object contextObject = executionQueue.take(); + if (contextObject == null) { + break; + } + + resultQueue.add(executeScript(contextObject)); + } catch (final InterruptedException e) { + LOGGER.debug("execution was interruped for " + subjectKey.getId() + WITH_MESSAGE + e.getMessage(), e); + resultQueue.add(false); + Thread.currentThread().interrupt(); + } catch (StateMachineException sme) { + executorException.set(sme); + resultQueue.add(false); + } + } + + try { + Context.exit(); + } catch (final Exception e) { + executorException.set(new StateMachineException( + "executor close failed to close for " + subjectKey.getId() + WITH_MESSAGE + e.getMessage(), e)); + } + + shutdownLatch.countDown(); + + LOGGER.debug("JavascriptExecutor {} completed processing", subjectKey.getId()); + } + + private void initExecutor() throws StateMachineException { try { // Create a Javascript context for this thread javascriptContext = Context.enter(); @@ -76,18 +230,11 @@ public class JavascriptExecutor { } catch (Exception e) { Context.exit(); throw new StateMachineException( - "logic failed to compile for " + subjectKey.getId() + WITH_MESSAGE + e.getMessage(), e); + "logic failed to compile for " + subjectKey.getId() + WITH_MESSAGE + e.getMessage(), e); } } - /** - * Executes the the Javascript code. - * - * @param executionContext the execution context of the subject to be passed to the Javascript context - * @return true if the Javascript executed properly - * @throws StateMachineException thrown when Javascript execution fails - */ - public boolean execute(final Object executionContext) throws StateMachineException { + private boolean executeScript(final Object executionContext) throws StateMachineException { Object returnObject = null; try { @@ -99,28 +246,25 @@ public class JavascriptExecutor { returnObject = script.exec(javascriptContext, javascriptScope); } catch (final Exception e) { throw new StateMachineException( - "logic failed to run for " + subjectKey.getId() + WITH_MESSAGE + e.getMessage(), e); + "logic failed to run for " + subjectKey.getId() + WITH_MESSAGE + e.getMessage(), e); } if (!(returnObject instanceof Boolean)) { throw new StateMachineException( - "execute: logic for " + subjectKey.getId() + " returned a non-boolean value " + returnObject); + "execute: logic for " + subjectKey.getId() + " returned a non-boolean value " + returnObject); } return (boolean) returnObject; } - /** - * Cleans up the executor after processing. - * - * @throws StateMachineException thrown when cleanup of the executor fails - */ - public void cleanUp() throws StateMachineException { - try { - Context.exit(); - } catch (final Exception e) { - throw new StateMachineException("cleanUp: executor cleanup failed to close for " + subjectKey.getId() - + WITH_MESSAGE + e.getMessage(), e); + private void clearAndThrowExecutorException() throws StateMachineException { + StateMachineException exceptionToThrow = executorException.getAndSet(null); + if (exceptionToThrow != null) { + throw exceptionToThrow; } } + + protected Thread getExecutorThread() { + return executorThread; + } } diff --git a/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptStateFinalizerExecutor.java b/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptStateFinalizerExecutor.java index 27e649fd3..63e4948dd 100644 --- a/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptStateFinalizerExecutor.java +++ b/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptStateFinalizerExecutor.java @@ -53,11 +53,12 @@ public class JavascriptStateFinalizerExecutor extends StateFinalizerExecutor { super.prepare(); // Create the executor - javascriptExecutor = new JavascriptExecutor(getSubject().getKey()); + if (javascriptExecutor == null) { + javascriptExecutor = new JavascriptExecutor(getSubject().getKey()); + } // Initialize and cleanup the executor to check the Javascript code javascriptExecutor.init(getSubject().getLogic()); - javascriptExecutor.cleanUp(); } /** @@ -72,14 +73,12 @@ public class JavascriptStateFinalizerExecutor extends StateFinalizerExecutor { */ @Override public String execute(final long executionId, final Properties executionProperties, - final Map<String, Object> incomingFields) throws StateMachineException, ContextException { + final Map<String, Object> incomingFields) throws StateMachineException, ContextException { // Do execution pre work executePre(executionId, executionProperties, incomingFields); // Execute the Javascript executor - javascriptExecutor.init(getSubject().getLogic()); boolean result = javascriptExecutor.execute(getExecutionContext()); - javascriptExecutor.cleanUp(); // Execute the Javascript executePost(result); @@ -95,6 +94,8 @@ public class JavascriptStateFinalizerExecutor extends StateFinalizerExecutor { @Override public void cleanUp() throws StateMachineException { LOGGER.debug("cleanUp:" + getSubject().getKey().getId() + "," + getSubject().getLogicFlavour() + "," - + getSubject().getLogic()); + + getSubject().getLogic()); + + javascriptExecutor.cleanUp(); } } diff --git a/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptTaskExecutor.java b/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptTaskExecutor.java index bec5670c5..a9dba27f2 100644 --- a/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptTaskExecutor.java +++ b/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptTaskExecutor.java @@ -53,11 +53,12 @@ public class JavascriptTaskExecutor extends TaskExecutor { super.prepare(); // Create the executor - javascriptExecutor = new JavascriptExecutor(getSubject().getKey()); + if (javascriptExecutor == null) { + javascriptExecutor = new JavascriptExecutor(getSubject().getKey()); + } // Initialize and cleanup the executor to check the Javascript code javascriptExecutor.init(getSubject().getTaskLogic().getLogic()); - javascriptExecutor.cleanUp(); } /** @@ -72,15 +73,13 @@ public class JavascriptTaskExecutor extends TaskExecutor { */ @Override public Map<String, Object> execute(final long executionId, final Properties executionProperties, - final Map<String, Object> incomingFields) throws StateMachineException, ContextException { + final Map<String, Object> incomingFields) throws StateMachineException, ContextException { // Do execution pre work executePre(executionId, executionProperties, incomingFields); // Execute the Javascript executor - javascriptExecutor.init(getSubject().getTaskLogic().getLogic()); boolean result = javascriptExecutor.execute(getExecutionContext()); - javascriptExecutor.cleanUp(); // Execute the Javascript executePost(result); @@ -96,6 +95,12 @@ public class JavascriptTaskExecutor extends TaskExecutor { @Override public void cleanUp() throws StateMachineException { LOGGER.debug("cleanUp:" + getSubject().getKey().getId() + "," + getSubject().getTaskLogic().getLogicFlavour() - + "," + getSubject().getTaskLogic().getLogic()); + + "," + getSubject().getTaskLogic().getLogic()); + + if (javascriptExecutor != null) { + javascriptExecutor.cleanUp(); + } + + javascriptExecutor = null; } } diff --git a/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptTaskSelectExecutor.java b/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptTaskSelectExecutor.java index c32b70991..93384c129 100644 --- a/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptTaskSelectExecutor.java +++ b/plugins/plugins-executor/plugins-executor-javascript/src/main/java/org/onap/policy/apex/plugins/executor/javascript/JavascriptTaskSelectExecutor.java @@ -54,7 +54,9 @@ public class JavascriptTaskSelectExecutor extends TaskSelectExecutor { super.prepare(); // Create the executor - javascriptExecutor = new JavascriptExecutor(getSubject().getKey()); + if (javascriptExecutor == null) { + javascriptExecutor = new JavascriptExecutor(getSubject().getKey()); + } // Initialize and cleanup the executor to check the Javascript code javascriptExecutor.init(getSubject().getTaskSelectionLogic().getLogic()); @@ -72,14 +74,12 @@ public class JavascriptTaskSelectExecutor extends TaskSelectExecutor { */ @Override public AxArtifactKey execute(final long executionId, final Properties executionProperties, - final EnEvent incomingEvent) throws StateMachineException, ContextException { + final EnEvent incomingEvent) throws StateMachineException, ContextException { // Do execution pre work executePre(executionId, executionProperties, incomingEvent); // Execute the Javascript executor - javascriptExecutor.init(getSubject().getTaskSelectionLogic().getLogic()); boolean result = javascriptExecutor.execute(getExecutionContext()); - javascriptExecutor.cleanUp(); // Execute the Javascript executePost(result); @@ -94,8 +94,10 @@ public class JavascriptTaskSelectExecutor extends TaskSelectExecutor { */ @Override public void cleanUp() throws StateMachineException { - LOGGER.debug("cleanUp:" + getSubject().getKey().getId() + "," - + getSubject().getTaskSelectionLogic().getLogicFlavour() + "," - + getSubject().getTaskSelectionLogic().getLogic()); + LOGGER.debug( + "cleanUp:" + getSubject().getKey().getId() + "," + getSubject().getTaskSelectionLogic().getLogicFlavour() + + "," + getSubject().getTaskSelectionLogic().getLogic()); + + javascriptExecutor.cleanUp(); } } |