Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schedule initialization of JS context in a separate thread #5680

Merged
merged 2 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
RuntimeServerInfo.JOB_PARALLELISM_OPTION,
Runtime.getRuntime.availableProcessors().toString
)
.option(RuntimeOptions.PREINITIALIZE, "js")
.out(stdOut)
.err(stdErr)
.in(stdIn)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ public class RuntimeOptions {
OptionDescriptor.newBuilder(EDITION_OVERRIDE_KEY, EDITION_OVERRIDE).build();

public static final String DISABLE_IR_CACHES = optionName("disableIrCaches");
public static final String PREINITIALIZE = optionName("preinitialize");
public static final OptionKey<String> PREINITIALIZE_KEY = new OptionKey<>("");
private static final OptionDescriptor PREINITIALIZE_DESCRIPTOR =
OptionDescriptor.newBuilder(PREINITIALIZE_KEY, PREINITIALIZE).build();
public static final OptionKey<Boolean> DISABLE_IR_CACHES_KEY = new OptionKey<>(false);
private static final OptionDescriptor DISABLE_IR_CACHES_DESCRIPTOR =
OptionDescriptor.newBuilder(DISABLE_IR_CACHES_KEY, DISABLE_IR_CACHES).build();
Expand Down Expand Up @@ -115,6 +119,7 @@ public class RuntimeOptions {
EDITION_OVERRIDE_DESCRIPTOR,
INTERPRETER_SEQUENTIAL_COMMAND_EXECUTION_DESCRIPTOR,
DISABLE_IR_CACHES_DESCRIPTOR,
PREINITIALIZE_DESCRIPTOR,
WAIT_FOR_PENDING_SERIALIZATION_JOBS_DESCRIPTOR,
USE_GLOBAL_IR_CACHE_LOCATION_DESCRIPTOR,
ENABLE_EXECUTION_TIMER_DESCRIPTOR));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package org.enso.interpreter.epb;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleContext;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.nodes.Node;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
import java.util.logging.Level;
import org.enso.interpreter.epb.runtime.GuardedTruffleContext;

