-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Allow polyglot embedders to control throwDeniedThreadAccess
#8266
base: master
Are you sure you want to change the base?
Changes from all commits
82b14c3
6133eb0
c4e3624
fe1c98f
853702c
addb748
b2d6f34
f91faeb
22746a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -287,7 +288,7 @@ | |
* 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. | ||
* method (unless {@link Builder#onDeniedThreadAccess} is specified). | ||
* <p> | ||
* Meta-data from the context's underlying {@link #getEngine() engine} can be retrieved safely by | ||
* any thread at any time. | ||
|
@@ -1060,6 +1061,7 @@ public final class Builder { | |
private ClassLoader hostClassLoader; | ||
private boolean useSystemExit; | ||
private SandboxPolicy sandboxPolicy; | ||
private Consumer<RuntimeException> onDeniedThreadAccess; | ||
|
||
Builder(String... permittedLanguages) { | ||
Objects.requireNonNull(permittedLanguages); | ||
|
@@ -1180,6 +1182,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. | ||
* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could use usage examples. For example customization of the error message and or using a locking strategy? |
||
* @param handler callback that either throws the provided {@code RuntimeException} or | ||
* returns to signal request for retry | ||
* @return this builder | ||
* @since 23.2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Target version is 24.1 |
||
*/ | ||
public Builder onDeniedThreadAccess(Consumer<RuntimeException> handler) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API changes like this need an entry in the changelog. |
||
this.onDeniedThreadAccess = handler; | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the default value for all privileges. If not explicitly specified, then all access | ||
* is <code>false</code>. If all access is enabled then certain privileges may still be | ||
|
@@ -1935,7 +1954,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); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,6 +86,7 @@ | |
import com.oracle.truffle.api.test.polyglot.MultiThreadedLanguage.LanguageContext; | ||
import com.oracle.truffle.api.test.polyglot.MultiThreadedLanguage.ThreadRequest; | ||
import com.oracle.truffle.tck.tests.TruffleTestAssumptions; | ||
import static org.junit.Assert.assertFalse; | ||
|
||
public class MultiThreadedLanguageTest { | ||
|
||
|
@@ -141,6 +142,86 @@ public void testNoThreadAllowed() { | |
context.close(); | ||
} | ||
|
||
@Test | ||
public void testNoThreadAllowedWithSecurityException() { | ||
Context context = Context.newBuilder(MultiThreadedLanguage.ID).onDeniedThreadAccess((t) -> { | ||
throw new SecurityException(t.getMessage()); | ||
}).build(); | ||
|
||
MultiThreadedLanguage.isThreadAccessAllowed = (req) -> { | ||
return false; | ||
}; | ||
|
||
try { | ||
context.initialize(MultiThreadedLanguage.ID); | ||
fail(); | ||
} catch (PolyglotException e) { | ||
assertTrue(e.getMessage().contains("SecurityException")); | ||
assertTrue(e.isInternalError()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Internal errors must never be reachable. Whatever exception is thrown by onDeniedThreadAccess must be a host exception. |
||
} | ||
|
||
try { | ||
eval(context, (env) -> null); | ||
fail(); | ||
} catch (PolyglotException e) { | ||
assertTrue(e.getMessage().contains("SecurityException")); | ||
assertTrue(e.isInternalError()); | ||
} | ||
|
||
try { | ||
eval(context, (env) -> null); | ||
fail(); | ||
} catch (PolyglotException e) { | ||
assertTrue(e.getMessage().contains("SecurityException")); | ||
assertTrue(e.isInternalError()); | ||
} | ||
|
||
// allow again so we can close | ||
MultiThreadedLanguage.isThreadAccessAllowed = (req) -> { | ||
return true; | ||
}; | ||
context.close(); | ||
} | ||
|
||
@Test | ||
public void testNoThreadAllowedRetry() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like to see an integration test that uses it in a way that involves multiple threads. How about something that would deadlock if executed within the lock? |
||
boolean[] isThreadAccessAllowed = {false}; | ||
int[] state = {0}; | ||
Context context = Context.newBuilder(MultiThreadedLanguage.ID).onDeniedThreadAccess((t) -> { | ||
switch (state[0]) { | ||
case 0 -> { | ||
// retry | ||
assertFalse("Thread access wasn't allowed", isThreadAccessAllowed[0]); | ||
state[0] = 1; | ||
return; | ||
} | ||
case 1 -> { | ||
// allow & retry | ||
assertFalse("Thread access wasn't allowed", isThreadAccessAllowed[0]); | ||
isThreadAccessAllowed[0] = true; | ||
state[0] = 2; | ||
return; | ||
} | ||
default -> // just forbid with an exception | ||
throw t; | ||
} | ||
}).build(); | ||
|
||
MultiThreadedLanguage.isThreadAccessAllowed = (req) -> { | ||
return isThreadAccessAllowed[0]; | ||
}; | ||
|
||
Value res = eval(context, (env) -> 42); | ||
assertEquals("There were two queries - e.g. one retry", 2, state[0]); | ||
assertEquals("Fourty two is reaturned", 42, res.asInt()); | ||
|
||
// allow again so we can close | ||
MultiThreadedLanguage.isThreadAccessAllowed = (req) -> { | ||
return true; | ||
}; | ||
context.close(); | ||
} | ||
|
||
private static void assertMultiThreadedError(Value value, Consumer<Value> valueConsumer) { | ||
try { | ||
valueConsumer.accept(value); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,7 +1150,14 @@ public Thread createThread(Object polyglotLanguageContext, Runnable runnable, Ob | |
threadContext = innerContext.getContext(threadContext.language); | ||
} | ||
PolyglotThread newThread = new PolyglotThread(threadContext, runnable, group, stackSize, beforeEnter, afterLeave); | ||
threadContext.context.checkMultiThreadedAccess(newThread); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't you move the loop into checkMultiThreadedAccess? |
||
for (;;) { | ||
try { | ||
threadContext.context.checkMultiThreadedAccess(newThread); | ||
break; | ||
} catch (PolyglotThreadAccessException ex) { | ||
ex.rethrow(threadContext.context); | ||
} | ||
} | ||
return newThread; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The corresponding TruffleContext API is missing.