From c4e3624dc79f440d624c22ab1d22a62dce5358a7 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 29 Jan 2024 07:33:43 +0100 Subject: [PATCH] Allow registration of onDeniedThreadAccess callback --- .../src/org/graalvm/polyglot/Context.java | 26 ++++++++++++++++--- .../polyglot/impl/AbstractPolyglotImpl.java | 4 ++- .../api/test/wrapper/HostEngineDispatch.java | 4 +-- .../api/test/wrapper/HostEntryPoint.java | 2 +- .../truffle/polyglot/EngineAccessor.java | 12 +++++---- .../polyglot/PolyglotContextConfig.java | 5 +++- .../truffle/polyglot/PolyglotContextImpl.java | 2 +- .../polyglot/PolyglotEngineDispatch.java | 4 +-- .../truffle/polyglot/PolyglotEngineImpl.java | 5 ++-- .../polyglot/PolyglotLanguageContext.java | 2 +- .../PolyglotThreadAccessException.java | 11 +++++--- 11 files changed, 55 insertions(+), 22 deletions(-) diff --git a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Context.java b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Context.java index 43358167f793..ca4e6826ff6c 100644 --- a/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Context.java +++ b/sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Context.java @@ -57,6 +57,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.logging.Handler; import java.util.logging.Level; @@ -286,8 +287,9 @@ * instance may be used from multiple threads at the same time depends on if all initialized * languages support it. If initialized languages support multi-threading, then the context instance * may be used from multiple threads at the same time. If a context is used from multiple threads - * and the language does not fit, then an {@link IllegalStateException} is thrown by the accessing - * method. + * and the language does not fit, then an + * {@link IllegalStateException} is thrown by the accessing + * method (unless {@link Builder#onDeniedThreadAccess} is specified). *

* Meta-data from the context's underlying {@link #getEngine() engine} can be retrieved safely by * any thread at any time. @@ -1060,6 +1062,7 @@ public final class Builder { private ClassLoader hostClassLoader; private boolean useSystemExit; private SandboxPolicy sandboxPolicy; + private Consumer onDeniedThreadAccess; Builder(String... permittedLanguages) { Objects.requireNonNull(permittedLanguages); @@ -1180,6 +1183,23 @@ public Builder allowCreateThread(boolean enabled) { 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 the provided exception or resolve the multithreaded situation + * and return to retry the thread access again. + * + * @param handler callback that either throws the provided {@code RuntimeException} or returns + * to signal request for retry + * @return this builder + * @since 23.2 + */ + public Builder onDeniedThreadAccess(Consumer handler) { + this.onDeniedThreadAccess = handler; + return this; + } + /** * Sets the default value for all privileges. If not explicitly specified, then all access * is false. If all access is enabled then certain privileges may still be @@ -1935,7 +1955,7 @@ public Context build() { Object logHandler = customLogHandler != null ? Engine.getImpl().newLogHandler(customLogHandler) : null; String tmpDir = Engine.getImpl().getIO().hasHostFileAccess(useIOAccess) ? System.getProperty("java.io.tmpdir") : null; ctx = (Context) engine.dispatch.createContext(engine.receiver, useSandboxPolicy, contextOut, contextErr, contextIn, hostClassLookupEnabled, - hostAccess, polyglotAccess, nativeAccess, createThread, hostClassLoading, innerContextOptions, + hostAccess, polyglotAccess, nativeAccess, createThread, onDeniedThreadAccess, hostClassLoading, innerContextOptions, experimentalOptions, localHostLookupFilter, contextOptions, arguments == null ? Collections.emptyMap() : arguments, permittedLanguages, useIOAccess, logHandler, createProcess, processHandler, useEnvironmentAccess, environment, zone, limits, localCurrentWorkingDirectory, tmpDir, hostClassLoader, allowValueSharing, useSystemExit); 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 b5f5b90a28d2..f306a82b722f 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 @@ -807,7 +807,9 @@ public abstract Object createContext(Object receiver, SandboxPolicy sandboxPolic Object hostAccess, Object polyglotAccess, boolean allowNativeAccess, - boolean allowCreateThread, boolean allowHostClassLoading, boolean allowInnerContextOptions, boolean allowExperimentalOptions, + boolean allowCreateThread, + Consumer onDeniedThreadAccess, + 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/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 eff9a0a133b4..c1a64b7d1610 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,7 @@ 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, Consumer onDeniedThreadAccess, 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) { @@ -82,7 +82,7 @@ public Object createContext(Object receiver, SandboxPolicy sandboxPolicy, Output Engine localEngine = (Engine) engine.localEngine; AbstractEngineDispatch dispatch = api.getEngineDispatch(localEngine); Object engineReceiver = api.getEngineReceiver(localEngine); - Context localContext = (Context) dispatch.createContext(engineReceiver, sandboxPolicy, out, err, in, allowHostAccess, hostAccess, polyglotAccess, allowNativeAccess, allowCreateThread, + Context localContext = (Context) dispatch.createContext(engineReceiver, sandboxPolicy, out, err, in, allowHostAccess, hostAccess, polyglotAccess, allowNativeAccess, allowCreateThread, onDeniedThreadAccess, allowHostClassLoading, allowInnerContextOptions, allowExperimentalOptions, classFilter, options, arguments, onlyLanguages, ioAccess, logHandler, allowCreateProcess, processHandler, environmentAccess, environment, zone, limitsImpl, currentWorkingDirectory, tmpDir, hostClassLoader, true, useSystemExit); diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/wrapper/HostEntryPoint.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/wrapper/HostEntryPoint.java index 63da5bbeee23..ce4a676b1766 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/wrapper/HostEntryPoint.java +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/wrapper/HostEntryPoint.java @@ -125,7 +125,7 @@ public long remoteCreateContext(long engineId, SandboxPolicy sandboxPolicy, Stri Object receiver = api.getEngineReceiver(engine); AbstractEngineDispatch dispatch = api.getEngineDispatch(engine); Context remoteContext = (Context) dispatch.createContext(receiver, sandboxPolicy, null, null, null, false, null, PolyglotAccess.NONE, false, - false, false, false, false, null, new HashMap<>(), new HashMap<>(), + false, null, false, false, false, null, new HashMap<>(), new HashMap<>(), new String[0], IOAccess.NONE, null, false, null, EnvironmentAccess.NONE, null, null, null, null, tmpDir, null, true, false); 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 8dbfbfe944e8..022e5c00799c 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 @@ -1095,7 +1095,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, creatorConfig.onDeniedThreadAccess, useAllowHostClassLoading, useAllowInnerContextOptions, creatorConfig.allowExperimentalOptions, useClassFilter, useArguments, allowedLanguages, useOptions, fileSystemConfig, creatorConfig.logHandler, useAllowCreateProcess, useProcessHandler, useEnvironmentAccess, useCustomEnvironment, @@ -1150,10 +1150,12 @@ public Thread createThread(Object polyglotLanguageContext, Runnable runnable, Ob threadContext = innerContext.getContext(threadContext.language); } PolyglotThread newThread = new PolyglotThread(threadContext, runnable, group, stackSize, beforeEnter, afterLeave); - try { - threadContext.context.checkMultiThreadedAccess(newThread); - } catch (PolyglotThreadAccessException ex) { - throw ex.rethrow(threadContext.context); + for (;;) { + try { + threadContext.context.checkMultiThreadedAccess(newThread); + } catch (PolyglotThreadAccessException ex) { + ex.rethrow(threadContext.context); + } } return newThread; } 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 280540b9f16e..449114978491 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 onDeniedThreadAccess; /** * 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 onDeniedThreadAccess, 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.onDeniedThreadAccess = onDeniedThreadAccess; 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 f0a49362472e..85e721efe715 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 @@ -840,7 +840,7 @@ Object[] enterThreadChanged(boolean enterReverted, boolean pollSafepoint, boolea PolyglotThreadAccessException threadAccessException = null; LOOP: for (;;) { if (threadAccessException != null) { - throw threadAccessException.rethrow(this); + threadAccessException.rethrow(this); } boolean needsInitialization = false; synchronized (this) { 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 dd965bdcccb3..3753721a2da5 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 @@ -143,14 +143,14 @@ public OptionDescriptors getOptions(Object oreceiver) { public Object createContext(Object oreceiver, SandboxPolicy sandboxPolicy, OutputStream out, OutputStream err, InputStream in, boolean allowHostLookup, Object hostAccess, Object polyglotAccess, boolean allowNativeAccess, - boolean allowCreateThread, boolean allowHostClassLoading, boolean allowInnerContextOptions, + boolean allowCreateThread, Consumer onDeniedThreadAccess, 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) { PolyglotEngineImpl receiver = (PolyglotEngineImpl) oreceiver; PolyglotContextImpl context = receiver.createContext(sandboxPolicy, out, err, in, allowHostLookup, hostAccess, polyglotAccess, - allowNativeAccess, allowCreateThread, allowHostClassLoading, + allowNativeAccess, allowCreateThread, onDeniedThreadAccess, 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 29af364b69d9..5b3fff962090 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 @@ -130,6 +130,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; final class PolyglotEngineImpl implements com.oracle.truffle.polyglot.PolyglotImpl.VMObject { @@ -1691,7 +1692,7 @@ 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 onDeniedThreadAccess, 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) { @@ -1807,7 +1808,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, onDeniedThreadAccess, 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 348f7c225d71..d86c1562f49d 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 @@ -624,7 +624,7 @@ void ensureCreated(PolyglotLanguage accessingLanguage) { PolyglotThreadAccessException threadAccessException = null; LOOP: for (;;) { if (threadAccessException != null) { - throw threadAccessException.rethrow(context); + threadAccessException.rethrow(context); } synchronized (context) { if (!created) { 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 index 1d24a6e357ba..84d2d5e55ab3 100644 --- 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 @@ -47,8 +47,13 @@ final class PolyglotThreadAccessException extends Exception { super(message); } - RuntimeException rethrow(PolyglotContextImpl noLockCheck) { - assert !Thread.holdsLock(noLockCheck) : "Only rethrow without holding internal lock"; - throw PolyglotEngineException.illegalState(getMessage()); + void rethrow(PolyglotContextImpl context) { + assert !Thread.holdsLock(context) : "Only rethrow without holding internal lock"; + PolyglotEngineException ex = PolyglotEngineException.illegalState(getMessage()); + if (context.config.onDeniedThreadAccess != null) { + context.config.onDeniedThreadAccess.accept(ex); + } else { + throw ex; + } } }