/**
Expand Down Expand Up @@ -32,20 +36,65 @@ public EpbContext(TruffleLanguage.Env env) {
}

/**
* Initializes the context. No-op in the inner context. Spawns the inner context if called from
* the outer context.
* Initializes the context.No-op in the inner context. Spawns the inner context if called from the
* outer context. Shielded against double initialization.
*
* @param preInitializeLanguages comma separated list of languages to immediately initialize
*/
public void initialize() {
public void initialize(String preInitializeLanguages) {
if (!isInner) {
innerContext =
new GuardedTruffleContext(
env.newInnerContextBuilder()
.initializeCreatorContext(true)
.inheritAllAccess(true)
.config(INNER_OPTION, "yes")
.build(),
true);
if (innerContext == null) {
innerContext =
new GuardedTruffleContext(
env.newInnerContextBuilder()
.initializeCreatorContext(true)
.inheritAllAccess(true)
.config(INNER_OPTION, "yes")
.build(),
true);
}
initializeLanguages(env, innerContext, preInitializeLanguages);
}
}

private static void initializeLanguages(
TruffleLanguage.Env environment, GuardedTruffleContext innerContext, String langs) {
if (langs == null || langs.isEmpty()) {
return;
}
var log = environment.getLogger(EpbContext.class);
log.log(Level.FINE, "Initializing languages {0}", langs);
var cdl = new CountDownLatch(1);
var run =
(Consumer<TruffleContext>)
(context) -> {
var lock = innerContext.enter(null);
log.log(Level.FINEST, "Entering initialization thread");
cdl.countDown();
try {
for (var l : langs.split(",")) {
log.log(Level.FINEST, "Initializing language {0}", l);
long then = System.currentTimeMillis();
var res = context.initializeInternal(null, l);
long took = System.currentTimeMillis() - then;
log.log(
Level.FINE,
"Done initializing language {0} with {1} in {2} ms",
new Object[] {l, res, took});
}
} finally {
innerContext.leave(null, lock);
}
};
var init = innerContext.createThread(environment, run);
log.log(Level.FINEST, "Starting initialization thread");
init.start();
try {
cdl.await();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
log.log(Level.FINEST, "Initializing on background");
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package org.enso.interpreter.epb;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import java.util.function.Consumer;
import org.enso.interpreter.epb.node.ContextRewrapNode;
import org.enso.interpreter.epb.node.ForeignEvalNode;

Expand Down Expand Up @@ -34,19 +34,23 @@
characterMimeTypes = {EpbLanguage.MIME},
internal = true,
defaultMimeType = EpbLanguage.MIME,
contextPolicy = TruffleLanguage.ContextPolicy.SHARED)
contextPolicy = TruffleLanguage.ContextPolicy.SHARED,
services = Consumer.class)
public class EpbLanguage extends TruffleLanguage<EpbContext> {
public static final String ID = "epb";
public static final String MIME = "application/epb";

@Override
protected EpbContext createContext(Env env) {
return new EpbContext(env);
var ctx = new EpbContext(env);
Consumer<String> init = ctx::initialize;
env.registerService(init);
return ctx;
}

@Override
protected void initializeContext(EpbContext context) {
context.initialize();
context.initialize(null);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleContext;
import com.oracle.truffle.api.TruffleLanguage;

import com.oracle.truffle.api.nodes.Node;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

/** Wraps a {@link TruffleContext} by providing an optional GIL functionality. */
public class GuardedTruffleContext {
Expand All @@ -27,6 +29,16 @@ public GuardedTruffleContext(TruffleContext context, boolean isSingleThreaded) {
}
}

/**
* Spawns new thread with associated {@code context}
*
* @param env environment to spawn the thread in
* @param run code to execute in given TruffleContext
*/
public Thread createThread(TruffleLanguage.Env env, Consumer<TruffleContext> run) {
return env.createThread(() -> run.accept(context), context);
}

/**
* Enters this context. If this wrapper is single threaded and the context is in use, this method
* will block indefinitely until the context becomes available.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.enso.interpreter.test.instrument;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;
import static org.junit.Assert.fail;

final class MockLogHandler extends Handler {
private final List<LogRecord> logs = new ArrayList<>();

MockLogHandler() {
setLevel(Level.ALL);
}

@Override
public synchronized void publish(LogRecord lr) {
logs.add(lr);
}

@Override
public void flush() {}

@Override
public void close() throws SecurityException {}

synchronized Object[] assertMessage(String loggerName, String msgPrefix) {
var f = new SimpleFormatter();
var sb = new StringBuilder();
sb.append("Cannot find ").append(msgPrefix).append(" in ").append(loggerName);
for (var r : logs) {
if (loggerName.equals(r.getLoggerName()) && r.getMessage().startsWith(msgPrefix)) {
return r.getParameters();
}
sb.append("\n").append(f.format(r));
}
fail(sb.toString());
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,41 +1,56 @@
package org.enso.interpreter.test.instrument;

import java.io.OutputStream;
import java.nio.file.Paths;
import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.BeforeClass;
import org.junit.Test;

public class VerifyJavaScriptIsAvailableTest {
private static Context ctx;
private static MockLogHandler handler;

@BeforeClass
public static void initEnsoContext() {
handler = new MockLogHandler();
ctx =
Context.newBuilder()
.allowExperimentalOptions(true)
.allowIO(true)
.option(RuntimeOptions.PREINITIALIZE, "js")
.option(
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
Paths.get("../../distribution/component").toFile().getAbsolutePath())
.logHandler(OutputStream.nullOutputStream())
.option("log.level", "FINE")
.logHandler(handler)
.allowAllAccess(true)
.build();
assertNotNull("Enso language is supported", ctx.getEngine().getLanguages().get("enso"));
var fourtyTwo =
ctx.eval("enso", "mul x y = x * y").invokeMember("eval_expression", "mul").execute(6, 7);
assertEquals(42, fourtyTwo.asInt());
}

@AfterClass
public static void closeEnsoContext() throws Exception {
ctx.close();

var args =
handler.assertMessage(
"epb.org.enso.interpreter.epb.EpbContext", "Done initializing language");
assertEquals("js", args[0]);
assertEquals(Boolean.TRUE, args[1]);
}

@Test
public void javaScriptIsPresent() {
var js = ctx.getEngine().getLanguages().get("js");
assertNotNull("JavaScript is available", js);
var fourtyTwo = ctx.eval("js", "6 * 7");
assertEquals(42, fourtyTwo.asInt());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.enso.compiler.Compiler;
import org.enso.compiler.context.InlineContext;
Expand Down Expand Up @@ -156,8 +157,16 @@ protected EnsoContext createContext(Env env) {
* @param context the language context
*/
@Override
@SuppressWarnings("unchecked")
protected void initializeContext(EnsoContext context) {
context.initialize();
var env = context.getEnvironment();
var preinit = env.getOptions().get(RuntimeOptions.PREINITIALIZE_KEY);
if (preinit != null && preinit.length() > 0) {
var epb = env.getInternalLanguages().get(EpbLanguage.ID);
var run = env.lookup(epb, Consumer.class);
run.accept(preinit);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
import com.oracle.truffle.api.instrumentation.ExecutionEventNode;
import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory;
import com.oracle.truffle.api.nodes.RootNode;
import java.util.Arrays;
Expand Down