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?
Allow polyglot embedders to control throwDeniedThreadAccess
#8266
Conversation
With
The good thing from the Truffle API perspective is:
The |
I like the idea in principle. What I like about this approach:
What I don't like about this approach:
to something like this (just a sketch)
|
OK, I'll write some unit tests and then we can continue the review. I am not sure I fully understand all the comments, for example:
Sure, I only care about Graal.js - all the other languages we are interested in are mutli threading ready. All I need is to make sure when there is a collision, it doesn't end up that fatal as right now and there is a way to recover. I don't get the comment about multi-threaded languages. We can sort it out when I prepare some unit tests for review. But I'd prefer to keep my PR as minimal as possible - solving complete Truffle corner cases is job for a professional, not for an outsider like me.
Do you mean those I'll find some time this week and add some test cases to this PR. |
The first test added in addb748 verifies that all the ways that originally yielded The other test (added in f91faeb) verifies that the @chumer, what additional tests you want me to provide? |
Hello @chumer, is there anything else I shall do to get this API change in? |
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.
A great step forward. Some of the things I previously commented on were unfortunately not adressed. Added a few more comments.
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
Can't you move the loop into checkMultiThreadedAccess?
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 comment
The 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.
} | ||
|
||
@Test | ||
public void testNoThreadAllowedRetry() { |
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.
I would like to see an integration test that uses it in a way that involves multiple threads.
Something that really models how you want to use it.
How about something that would deadlock if executed within the lock?
synchronized (this) { | ||
PolyglotThreadInfo threadInfo = getCurrentThreadInfo(); | ||
PolyglotThreadAccessException threadAccessException = null; | ||
LOOP: for (;;) { |
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.
minor: We are not actually using upper-case loop labels. We typically use lower-cased ones. Let's be consistent.
* 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Target version is 24.1
* @return this builder | ||
* @since 23.2 | ||
*/ | ||
public Builder onDeniedThreadAccess(Consumer<RuntimeException> handler) { |
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.
API changes like this need an entry in the changelog.
*/ | ||
package com.oracle.truffle.polyglot; | ||
|
||
final class PolyglotThreadAccessException extends Exception { |
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.
This exception does not need a stack trace as it is supposed to be caught always.
@@ -1180,6 +1182,23 @@ public Builder allowCreateThread(boolean enabled) { | |||
return this; | |||
} | |||
|
|||
/** |
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.
Let's fix #8033 by controlling what happens when
throwDeniedThreadAccess
is called.The simplest way to address #8033 is to replace the current action in
throwDeniedThreadAccess
- e.g. throwPolyglotEngineException.illegalState
with a callback to embedder to decide what to do - don't throw, but retry. However calling foreign code at the moment ofthrowDeniedThreadAccess
is slightly dangerous as internal lock onPolyglotContextImpl
is being held. As such, let's start with the first step 82b14c3 - let's make sure the exception is always thrown without holding internal lock(s). To do so, I am introducing an internal checked exception and propagating it up to the point where the lock isn't held. Only then it is converted to originalPolyglotEngineException.illegalState
exception and thrown. The fact that the exception is checked, instructs thejavac
to check all paths that may yield thePolyglotThreadAccessException
and help me to never forget to handle the conversion. The unusualLOOP: for (;;) {... continue LOOP.... break LOOP}
style is used as a preparation for the next step...The next step then defines
Builder.onDeniedThreadAccess(Consumer<RuntimeException> handler)
which allows the embedders to decide whether to throw the exception or do something more intrinsic.The third step is to use the API. Great, it seems to work as planned.