diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/StdLibModule.java b/pkl-core/src/main/java/org/pkl/core/runtime/StdLibModule.java index 41ac27e25..df8bdec3a 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/StdLibModule.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/StdLibModule.java @@ -15,6 +15,8 @@ */ package org.pkl.core.runtime; +import static org.pkl.core.PClassInfo.pklBaseUri; + import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import java.net.URI; import java.util.List; @@ -27,23 +29,33 @@ import org.pkl.core.http.HttpClient; import org.pkl.core.module.ModuleKeyFactories; import org.pkl.core.module.ModuleKeys; import org.pkl.core.module.ResolvedModuleKey; +import org.pkl.core.resource.ResourceReader; +import org.pkl.core.resource.ResourceReaders; public abstract class StdLibModule { @TruffleBoundary protected static void loadModule(URI uri, VmTyped instance) { - // evaluate eagerly to increase thread safety - // (stdlib module objects are statically shared singletons when running on JVM) - // and ensure compile-time evaluation in AOT mode + doLoad(uri, instance); + } + + private static void doLoad(URI uri, VmTyped instance) { VmUtils.createContext( () -> { var vmContext = VmContext.get(null); + var isPklBaseModule = uri.equals(pklBaseUri); + var resourceReaders = + isPklBaseModule + // needed when initializing `pkl:base` because of + // `read("prop:pkl.outputFormat")` + ? List.of(ResourceReaders.externalProperty()) + : List.of(); vmContext.initialize( new VmContext.Holder( StackFrameTransformers.defaultTransformer, SecurityManagers.defaultManager, HttpClient.dummyClient(), new ModuleResolver(List.of(ModuleKeyFactories.standardLibrary)), - new ResourceManager(SecurityManagers.defaultManager, List.of()), + new ResourceManager(SecurityManagers.defaultManager, resourceReaders), Loggers.noop(), Map.of(), Map.of(), @@ -67,6 +79,15 @@ public abstract class StdLibModule { // (stdlib module objects are statically shared singletons when running on JVM) // and ensure compile-time evaluation in AOT mode instance.force(false, true); + + // seed base module's `output` members; `output` contains truffle nodes that + // need to be initialized statically (e.g. LetExprNode, TypeTestNode). + // additionally, its `cachedMembers` is not thread-safe and needs to be initialized + // statically. + if (isPklBaseModule) { + var output = VmUtils.readModuleOutput(instance); + output.force(false, true); + } }) .close(); } diff --git a/pkl-core/src/test/kotlin/org/pkl/core/EvaluatorTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/EvaluatorTest.kt index 490253e88..6679603d8 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/EvaluatorTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/EvaluatorTest.kt @@ -772,6 +772,23 @@ class EvaluatorTest { .doesNotThrowAnyException() } + @Test + fun `concurrent evals`() { + val exceptions = mutableListOf() + val threads = + (0..10).map { + Thread { Evaluator.preconfigured().use { ev -> ev.evaluateOutputText(text("foo = 1")) } } + .also { t -> + t.uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { _, e -> + synchronized(exceptions) { exceptions.add(e) } + } + t.start() + } + } + for (t in threads) t.join() + exceptions.firstOrNull()?.let { throw it } + } + private fun checkModule(module: PModule) { assertThat(module.properties.size).isEqualTo(2) assertThat(module.getProperty("name")).isEqualTo("pigeon")