Skip to content

Commit

Permalink
Add API for cancelling executions.
Browse files Browse the repository at this point in the history
  • Loading branch information
chumer committed Jul 9, 2017
1 parent d135a9f commit 4622ed2
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.graalvm.polyglot.examples;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Value;

/**
* This example shows how harmful long running scripts can be cancelled. Cancelling the current
* execution will render the context inconsistent and therefore unusable.
*/
public class CancelExecution {

public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(1);
Engine engine = Engine.create();
Context context = engine.getLanguage("js").createContext();

// we submit a harmful infinite script to the executor
Future<Value> future = service.submit(() -> context.eval("while(true)"));

// wait some time to let the execution complete.
Thread.sleep(1000);

/*
* closes the context and cancels the running execution. This can be done on any parallel
* thread. Alternatively Engine.close(true) can be used to close all running contexts of an
* engine.
*/
context.close(true);

try {
future.get();
} catch (ExecutionException e) {
PolyglotException polyglotException = (PolyglotException) e.getCause();
/*
* After the execution got cancelled the executing thread stops by throwning a
* PolyglotException with the cancelled flag set.
*/
assert polyglotException.isCancelled();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public class ParallelEval {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService service = Executors.newFixedThreadPool(1);
Engine engine = Engine.create();
Future<Integer> future = service.submit(() -> engine.getLanguage("js").createContext().eval("21 + 21").asInt());
Future<Integer> future = service.submit(
() -> engine.getLanguage("js").createContext().eval("21 + 21").asInt());

// do work while JavaScript executes

Expand Down

This file was deleted.

41 changes: 30 additions & 11 deletions sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,10 @@ public final class Context implements AutoCloseable {

final AbstractContextImpl impl;
private final Language primaryLanguage;
private final boolean polyglot;

Context(AbstractContextImpl impl, Language language, boolean polyglot) {
Context(AbstractContextImpl impl, Language language) {
this.impl = impl;
this.primaryLanguage = language;
this.polyglot = polyglot;
}

/**
Expand Down Expand Up @@ -139,24 +137,45 @@ public Language getLanguage() {
* Closes this context and frees up potentially allocated native resources. Languages might not
* be able to free all native resources allocated by a context automatically, therefore it is
* recommended to close contexts after use. If the source {@link #getEngine() engine} is closed
* then this context is closed automatically.
* then this context is closed automatically. If a context got cancelled then the cancelled
* thread will throw a {@link PolyglotException} where the
* {@link PolyglotException#isCancelled() cancelled} flag is set. Please note that cancelling a
* single context can negatively affect the performance of other executing contexts of the same
* engine while the cancellation request is processed.
* <p>
* If internal errors occur during closing of the language then they are printed to the
* configured {@link Builder#setErr(OutputStream) error output stream}. If a context was closed
* then all its methods will throw an {@link IllegalStateException} when invoked. If an an
* attempt to close this context was successful then consecutive calls to close have no effect.
*
* @throws IllegalStateException If the context was created by a PolyglotContext.
* @see PolyglotContext#close() To close a polyglot context.
* @param cancelIfExecuting if <code>true</code> then currently executing contexts will be
* cancelled, else an {@link IllegalStateException} is thrown.
* @see Engine#close() To close an engine.
* @since 1.0
*/
public void close(boolean cancelIfRunning) {
impl.close(cancelIfRunning);
}

/**
* Closes this context and frees up potentially allocated native resources. Languages might not
* be able to free all native resources allocated by a context automatically, therefore it is
* recommended to close contexts after use. If the source {@link #getEngine() engine} is closed
* then this context is closed automatically. If the context is currently beeing executed on
* another thread then an {@link IllegalStateException} is thrown. To close currently executing
* contexts see {@link #close(boolean)}.
* <p>
* If internal errors occur during closing of the language then they are printed to the
* configured {@link Builder#setErr(OutputStream) error output stream}. If a context was closed
* then all its methods will throw an {@link IllegalStateException} when invoked. If an an
* attempt to close this context was successful then consecutive calls to close have no effect.
*
* @throws IllegalStateException if the context is currently executing on another thread.
* @see Engine#close() To close an engine.
* @since 1.0
*/
public void close() {
if (polyglot) {
throw new IllegalStateException("Context instances that originate from a PolyglotContext cannot be closed individually. Use PolyglotContext.close() instead.");
} else {
impl.close();
}
close(false);
}

public static final class Builder {
Expand Down
56 changes: 34 additions & 22 deletions sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Engine.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,25 @@
import org.graalvm.polyglot.impl.AbstractPolyglotImpl.AbstractValueImpl;

/**
* An engine represents an the execution environment for polyglot applications. Using an engine
* contexts can be created. Contexts can either be single language contexts that are
* {@link Language#createContext() created} using a {@link Language language} or polyglot contexts
* that are {@link Engine#createPolyglotContext() created} from the engine. Contexts allow to
* {@link Context#eval(Source) evaluate} guest language code directly and polylgot contexts require
* to evaluate code code should be run in.
* Represents an execution engine for polyglot applications. An engine consists of
* {@link #getLanguages() guest languages} and {@link #getInstruments() instruments}. An engine can
* be used to {@link #createPolyglotContext() create} {@link Context execution contexts}.
* <p>
* An instrument alters and/or monitors the execution of guest language source code. Common examples
* for instruments are debuggers, profilers or monitoring tools. Instruments are enabled via
* {@link Instrument#getOptions() options} passed to the {@link Builder#setOption(String, String)
* engine} when it is constructed.
*
* @see Context
* @see Value
* @see Language
* @see Instrument
*
* @since 1.0
*/
// TODO document that the current context class loader is captured when the engine is created.
public final class Engine implements AutoCloseable {

public static final String OPTION_COMPILER_TRACE_COMPILATION = "compiler.TraceCompilation";

final AbstractEngineImpl impl;

Engine(AbstractEngineImpl impl) {
Expand Down Expand Up @@ -117,18 +123,6 @@ public Language detectLanguage(Source source) {
return impl.detectLanguage(source.impl);
}

// TODO implement timeout features

@SuppressWarnings("unused")
void startTimeout(long timeout, TimeUnit unit) {
}

void resetTimeout() {
}

void clearTimeout() {
}

/**
* Returns all options available for the engine. The engine offers options with the following
* {@link OptionDescriptor#getGroup() groups}:
Expand Down Expand Up @@ -173,18 +167,36 @@ public String getVersion() {
return impl.getVersion();
}

/**
* Closes this engine and frees up allocated native resources. If there are still open context
* instances that were created using this engine and they are currently not beeing executed then
* all they will be closed automatically. If an an attempt to close the engine was successful
* then consecutive calls to close have no effect. If a context got cancelled then the cancelled
* thread will throw a {@link PolyglotException} where the
* {@link PolyglotException#isCancelled() cancelled} flag is set.
*
* @param cancelIfExecuting if <code>true</code> then currently executing contexts will be
* cancelled, else an {@link IllegalStateException} is thrown.
* @since 1.0
*/
public void close(boolean cancelIfExecuting) {
impl.ensureClosed(false);
}

/**
* Closes this engine and frees up allocated native resources. If there are still open context
* instances that were created using this engine and they are currently not beeing executed then
* all they will be closed automatically. If an an attempt to close the engine was successful
* then consecutive calls to close have no effect.
*
* @throws IllegalStateException if there currently executing open context instances.
* @see #close(boolean)
* @see Engine#close()
* @since 1.0
*/
@Override
public void close() {
impl.ensureClosed(false);
close(false);
}

public static Engine create() {
Expand Down Expand Up @@ -328,7 +340,7 @@ public Engine newEngine(AbstractEngineImpl impl) {

@Override
public Context newContext(AbstractContextImpl impl, Language languageImpl) {
return new Context(impl, languageImpl, false);
return new Context(impl, languageImpl);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,15 @@ public boolean isInternalError() {
}

/**
* Returns <code>true</code> if this exception was caused by a timeout event in the engine.
* Timeouts can either be caused by guest languages or if a
* {@link Engine#setTimeout(long, java.util.concurrent.TimeUnit) timout} was configured for the
* engine.
* Returns <code>true</code> if the execution was cancelled. The execution can be cancelled by
* {@link Context#close(boolean) closing} a context or if an instrument like a debugger decides
* to cancel the current execution. The context that caused a cancel event becomes unusable ie.
* closed.
*
* @since 1.0
*/
public boolean isTimeout() {
return impl.isTimeout();
public boolean isCancelled() {
return impl.isCancelled();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ protected AbstractContextImpl(AbstractPolyglotImpl impl) {

public abstract Engine getEngineImpl();

public abstract void close();
public abstract void close(boolean interuptExecution);

}

Expand Down Expand Up @@ -251,7 +251,7 @@ protected AbstractExceptionImpl(AbstractPolyglotImpl engineImpl) {

public abstract boolean isInternalError();

public abstract boolean isTimeout();
public abstract boolean isCancelled();

public abstract boolean isExit();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ public Iterator<StackFrame> iterator() {
}

@Override
public boolean isTimeout() {
public boolean isCancelled() {
return timeout;
}

Expand Down

0 comments on commit 4622ed2

Please sign in to comment.