Skip to content
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

feat: module for Polyglot Context injection #15

Open
wants to merge 1 commit into
base: 1.0.x
Choose a base branch
from

Conversation

sdelamo
Copy link
Contributor

@sdelamo sdelamo commented Sep 25, 2024

@steve-s and I put together this proof of concept for allowing users to inject Graal Languages Context into their application. I think using @ThreadLocal could good for this use case.

@steve-s will update you with details about the Context constraints.

Thoughts @fniephaus @mikehearn @dstepanov @graemerocher ?

@mikehearn
Copy link

It might be worth checking out the react code. I refactored it to be more general (btw the PR was never merged):

micronaut-projects/micronaut-views#859

Look at BeanPool. Using ThreadLocals for sharing contexts isn't quite right because especially with virtual threads there may be many threads processing HTTP requests that only want to enter contexts occasionally. So you need to create only as many contexts as is needed to satisfy the contention.

It also has other useful utility classes like IntrospectableToProxyObject. That code may be a good basis for a more general Truffle support.

@steve-s
Copy link
Contributor

steve-s commented Sep 25, 2024

Some details regarding the Contexts:

  • Context object represents a global runtime state for all the languages loaded in the Context. One can have multiple Context instances which would represent, e.g., independent Python interpreters.
  • Multiple Contexts can belong to one Engine (Engine instance can be passed to the ContextBuilder). The Contexts within one Engine share JIT compiled code of the guest language, e.g., Python, but no other state visible to the user.
  • Context creation and initialization can be relatively expensive. For JavaScript AFAIK it is ~0.5ms, for Python ~50ms.

The languages can have some usage constraints:

JavaScript is a single threaded language, which means that only single thread can evaluate JavaScript code through single Context instance. If multiple threads try to evaluate JavaScript using the same Context object, you'll get a runtime exception saying that the JavaScript language does not support this.

Python supports multi-threading and one can execute Python code from multiple threads at the same time, but internally Python uses "global" interpreter lock, so if two Java threads are executing Python code using the same Context, the two threads will take turns in a round robin fashion to make progress, so it will look like parallel execution, but in reality only one thread at a time executes and the other is blocked on the lock.

However, different Contexts do not share the same "global" interpreter lock. So if two threads execute Python using two different Context objects, they will not block each other like this.

@dstepanov
Copy link

Sounds like we should pool contexts

* @since 1.1.0
*/
@Experimental
public class ContextProvider {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the comments on the PR, contexts should be pooled.
The best would be to have something like:

<R> R useContext(Function<Context, R> fn);

With the implementation:

  • get the context from the pool
  • invoke lambda
  • return the context to the pool
  • return the lambda result

WDYT @steve-s @mikehearn ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks nice. There is still one issue: you can get Java objects that wrap JavaScript/Python/... objects that are associated with given context. Example:

Value fun = useContext(ctx -> ctx.eval("js", "x => x + 1"));
fun.execute(42); // <-- this will execute the function in the `Context` where it originated from

thinking about this, I am not sure there can ever be some "compile-time safety net" for this, users will have to understand the constraints and make sure they do not hold onto Value instances from the Context outside of the lambda.

@mikehearn used try-with-resources for his Context pool (but that doesn't solve this issue either).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Techically we can check what type is being returned from the method, but that would have to be an implementation specific check

Copy link
Contributor

@steve-s steve-s Sep 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: it is even trickier with the proxy feature:

Value pythonClassInstance = ctx.eval("python", """
   class MyPythonClass:
      def foo(x): return x+1

   MyPythonClass() # create instance
""")

MyJavaInterface javaProxy = pythonClassInstance.as(MyJavaInterface.class);
// now javaProxy looks like plain Java object, but internally it is backed by `Value`

assert javaProxy.foo(42) == 43 // evaluates Python function "foo" in the Context ctx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants