From b7f3b4c2b2bc5081f6d9b151116f481cf44591a5 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 25 Sep 2024 15:06:25 +0200 Subject: [PATCH] Allow control of throwDeniedThreadAccess via TruffleContext.threadAccessDeniedHandler --- .../polyglot/impl/AbstractPolyglotImpl.java | 3 +- truffle/CHANGELOG.md | 1 + .../InstrumentationHandler.java | 4 +- .../META-INF/native-image/reflect-config.json | 4 + .../truffle/api/test/TruffleContextTest.java | 51 ++- .../test/polyglot/MultiThreadedLanguage.java | 6 +- .../api/test/wrapper/HostEngineDispatch.java | 3 +- .../com.oracle.truffle.api/snapshot.sigtest | 3 +- .../oracle/truffle/api/TruffleContext.java | 25 +- .../com/oracle/truffle/api/impl/Accessor.java | 2 +- .../truffle/polyglot/EngineAccessor.java | 16 +- .../polyglot/PolyglotContextConfig.java | 5 +- .../truffle/polyglot/PolyglotContextImpl.java | 290 +++++++++--------- .../polyglot/PolyglotEngineDispatch.java | 2 +- .../truffle/polyglot/PolyglotEngineImpl.java | 6 +- .../polyglot/PolyglotLanguageContext.java | 124 ++++---- .../PolyglotThreadAccessException.java | 79 +++++ 17 files changed, 405 insertions(+), 219 deletions(-) create mode 100644 truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotThreadAccessException.java diff --git a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/impl/AbstractPolyglotImpl.java b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/impl/AbstractPolyglotImpl.java index 82b38828285c..5778f409cd0b 100644 --- a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/impl/AbstractPolyglotImpl.java +++ b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/impl/AbstractPolyglotImpl.java @@ -803,7 +803,8 @@ public abstract Object createContext(Object receiver, SandboxPolicy sandboxPolic Object hostAccess, Object polyglotAccess, boolean allowNativeAccess, - boolean allowCreateThread, boolean allowHostClassLoading, boolean allowInnerContextOptions, boolean allowExperimentalOptions, + boolean allowCreateThread, + boolean allowHostClassLoading, boolean allowInnerContextOptions, boolean allowExperimentalOptions, Predicate classFilter, Map options, Map arguments, String[] onlyLanguages, Object ioAccess, Object logHandler, boolean allowCreateProcess, ProcessHandler processHandler, diff --git a/truffle/CHANGELOG.md b/truffle/CHANGELOG.md index 174dcaf05554..ba0279f15563 100644 --- a/truffle/CHANGELOG.md +++ b/truffle/CHANGELOG.md @@ -7,6 +7,7 @@ This changelog summarizes major changes between Truffle versions relevant to lan * GR-57164 Added support for reading unaligned ints, shorts and long to `ByteArraySupport`. * GR-57164 `RootNode.translateStackTraceElement()` is now always consulted for polyglot and debugger stack traces. Stack traces now use the source section, the executable name, the name of the declared meta-object to build `StackTraceElement` instances. * GR-57322 Added `TruffleLanguage.Env.getHostLanguage()` returning the host language info. This allows languages to lookup the top scope of the host language using `Env.getScopeInternal(LanguageInfo)`. +* PR-8266 Allow control of `throwDeniedThreadAccess` via `TruffleContext.threadAccessDeniedHandler` ## Version 24.1.0 diff --git a/truffle/src/com.oracle.truffle.api.instrumentation/src/com/oracle/truffle/api/instrumentation/InstrumentationHandler.java b/truffle/src/com.oracle.truffle.api.instrumentation/src/com/oracle/truffle/api/instrumentation/InstrumentationHandler.java index 174ba8dfb166..6170dca0228e 100644 --- a/truffle/src/com.oracle.truffle.api.instrumentation/src/com/oracle/truffle/api/instrumentation/InstrumentationHandler.java +++ b/truffle/src/com.oracle.truffle.api.instrumentation/src/com/oracle/truffle/api/instrumentation/InstrumentationHandler.java @@ -2268,10 +2268,10 @@ void create(String[] expectedServices) { } private boolean checkServices(String[] expectedServices) { - LOOP: for (String name : expectedServices) { + loop: for (String name : expectedServices) { for (Object obj : services) { if (findType(name, obj.getClass())) { - continue LOOP; + continue loop; } } failInstrumentInitialization(env, String.format("%s declares service %s but doesn't register it", instrumentClassName, name), null); diff --git a/truffle/src/com.oracle.truffle.api.test/src/META-INF/native-image/reflect-config.json b/truffle/src/com.oracle.truffle.api.test/src/META-INF/native-image/reflect-config.json index d0fd869ca63c..513287d1b37f 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/META-INF/native-image/reflect-config.json +++ b/truffle/src/com.oracle.truffle.api.test/src/META-INF/native-image/reflect-config.json @@ -261,5 +261,9 @@ { "name": "java.nio.HeapByteBuffer", "allPublicMethods": true + }, + { + "name": "org.hamcrest.core.StringContains", + "allPublicMethods": true } ] diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/TruffleContextTest.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/TruffleContextTest.java index 0d629a5e0fd0..3a889f2a02b6 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/TruffleContextTest.java +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/TruffleContextTest.java @@ -40,12 +40,10 @@ */ package com.oracle.truffle.api.test; -import static com.oracle.truffle.api.test.common.AbstractExecutableTestLanguage.evalTestLanguage; import static com.oracle.truffle.api.test.common.AbstractExecutableTestLanguage.execute; import static com.oracle.truffle.api.test.polyglot.AbstractPolyglotTest.assertFails; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import java.util.function.Consumer; @@ -67,9 +65,15 @@ import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.test.common.AbstractExecutableTestLanguage; +import static com.oracle.truffle.api.test.common.AbstractExecutableTestLanguage.evalTestLanguage; import com.oracle.truffle.api.test.common.NullObject; import com.oracle.truffle.api.test.common.TestUtils; import com.oracle.truffle.api.test.polyglot.AbstractPolyglotTest; +import com.oracle.truffle.api.test.polyglot.MultiThreadedLanguage; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import org.hamcrest.core.AllOf; +import static org.junit.Assert.fail; /* * There is other TruffleContextTest in com.oracle.truffle.api.instrumentation.test package. @@ -239,6 +243,49 @@ public void testCancelledInnerContext() { } } + @Registration + static class InnerContextTestLanguage5 extends AbstractExecutableTestLanguage { + @TruffleBoundary + @Override + protected Object execute(RootNode node, Env env, Object[] contextArguments, Object[] frameArguments) throws Exception { + try (TruffleContext context = env.newInnerContextBuilder(MultiThreadedLanguage.ID).inheritAllAccess(true).threadAccessDeniedHandler((msg) -> { + assertThat(msg, AllOf.allOf( + containsString("Single threaded access requested"), + containsString("but is not allowed for language(s) MultiThreadedLanguage"))); + throw new SecurityException("Threads are not secure!"); + }).build()) { + MultiThreadedLanguage.isThreadAccessAllowed = (req) -> { + return false; + }; + try { + context.initializePublic(node, MultiThreadedLanguage.ID); + fail(); + } catch (SecurityException e) { + assertEquals("Threads are not secure!", e.getMessage()); + } + try { + context.evalPublic(node, Source.newBuilder(MultiThreadedLanguage.ID, "", "cannot.eval.src").build()); + fail(); + } catch (SecurityException e) { + assertEquals("Threads are not secure!", e.getMessage()); + } + // allow again so we can close + MultiThreadedLanguage.isThreadAccessAllowed = (req) -> { + return true; + }; + } + return NullObject.SINGLETON; + } + } + + /** Verifies {@code threadAccessDeniedHandler}. */ + @Test + public void testNoThreadAllowedWithSecurityException() { + try (Context context = Context.newBuilder().allowPolyglotAccess(PolyglotAccess.ALL).build()) { + evalTestLanguage(context, InnerContextTestLanguage5.class, ""); + } + } + @Registration static class InnerContextExitTestLanguage1 extends AbstractExecutableTestLanguage { diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/MultiThreadedLanguage.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/MultiThreadedLanguage.java index 4447902d3992..a190c06bfec1 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/MultiThreadedLanguage.java +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/MultiThreadedLanguage.java @@ -52,11 +52,11 @@ @TruffleLanguage.Registration(id = MultiThreadedLanguage.ID, name = MultiThreadedLanguage.ID) public class MultiThreadedLanguage extends TruffleLanguage { - static final String ID = "MultiThreadedLanguage"; + public static final String ID = "MultiThreadedLanguage"; static final ThreadLocal> runinside = new ThreadLocal<>(); static volatile Function initializeThread; - static volatile Function isThreadAccessAllowed; + public static volatile Function isThreadAccessAllowed; static volatile Function initializeMultiThreading; static volatile Function finalizeContext; static volatile Function disposeThread; @@ -73,7 +73,7 @@ static class LanguageContext { } - static class ThreadRequest { + public static final class ThreadRequest { final LanguageContext context; final Thread thread; diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/wrapper/HostEngineDispatch.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/wrapper/HostEngineDispatch.java index 0d1ad6929e60..81743a933372 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/wrapper/HostEngineDispatch.java +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/wrapper/HostEngineDispatch.java @@ -74,7 +74,8 @@ protected HostEngineDispatch(HostPolyglotDispatch polyglot) { @Override public Object createContext(Object receiver, SandboxPolicy sandboxPolicy, OutputStream out, OutputStream err, InputStream in, boolean allowHostAccess, Object hostAccess, Object polyglotAccess, - boolean allowNativeAccess, boolean allowCreateThread, boolean allowHostClassLoading, boolean allowInnerContextOptions, boolean allowExperimentalOptions, + boolean allowNativeAccess, boolean allowCreateThread, boolean allowHostClassLoading, boolean allowInnerContextOptions, + boolean allowExperimentalOptions, Predicate classFilter, Map options, Map arguments, String[] onlyLanguages, Object ioAccess, Object logHandler, boolean allowCreateProcess, ProcessHandler processHandler, Object environmentAccess, Map environment, ZoneId zone, Object limitsImpl, String currentWorkingDirectory, String tmpDir, ClassLoader hostClassLoader, boolean allowValueSharing, boolean useSystemExit) { diff --git a/truffle/src/com.oracle.truffle.api/snapshot.sigtest b/truffle/src/com.oracle.truffle.api/snapshot.sigtest index edd6237c9794..662eec86790a 100644 --- a/truffle/src/com.oracle.truffle.api/snapshot.sigtest +++ b/truffle/src/com.oracle.truffle.api/snapshot.sigtest @@ -325,9 +325,10 @@ meth public com.oracle.truffle.api.TruffleContext$Builder onExited(java.util.fun meth public com.oracle.truffle.api.TruffleContext$Builder option(java.lang.String,java.lang.String) meth public com.oracle.truffle.api.TruffleContext$Builder options(java.util.Map) meth public com.oracle.truffle.api.TruffleContext$Builder out(java.io.OutputStream) +meth public com.oracle.truffle.api.TruffleContext$Builder threadAccessDeniedHandler(java.util.function.Consumer) meth public com.oracle.truffle.api.TruffleContext$Builder timeZone(java.time.ZoneId) supr java.lang.Object -hfds allowCreateProcess,allowCreateThread,allowEnvironmentAccess,allowHostClassLoading,allowHostLookup,allowIO,allowInnerContextOptions,allowNativeAccess,allowPolyglotAccess,arguments,config,environment,err,in,inheritAccess,initializeCreatorContext,onCancelled,onClosed,onExited,options,out,permittedLanguages,sharingEnabled,sourceEnvironment,timeZone +hfds allowCreateProcess,allowCreateThread,allowEnvironmentAccess,allowHostClassLoading,allowHostLookup,allowIO,allowInnerContextOptions,allowNativeAccess,allowPolyglotAccess,arguments,config,environment,err,in,inheritAccess,initializeCreatorContext,onCancelled,onClosed,onExited,options,out,permittedLanguages,sharingEnabled,sourceEnvironment,threadAccessDeniedHandler,timeZone CLSS public final com.oracle.truffle.api.TruffleFile fld public final static com.oracle.truffle.api.TruffleFile$AttributeDescriptor IS_DIRECTORY diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleContext.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleContext.java index 6931f7c64142..23cb57151514 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleContext.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/TruffleContext.java @@ -83,7 +83,7 @@ * then it is automatically closed together with the parent context. *

* Example usage: - * + * * {@snippet file="com/oracle/truffle/api/TruffleContext.java" * region="TruffleContextSnippets.MyNode#executeInContext"} * @@ -533,7 +533,7 @@ public T leaveAndEnter(Node node, Supplier runWhileOutsideContext) { * function is run at most once. * @return return value of the interruptible, or null if the interruptibe throws an * {@link InterruptedException} without the context being cancelled or exited. - * + * * @since 23.1 */ @SuppressWarnings("unused") @@ -757,6 +757,7 @@ public final class Builder { private Boolean allowPolyglotAccess; private Boolean allowEnvironmentAccess; private ZoneId timeZone; + private Consumer threadAccessDeniedHandler; Builder(Env env) { this.sourceEnvironment = env; @@ -1036,6 +1037,23 @@ public Builder allowPolyglotAccess(boolean b) { return this; } + /** + * Installs handler to control what happens on multiple thread access. When multiple threads + * are accessing a context which isn't ready for multithreaded access an exception is + * yielded by default. By installing this {@code handler} one can control what shall happen. + * Either to throw exception (with the provided reason) or to resolve the multithreaded + * situation somehow and return to retry the thread access again. + * + * @param handler callback (that gets a reason as an input) that either throws an exception + * or returns to signal a request for retry + * @return this builder + * @since 24.2 + */ + public Builder threadAccessDeniedHandler(Consumer handler) { + this.threadAccessDeniedHandler = handler; + return this; + } + /** * Allows or denies access to the parent context's environment in this context. Set to * true to inherit variables from the outer context or false to @@ -1208,7 +1226,8 @@ public TruffleContext build() { return LanguageAccessor.engineAccess().createInternalContext( sourceEnvironment.getPolyglotLanguageContext(), this.out, this.err, this.in, this.timeZone, this.permittedLanguages, this.config, this.options, this.arguments, this.sharingEnabled, this.initializeCreatorContext, this.onCancelled, this.onExited, - this.onClosed, this.inheritAccess, this.allowCreateThread, this.allowNativeAccess, this.allowIO, this.allowHostLookup, this.allowHostClassLoading, + this.onClosed, this.inheritAccess, this.allowCreateThread, this.threadAccessDeniedHandler, this.allowNativeAccess, this.allowIO, this.allowHostLookup, + this.allowHostClassLoading, this.allowCreateProcess, this.allowPolyglotAccess, this.allowEnvironmentAccess, this.environment, this.allowInnerContextOptions); } catch (Throwable t) { throw Env.engineToLanguageException(t); diff --git a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java index edd7d0c79781..b1a175da6d26 100644 --- a/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java +++ b/truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/impl/Accessor.java @@ -432,7 +432,7 @@ public final void detachOutputConsumer(DispatchOutputStream dos, OutputStream ou public abstract TruffleContext createInternalContext(Object sourcePolyglotLanguageContext, OutputStream out, OutputStream err, InputStream in, ZoneId timeZone, String[] permittedLanguages, Map config, Map options, Map arguments, Boolean sharingEnabled, boolean initializeCreatorContext, Runnable onCancelled, Consumer onExited, - Runnable onClosed, boolean inheritAccess, Boolean allowCreateThreads, Boolean allowNativeAccess, Boolean allowIO, + Runnable onClosed, boolean inheritAccess, Boolean allowCreateThreads, Consumer threadAccessDeniedHandler, Boolean allowNativeAccess, Boolean allowIO, Boolean allowHostLookup, Boolean allowHostClassLoading, Boolean allowCreateProcess, Boolean allowPolyglotAccess, Boolean allowEnvironmentAccess, Map environment, Boolean allowInnerContextOptions); diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java index 31a82d470c5e..843131aa2791 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/EngineAccessor.java @@ -987,6 +987,7 @@ public TruffleContext createInternalContext(Object sourcePolyglotLanguageContext String[] onlyLanguagesArray, Map config, Map options, Map arguments, Boolean sharingEnabled, boolean initializeCreatorContext, Runnable onCancelledRunnable, Consumer onExitedRunnable, Runnable onClosedRunnable, boolean inheritAccess, Boolean allowCreateThreads, + Consumer threadAccessDeniedHandler, Boolean allowNativeAccess, Boolean allowIO, Boolean allowHostLookup, Boolean allowHostClassLoading, Boolean allowCreateProcess, Boolean allowPolyglotAccess, Boolean allowEnvironmentAccess, Map customEnvironment, Boolean allowInnerContextOptions) { @@ -1110,6 +1111,8 @@ public TruffleContext createInternalContext(Object sourcePolyglotLanguageContext ZoneId useTimeZone = timeZone == null ? creatorConfig.timeZone : timeZone; + Consumer useDeniedThreadAccess = threadAccessDeniedHandler != null ? threadAccessDeniedHandler : creatorConfig.threadAccessDeniedHandler; + Map useArguments; if (arguments == null) { // change: application arguments are not inherited by default @@ -1119,7 +1122,7 @@ public TruffleContext createInternalContext(Object sourcePolyglotLanguageContext } PolyglotContextConfig innerConfig = new PolyglotContextConfig(engine, creatorConfig.sandboxPolicy, sharingEnabled, useOut, useErr, useIn, - useAllowHostLookup, usePolyglotAccess, useAllowNativeAccess, useAllowCreateThread, useAllowHostClassLoading, + useAllowHostLookup, usePolyglotAccess, useAllowNativeAccess, useAllowCreateThread, useDeniedThreadAccess, useAllowHostClassLoading, useAllowInnerContextOptions, creatorConfig.allowExperimentalOptions, useClassFilter, useArguments, allowedLanguages, useOptions, fileSystemConfig, creatorConfig.logHandler, useAllowCreateProcess, useProcessHandler, useEnvironmentAccess, useCustomEnvironment, @@ -1184,9 +1187,14 @@ public Thread createThread(Object polyglotLanguageContext, Runnable runnable, Ob newThread = new Thread(group, task, name, stackSize); } newThread.setUncaughtExceptionHandler(threadContext.getPolyglotExceptionHandler()); - - threadContext.context.checkMultiThreadedAccess(newThread); - return newThread; + for (;;) { + try { + threadContext.context.checkMultiThreadedAccess(newThread); + return newThread; + } catch (PolyglotThreadAccessException ex) { + ex.rethrow(threadContext.context); + } + } } @Override diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextConfig.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextConfig.java index 51ac382ecaeb..637c7ab22e4e 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextConfig.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextConfig.java @@ -109,6 +109,7 @@ final class PolyglotContextConfig { final Runnable onCancelled; final Consumer onExited; final Runnable onClosed; + final Consumer threadAccessDeniedHandler; /** * Groups PolyglotContext's filesystem related configurations. @@ -231,6 +232,7 @@ private static Map computeCommonOptions(Map opti sharableConfig.polyglotAccess == null ? engine.getAPIAccess().getPolyglotAccessAll() : sharableConfig.polyglotAccess, sharableConfig.nativeAccessAllowed, sharableConfig.createThreadAllowed, + null, false, false, false, @@ -256,7 +258,7 @@ private static Map computeCommonOptions(Map opti PolyglotContextConfig(PolyglotEngineImpl engine, SandboxPolicy sandboxPolicy, Boolean forceSharing, OutputStream out, OutputStream err, InputStream in, boolean hostLookupAllowed, Object polyglotAccess, boolean nativeAccessAllowed, - boolean createThreadAllowed, boolean hostClassLoadingAllowed, + boolean createThreadAllowed, Consumer threadAccessDeniedHandler, boolean hostClassLoadingAllowed, boolean contextOptionsAllowed, boolean allowExperimentalOptions, Predicate classFilter, Map applicationArguments, Set onlyLanguages, Map options, FileSystemConfig fileSystemConfig, LogHandler logHandler, @@ -278,6 +280,7 @@ private static Map computeCommonOptions(Map opti this.polyglotAccess = polyglotAccess; this.nativeAccessAllowed = nativeAccessAllowed; this.createThreadAllowed = createThreadAllowed; + this.threadAccessDeniedHandler = threadAccessDeniedHandler; this.hostClassLoadingAllowed = hostClassLoadingAllowed; this.innerContextOptionsAllowed = contextOptionsAllowed; this.allowExperimentalOptions = allowExperimentalOptions; diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java index 1752a6831f38..5e7fc5678d64 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotContextImpl.java @@ -825,162 +825,170 @@ Object[] enterThreadChanged(boolean enterReverted, boolean pollSafepoint, boolea Object[] prev = null; Thread current = Thread.currentThread(); try { - boolean deactivateSafepoints = mustSucceed; - boolean localPollSafepoint = pollSafepoint && !mustSucceed; - try { - if (current instanceof SystemThread) { - assert !mustSucceed; - throw PolyglotEngineException.illegalState("Context cannot be entered on system threads."); - } - if (enterDisallowedForPolyglotThread.contains(current)) { - assert !mustSucceed; - throw PolyglotEngineException.illegalState("Context cannot be entered in polyglot thread's beforeEnter or afterLeave notifications."); - } - boolean needsInitialization = false; - synchronized (this) { - PolyglotThreadInfo threadInfo = getCurrentThreadInfo(); - - if (enterReverted && threadInfo.getEnteredCount() == 0) { - threadLocalActions.notifyThreadActivation(threadInfo, false); - if ((state.isCancelling() || state.isExiting() || state == State.CLOSED_CANCELLED || state == State.CLOSED_EXITED) && !threadInfo.isActive()) { - notifyThreadClosed(threadInfo); + for (;;) { + try { + boolean deactivateSafepoints = mustSucceed; + boolean localPollSafepoint = pollSafepoint && !mustSucceed; + try { + if (current instanceof SystemThread) { + assert !mustSucceed; + throw PolyglotEngineException.illegalState("Context cannot be entered on system threads."); } - if ((state.isInterrupting() || state == State.CLOSED_INTERRUPTED) && !threadInfo.isActive()) { - if (threadInfo.interruptSent) { - Thread.interrupted(); - threadInfo.interruptSent = false; - } - notifyAll(); + if (enterDisallowedForPolyglotThread.contains(current)) { + assert !mustSucceed; + throw PolyglotEngineException.illegalState("Context cannot be entered in polyglot thread's beforeEnter or afterLeave notifications."); } - } - if (deactivateSafepoints && threadInfo != PolyglotThreadInfo.NULL) { - threadLocalActions.notifyThreadActivation(threadInfo, false); - } + boolean needsInitialization = false; + synchronized (this) { + PolyglotThreadInfo threadInfo = getCurrentThreadInfo(); - assert threadInfo != null; - if (!leaveAndEnter) { - checkClosedOrDisposing(mustSucceed); - if (threadInfo.isInLeaveAndEnter()) { - throw PolyglotEngineException.illegalState("Context cannot be entered inside leaveAndEnter."); - } - } + if (enterReverted && threadInfo.getEnteredCount() == 0) { + threadLocalActions.notifyThreadActivation(threadInfo, false); + if ((state.isCancelling() || state.isExiting() || state == State.CLOSED_CANCELLED || state == State.CLOSED_EXITED) && !threadInfo.isActive()) { + notifyThreadClosed(threadInfo); + } + if ((state.isInterrupting() || state == State.CLOSED_INTERRUPTED) && !threadInfo.isActive()) { + if (threadInfo.interruptSent) { + Thread.interrupted(); + threadInfo.interruptSent = false; + } + notifyAll(); + } + } + if (deactivateSafepoints && threadInfo != PolyglotThreadInfo.NULL) { + threadLocalActions.notifyThreadActivation(threadInfo, false); + } - threadInfo = threads.get(current); - if (threadInfo == null) { - threadInfo = createThreadInfo(current, polyglotThreadFirstEnter); - needsInitialization = true; - } - if (singleThreaded) { - /* - * If this is the only thread, then setting the cached thread info to NULL - * is no performance problem. If there is other thread that is just about to - * enter, we are making sure that it initializes multi-threading if this - * thread doesn't do it. - */ - setCachedThreadInfo(PolyglotThreadInfo.NULL); - } - boolean transitionToMultiThreading = isSingleThreaded() && hasActiveOtherThread(true, false); + assert threadInfo != null; + if (!leaveAndEnter) { + checkClosedOrDisposing(mustSucceed); + if (threadInfo.isInLeaveAndEnter()) { + throw PolyglotEngineException.illegalState("Context cannot be entered inside leaveAndEnter."); + } + } - if (transitionToMultiThreading) { - // recheck all thread accesses - checkAllThreadAccesses(Thread.currentThread(), false); - } + threadInfo = threads.get(current); + if (threadInfo == null) { + threadInfo = createThreadInfo(current, polyglotThreadFirstEnter); + needsInitialization = true; + } + if (singleThreaded) { + /* + * If this is the only thread, then setting the cached thread info + * to NULL is no performance problem. If there is other thread that + * is just about to enter, we are making sure that it initializes + * multi-threading if this thread doesn't do it. + */ + setCachedThreadInfo(PolyglotThreadInfo.NULL); + } + boolean transitionToMultiThreading = isSingleThreaded() && hasActiveOtherThread(true, false); - if (transitionToMultiThreading) { - /* - * We need to do this early (before initializeMultiThreading) as entering or - * local initialization depends on single thread per context. - */ - engine.singleThreadPerContext.invalidate(); - singleThreaded = false; - } + if (transitionToMultiThreading) { + // recheck all thread accesses + checkAllThreadAccesses(Thread.currentThread(), false); + } - if (needsInitialization) { - threads.put(current, threadInfo); - } + if (transitionToMultiThreading) { + /* + * We need to do this early (before initializeMultiThreading) as + * entering or local initialization depends on single thread per + * context. + */ + engine.singleThreadPerContext.invalidate(); + singleThreaded = false; + } - if (needsInitialization) { - /* - * Do not enter the thread before initializing thread locals. Creation of - * thread locals might fail. - */ - initializeThreadLocals(threadInfo); - } + if (needsInitialization) { + threads.put(current, threadInfo); + } - prev = threadInfo.enterInternal(); - if (leaveAndEnter) { - threadInfo.setLeaveAndEnterInterrupter(null); - notifyAll(); - } - if (needsInitialization) { - this.threadLocalActions.notifyEnterCreatedThread(); - } - if (closingThread != Thread.currentThread()) { - try { - threadInfo.notifyEnter(engine, this); - } catch (Throwable t) { - threadInfo.leaveInternal(prev); - throw t; - } - } - enteredThread = threadInfo; + if (needsInitialization) { + /* + * Do not enter the thread before initializing thread locals. + * Creation of thread locals might fail. + */ + initializeThreadLocals(threadInfo); + } - // new thread became active so we need to check potential active thread local - // actions and process them. - Set activatedActions = null; - if (enteredThread.getEnteredCount() == 1 && !deactivateSafepoints) { - activatedActions = threadLocalActions.notifyThreadActivation(threadInfo, true); - } + prev = threadInfo.enterInternal(); + if (leaveAndEnter) { + threadInfo.setLeaveAndEnterInterrupter(null); + notifyAll(); + } + if (needsInitialization) { + this.threadLocalActions.notifyEnterCreatedThread(); + } + if (closingThread != Thread.currentThread()) { + try { + threadInfo.notifyEnter(engine, this); + } catch (Throwable t) { + threadInfo.leaveInternal(prev); + throw t; + } + } + enteredThread = threadInfo; + + // new thread became active so we need to check potential active thread + // local + // actions and process them. + Set activatedActions = null; + if (enteredThread.getEnteredCount() == 1 && !deactivateSafepoints) { + activatedActions = threadLocalActions.notifyThreadActivation(threadInfo, true); + } - if (transitionToMultiThreading) { - // we need to verify that all languages give access - // to all threads in multi-threaded mode. - transitionToMultiThreaded(mustSucceed); - } + if (transitionToMultiThreading) { + // we need to verify that all languages give access + // to all threads in multi-threaded mode. + transitionToMultiThreaded(mustSucceed); + } - if (needsInitialization) { - initializeNewThread(enteredThread, mustSucceed); - } + if (needsInitialization) { + initializeNewThread(enteredThread, mustSucceed); + } - if (enteredThread.getEnteredCount() == 1 && !pauseHandles.isEmpty()) { - for (Iterator pauseHandleIterator = pauseHandles.iterator(); pauseHandleIterator.hasNext();) { - ContextPauseHandle pauseHandle = pauseHandleIterator.next(); - if (!pauseHandle.pauseThreadLocalAction.isPause() || pauseHandle.isCancelled()) { - pauseHandleIterator.remove(); - } else { - if (activatedActions == null || !activatedActions.contains(pauseHandle.pauseThreadLocalAction)) { - threadLocalActions.submit(new Thread[]{Thread.currentThread()}, PolyglotEngineImpl.ENGINE_ID, pauseHandle.pauseThreadLocalAction, - new HandshakeConfig(true, true, false, false)); + if (enteredThread.getEnteredCount() == 1 && !pauseHandles.isEmpty()) { + for (Iterator pauseHandleIterator = pauseHandles.iterator(); pauseHandleIterator.hasNext();) { + ContextPauseHandle pauseHandle = pauseHandleIterator.next(); + if (!pauseHandle.pauseThreadLocalAction.isPause() || pauseHandle.isCancelled()) { + pauseHandleIterator.remove(); + } else { + if (activatedActions == null || !activatedActions.contains(pauseHandle.pauseThreadLocalAction)) { + threadLocalActions.submit(new Thread[]{Thread.currentThread()}, PolyglotEngineImpl.ENGINE_ID, pauseHandle.pauseThreadLocalAction, + new HandshakeConfig(true, true, false, false)); + } + } } } - } - } - // never cache last thread on close or when closingThread - setCachedThreadInfo(threadInfo); + // never cache last thread on close or when closingThread + setCachedThreadInfo(threadInfo); - if (needsInitialization && !threadInfo.isPolyglotThread()) { - deadThreads = collectDeadThreads(); - } - } + if (needsInitialization && !threadInfo.isPolyglotThread()) { + deadThreads = collectDeadThreads(); + } + } - if (deadThreads != null) { - finalizeAndDisposeThreads(deadThreads); - } + if (deadThreads != null) { + finalizeAndDisposeThreads(deadThreads); + } - if (needsInitialization) { - EngineAccessor.INSTRUMENT.notifyThreadStarted(engine, creatorTruffleContext, current); - } + if (needsInitialization) { + EngineAccessor.INSTRUMENT.notifyThreadStarted(engine, creatorTruffleContext, current); + } - return prev; - } finally { - /* - * We need to always poll the safepoint here in case we already submitted a thread - * local action for this thread. Not polling here would make dependencies of that - * event wait forever. - */ - if (localPollSafepoint && enteredThread != null) { - TruffleSafepoint.pollHere(this.uncachedLocation); + return prev; + } finally { + /* + * We need to always poll the safepoint here in case we already submitted a + * thread local action for this thread. Not polling here would make + * dependencies of that event wait forever. + */ + if (localPollSafepoint && enteredThread != null) { + TruffleSafepoint.pollHere(this.uncachedLocation); + } + } + } catch (PolyglotThreadAccessException ex) { + ex.rethrow(this); } } } catch (Throwable t) { @@ -1078,12 +1086,12 @@ void setCachedThreadInfo(PolyglotThreadInfo info) { } } - synchronized void checkMultiThreadedAccess(Thread newThread) { + synchronized void checkMultiThreadedAccess(Thread newThread) throws PolyglotThreadAccessException { boolean singleThread = singleThreaded ? !isActiveNotCancelled() : false; checkAllThreadAccesses(newThread, singleThread); } - private void checkAllThreadAccesses(Thread enteringThread, boolean singleThread) { + private void checkAllThreadAccesses(Thread enteringThread, boolean singleThread) throws PolyglotThreadAccessException { assert Thread.holdsLock(this); List deniedLanguages = null; for (PolyglotLanguageContext context : contexts) { @@ -1414,7 +1422,7 @@ private void transitionToMultiThreaded(boolean mustSucceed) { volatileStatementCounter.getAndAdd(-statementsExecuted); } - private PolyglotThreadInfo createThreadInfo(Thread current, PolyglotThreadTask polyglotThreadTask) { + private PolyglotThreadInfo createThreadInfo(Thread current, PolyglotThreadTask polyglotThreadTask) throws PolyglotThreadAccessException { assert Thread.holdsLock(this); if (JDKAccessor.isVirtualThread(current)) { @@ -1444,7 +1452,7 @@ private PolyglotThreadInfo createThreadInfo(Thread current, PolyglotThreadTask p return threadInfo; } - static RuntimeException throwDeniedThreadAccess(Thread current, boolean accessSingleThreaded, List deniedLanguages) { + static RuntimeException throwDeniedThreadAccess(Thread current, boolean accessSingleThreaded, List deniedLanguages) throws PolyglotThreadAccessException { String message; StringBuilder languagesString = new StringBuilder(""); for (PolyglotLanguage language : deniedLanguages) { @@ -1458,7 +1466,7 @@ static RuntimeException throwDeniedThreadAccess(Thread current, boolean accessSi } else { message = String.format("Multi threaded access requested by thread %s but is not allowed for language(s) %s.", current, languagesString); } - throw PolyglotEngineException.illegalState(message); + throw new PolyglotThreadAccessException(message); } public Object getBindings(String languageId) { diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineDispatch.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineDispatch.java index e7badb957c59..ab63819c7df7 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineDispatch.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineDispatch.java @@ -150,7 +150,7 @@ public Object createContext(Object oreceiver, SandboxPolicy sandboxPolicy, Outpu String tmpDir, ClassLoader hostClassLoader, boolean allowValueSharing, boolean useSystemExit) { PolyglotEngineImpl receiver = (PolyglotEngineImpl) oreceiver; PolyglotContextImpl context = receiver.createContext(sandboxPolicy, out, err, in, allowHostLookup, hostAccess, polyglotAccess, - allowNativeAccess, allowCreateThread, allowHostClassLoading, + allowNativeAccess, allowCreateThread, null, allowHostClassLoading, allowInnerContextOptions, allowExperimentalOptions, classFilter, options, arguments, onlyLanguages, ioAccess, logHandler, allowCreateProcess, processHandler, environmentAccess, environment, zone, limitsImpl, diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java index d8157032533e..d6e27be24e0c 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotEngineImpl.java @@ -135,6 +135,7 @@ import com.oracle.truffle.polyglot.PolyglotLoggers.EngineLoggerProvider; import com.oracle.truffle.polyglot.PolyglotLoggers.LoggerCache; import com.oracle.truffle.polyglot.SystemThread.InstrumentSystemThread; +import java.util.function.Consumer; /** The implementation of {@link org.graalvm.polyglot.Engine}, stored in the receiver field. */ final class PolyglotEngineImpl implements com.oracle.truffle.polyglot.PolyglotImpl.VMObject { @@ -1732,7 +1733,8 @@ public SourceSection getSourceSection() throws UnsupportedMessageException { @SuppressWarnings({"all"}) public PolyglotContextImpl createContext(SandboxPolicy contextSandboxPolicy, OutputStream configOut, OutputStream configErr, InputStream configIn, boolean allowHostLookup, Object hostAccess, Object polyglotAccess, - boolean allowNativeAccess, boolean allowCreateThread, boolean allowHostClassLoading, boolean allowContextOptions, boolean allowExperimentalOptions, + boolean allowNativeAccess, boolean allowCreateThread, Consumer threadAccessDeniedHandler, boolean allowHostClassLoading, boolean allowContextOptions, + boolean allowExperimentalOptions, Predicate classFilter, Map options, Map arguments, String[] onlyLanguagesArray, Object ioAccess, Object handler, boolean allowCreateProcess, ProcessHandler processHandler, Object environmentAccess, Map environment, ZoneId zone, Object limitsImpl, String currentWorkingDirectory, String tmpDir, ClassLoader hostClassLoader, boolean allowValueSharing, boolean useSystemExit) { @@ -1847,7 +1849,7 @@ public PolyglotContextImpl createContext(SandboxPolicy contextSandboxPolicy, Out } PolyglotLimits polyglotLimits = (PolyglotLimits) limitsImpl; PolyglotContextConfig config = new PolyglotContextConfig(this, contextSandboxPolicy, null, useOut, useErr, useIn, - allowHostLookup, polyglotAccess, allowNativeAccess, allowCreateThread, allowHostClassLoading, allowContextOptions, + allowHostLookup, polyglotAccess, allowNativeAccess, allowCreateThread, threadAccessDeniedHandler, allowHostClassLoading, allowContextOptions, allowExperimentalOptions, classFilter, arguments, allowedLanguages, options, fileSystemConfig, useHandler, allowCreateProcess, useProcessHandler, environmentAccess, environment, zone, polyglotLimits, hostClassLoader, hostAccess, allowValueSharing, useSystemExit, null, null, null, null); context = loadPreinitializedContext(config); diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotLanguageContext.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotLanguageContext.java index 0ec67a5c6e0e..060d69009abe 100644 --- a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotLanguageContext.java +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotLanguageContext.java @@ -285,7 +285,7 @@ PolyglotLanguageInstance getLanguageInstance() { return lazy.languageInstance; } - private void checkThreadAccess(Env localEnv) { + private void checkThreadAccess(Env localEnv) throws PolyglotThreadAccessException { assert Thread.holdsLock(context); boolean singleThreaded = context.isSingleThreaded(); Thread firstFailingThread = null; @@ -618,70 +618,82 @@ void ensureCreated(PolyglotLanguage accessingLanguage) { } } - synchronized (context) { - if (!created) { - if (eventsEnabled) { - EngineAccessor.INSTRUMENT.notifyLanguageContextCreate(context.engine, context.creatorTruffleContext, language.info); - } - boolean wasCreated = false; - try { - Env localEnv = LANGUAGE.createEnv(this, languageInstance.spi, contextConfig.out, - contextConfig.err, - contextConfig.in, - creatorConfig, - contextConfig.getLanguageOptionValues(language).copy(), - contextConfig.getApplicationArguments(language)); - Lazy localLazy = new Lazy(languageInstance, contextConfig); - - if (layer.isSingleContext()) { - languageInstance.singleLanguageContext.update(this); - } else { - languageInstance.singleLanguageContext.invalidate(); + PolyglotThreadAccessException threadAccessException = null; + loop: for (;;) { + if (threadAccessException != null) { + threadAccessException.rethrow(context); + } + synchronized (context) { + if (!created) { + if (eventsEnabled) { + EngineAccessor.INSTRUMENT.notifyLanguageContextCreate(context.engine, context.creatorTruffleContext, language.info); } - - checkThreadAccess(localEnv); - - // no more errors after this line - creatingThread = Thread.currentThread(); - env = localEnv; - lazy = localLazy; - assert EngineAccessor.LANGUAGE.getLanguage(env) != null; - + boolean wasCreated = false; try { - List languageServicesCollector = new ArrayList<>(); - Object contextImpl = LANGUAGE.createEnvContext(localEnv, languageServicesCollector); - language.initializeContextClass(contextImpl); - String errorMessage = verifyServices(language.info, languageServicesCollector, language.cache.getServices()); - if (errorMessage != null) { - throw PolyglotEngineException.illegalState(errorMessage); + Env localEnv = LANGUAGE.createEnv(this, languageInstance.spi, contextConfig.out, + contextConfig.err, + contextConfig.in, + creatorConfig, + contextConfig.getLanguageOptionValues(language).copy(), + contextConfig.getApplicationArguments(language)); + Lazy localLazy = new Lazy(languageInstance, contextConfig); + + if (layer.isSingleContext()) { + languageInstance.singleLanguageContext.update(this); + } else { + languageInstance.singleLanguageContext.invalidate(); } - PolyglotFastThreadLocals.notifyLanguageCreated(this); - this.languageServices = languageServicesCollector; - if (language.isHost()) { - context.initializeHostContext(this, context.config); + + try { + checkThreadAccess(localEnv); + } catch (PolyglotThreadAccessException ex) { + threadAccessException = ex; + continue loop; } - wasCreated = true; - if (eventsEnabled) { - EngineAccessor.INSTRUMENT.notifyLanguageContextCreated(context.engine, context.creatorTruffleContext, language.info); + + // no more errors after this line + creatingThread = Thread.currentThread(); + env = localEnv; + lazy = localLazy; + assert EngineAccessor.LANGUAGE.getLanguage(env) != null; + + try { + List languageServicesCollector = new ArrayList<>(); + Object contextImpl = LANGUAGE.createEnvContext(localEnv, languageServicesCollector); + language.initializeContextClass(contextImpl); + String errorMessage = verifyServices(language.info, languageServicesCollector, language.cache.getServices()); + if (errorMessage != null) { + throw PolyglotEngineException.illegalState(errorMessage); + } + PolyglotFastThreadLocals.notifyLanguageCreated(this); + this.languageServices = languageServicesCollector; + if (language.isHost()) { + context.initializeHostContext(this, context.config); + } + wasCreated = true; + if (eventsEnabled) { + EngineAccessor.INSTRUMENT.notifyLanguageContextCreated(context.engine, context.creatorTruffleContext, language.info); + } + context.invokeContextLocalsFactory(context.contextLocals, languageInstance.contextLocalLocations); + context.invokeContextThreadLocalFactory(languageInstance.contextThreadLocalLocations); + + languageInstance = null; // commit language use + } catch (Throwable e) { + env = null; + lazy = null; + throw e; + } finally { + creatingThread = null; } - context.invokeContextLocalsFactory(context.contextLocals, languageInstance.contextLocalLocations); - context.invokeContextThreadLocalFactory(languageInstance.contextThreadLocalLocations); - - languageInstance = null; // commit language use - } catch (Throwable e) { - env = null; - lazy = null; - throw e; + created = true; } finally { - creatingThread = null; - } - created = true; - } finally { - if (!wasCreated && eventsEnabled) { - EngineAccessor.INSTRUMENT.notifyLanguageContextCreateFailed(context.engine, context.creatorTruffleContext, language.info); + if (!wasCreated && eventsEnabled) { + EngineAccessor.INSTRUMENT.notifyLanguageContextCreateFailed(context.engine, context.creatorTruffleContext, language.info); + } } } } + break loop; } } } diff --git a/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotThreadAccessException.java b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotThreadAccessException.java new file mode 100644 index 000000000000..0506ad3bbbdd --- /dev/null +++ b/truffle/src/com.oracle.truffle.polyglot/src/com/oracle/truffle/polyglot/PolyglotThreadAccessException.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.truffle.polyglot; + +import com.oracle.truffle.api.CompilerDirectives; + +final class PolyglotThreadAccessException extends Exception { + static final long serialVersionUID = 1L; + + PolyglotThreadAccessException(String message) { + super(message); + } + + @SuppressWarnings("sync-override") + @Override + public Throwable fillInStackTrace() { + return this; + } + + void rethrow(PolyglotContextImpl context) { + assert !Thread.holdsLock(context) : "Only rethrow without holding internal lock"; + var consumer = context.config.threadAccessDeniedHandler; + if (consumer != null) { + try { + consumer.accept(getMessage()); + } catch (IllegalStateException e) { + throw new PolyglotEngineException(e); + } catch (Throwable another) { + if (context.parent != null) { // inner context + // inner context. no wrapping needed + throw another; + } else { + throw CompilerDirectives.shouldNotReachHere("Embedder context cannot have threadAccessDeniedHandler."); + } + } + } else { + var ex = new IllegalStateException(getMessage()); + throw new PolyglotEngineException(ex); + } + } +}