I'm testing the JS language on GraalVM 1.0.0rc5 and getting an exception when trying to access a JS object created in context A but accessed in context B.
org.graalvm.polyglot.PolyglotException: java.lang.IllegalArgumentException: Values cannot be passed from one context to another. The current value originates from context 0x7ba8c737 and the argument originates from context 0x74294adb.
at com.oracle.truffle.api.vm.PolyglotLanguageContext.toGuestValue(PolyglotLanguageContext.java:566)
at com.oracle.truffle.api.vm.PolyglotLanguageContext.toGuestValue(PolyglotLanguageContext.java:677)
at org.graalvm.polyglot.Value.execute(Value.java:312)
My scenario is an object that is expensive to compute is created in context A and would like to be re-used in context B later (and C and D etc), this doesn't appear to be supported? Is there another way to achieve this outcome? It is effectively a long lived JS object, being injected into many shortlived contexts. This is achievable in Nashorn.
The reason for recreating new contexts is to clear any eval'd code between executions, but since the shared object is expensive to compute, it can't be recreated everytime, i'd like to share it. A way to 'clear' an existing context rather than re-created many short-lived contexts would also work..
Simple test code:
Value obj = null;
try (Context context = Context.newBuilder("js").build()) {
obj = context.eval("js", "{a: 1}"); //could be expensive
}
try (Context context = Context.newBuilder("js").build()) {
context.getBindings("js").putMember("jsobj", obj);
context.eval("js", "JSON.stringify(jsobj)");
}
You are hitting two limitations here:
1) When you close the original context all of its values become invalid.
2) You currently cannot pass objects from one context to the other, even if the contexts are still active (=not closed).
The only currently possible workaround for both problems is to serialize it into a json string and deserialize it in every new context. But I see that this is not what you want.
We can support 2, but 1 really is by design. If a context is closed some if its environment may be no longer available/allocated, therefore if you call a method on that object it will lead to undefined behavior.
Could you reuse the object like this by keeping the first context alive?
Value obj = null;
try (Context context1 = Context.newBuilder("js").build()) {
obj = context1.eval("js", "{a: 1}"); //could be expensive
for (...many contexts...) {
try (Context context2 = Context.newBuilder("js").build()) {
context2.getBindings("js").putMember("jsobj", obj);
context2.eval("js", "JSON.stringify(jsobj)");
}
}
}
Please note that even if we support 2 the value will only be accessible from one thread at a time. If you try to access that shared JS object from multiple threads it will fail. JS objects can only be used by one thread at a time. Nashorn tended to ignore that fact. Would you need to share that object for multiple contexts on multiple threads?
Yeah so a single thread is a reasonable restriction we can work with, especially with JS that naturally lends itself to single threaded execution. My understanding of Nashorn was that it allowed multi threaded access but gave no guarantees about behaviour, and that often meant you got strange non-deterministic behaviour.
Your example would be exactly how we would do it if you did support 2, the expensive object is calculated in its own long running context, and passed into the many later contexts.
Is supporting 2 on the roadmap?
Yes. 2 is on the roadmap. Cannot quite say yet when we get to it. Do you have a way to workaround it or is it a blocker for you?
Please note that accesses to the shared object of outer context in the inner context will be a bit slower than accesses to normal JS objects, as we need to do a thread-check and change the active context each time the shared object is accessed.
We are looking to migrate off Nashorn onto GraalJS, being able to share objects across contexts as described is a showstopper for us, but Nashorn still works (however unloved it is with all its problems).
We would love to get onto Graal soon tho, lots of great stuff on offer =)
Is this fixed in rc9 ? I see in the changelog this:
Primitives, host and Proxy values can now be shared between multiple context and engine instances. They no longer throw an IllegalArgumentException when shared.
To answer my own question, yes this is now fixed in rc9. Re-testing with the same repro code above now passes as expected. 👍
Nope, totes wrong, just my initial test is slightly wrong, needs to use getMember() instead of the return value, this proper test causes the failure still 😢
Value obj = null;
try (Context context = Context.newBuilder("js").build()) {
context.eval("js", "var jsobj = {a: 1}"); //could be expensive
obj = context.getBindings("js").getMember("jsobj");
}
try (Context context = Context.newBuilder("js").build()) {
context.getBindings("js").putMember("jsobj", obj);
System.out.println(context.eval("js", "JSON.stringify(jsobj)"));
}
stack:
Exception in thread "main" java.lang.IllegalArgumentException: The value 'DynamicObject<JSUserObject>@351d00c0' cannot be passed from one context to another. The current context is 0x409bf450 and the argument value originates from context 0x3b22cdd0.
at com.oracle.truffle.polyglot.PolyglotLanguageContext.migrateValue(PolyglotLanguageContext.java:695)
at com.oracle.truffle.polyglot.PolyglotLanguageContext.toGuestValue(PolyglotLanguageContext.java:653)
at com.oracle.truffle.polyglot.PolyglotLanguageContext$ToGuestValueNode.apply(PolyglotLanguageContext.java:542)
at com.oracle.truffle.polyglot.PolyglotValue$InteropCodeCache$PutMemberNode.executeImpl(PolyglotValue.java:1092)
at com.oracle.truffle.polyglot.HostRootNode.execute(HostRootNode.java:94)
at org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.callProxy(OptimizedCallTarget.java:289)
at org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.callRoot(OptimizedCallTarget.java:278)
at org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.callBoundary(OptimizedCallTarget.java:265)
at org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.doInvoke(OptimizedCallTarget.java:247)
at org.graalvm.compiler.truffle.runtime.GraalTVMCI.callProfiled(GraalTVMCI.java:86)
at com.oracle.truffle.api.impl.Accessor.callProfiled(Accessor.java:725)
at com.oracle.truffle.polyglot.VMAccessor.callProfiled(VMAccessor.java:91)
at com.oracle.truffle.polyglot.PolyglotValue$InteropValue.putMember(PolyglotValue.java:2466)
at org.graalvm.polyglot.Value.putMember(Value.java:286)
at Main.main(Main.java:17)
Caused by: Attached Guest Language Frames (1)
Ah so the behaviour described in the recent release notes indicate that just primitive, host and proxy Value objects can be shared. This is somewhat disingenuous as host and proxy objects can be _shared_ but not changed. In my example with a expensive computed object, I want to compute and then change the object through execution in many contexts.
Hi,
I just checked the advanced example Mapping to Java methods and fields at GraalVM SDK Java API Reference (org.graalvm.polyglot.Context).
I end up with not being able to access the properties of the JavaRecord object in js. They all are undefined. Did I miss something in the code here ?
~~~
import org.graalvm.polyglot.*;
class JavaRecord {
public int x = 42;
public double y = 42.0;
public String name() {
return "foo";
}
}
class CommandLineExample
{
public static void main ( String [] arguments )
{
Context context = Context.create();
Value record = context.asValue(new JavaRecord());
context.eval("js", "(function(record) {console.log(record); console.log(record.x); console.log(record.y); console.log(record.name()); return record.x;})")
.execute(record);// .asInt() == 42;
}
}
~~~
Hi @ArthurStocker
sorry for the delayed response.
You cannot access the fields of the JavaRecord class because it is not public. Make that class public, and your code works.
import org.graalvm.polyglot.*;
class CommandLineExample
{
public static void main ( String [] arguments )
{
Context context = Context.create();
Value record = context.asValue(new JavaRecord());
context.eval("js", "(function(record) {console.log(record); print(record.x); console.log(record.x); console.log(record.y); print('keys: '+Object.keys(record)); console.log(record.name()); return record.x;})")
.execute(new JavaRecord());// .asInt() == 42;
}
public static class JavaRecord {
public int x = 42;
public double y = 42.0;
public String name() {
return "foo";
}
}
}
Best,
Christian
Hi @wirthi
got it. Sorry, should have been able to find it myself.
re Arthur
gents, I'm having the same problem like originally describes as 2), however my scenario is a little more complex. Briefly: I'd like to load a javascript library in context A, pass it into context B as a member and use it there as an "injected" js library. I'm trying to load "whatever" javascript library in my own js code that get embedded into java:
myLib.js - simple js library, however this could be any lib from a CDN or locally held
(function () {
'use strict';
function MyLib() {
var factory = {};
factory.calcPlus10 = function(inputValue) {
return inputValue + 10;
}
return factory;
}
})
myGraalVmJsLib.js - my actual js code that gets embedded into Java and thats supposed to use myLib library
({
functionUseMyLib: function() {
console.log(myLib);
}
})
GraalVmTest - java snippet thats trying to embed myGraalVmJsLib.js
public class GraalVmTest {
public static void main(String[] args) throws IOException, URISyntaxException {
Context contextLib = Context.create();
URL resource = Thread.currentThread().getContextClassLoader().getResource("./myLib.js");
Value jsLib = contextLib.eval("js", new String(Files.readAllBytes(Paths.get(resource.toURI()))));
System.out.println("canExecute: "+jsLib.canExecute());
Context contextEmbed = Context.create();
URL myResource = Thread.currentThread().getContextClassLoader().getResource("./myGraalVmJsLib.js");
contextEmbed.getBindings("js").putMember("myLib", jsLib );
Value result = contextEmbed.eval("js", new String(Files.readAllBytes(Paths.get(myResource.toURI()))));
result.getMember("functionUseMyLib").executeVoid();
}
}
Running this will produce the following outputs
canExecute: true
Exception in thread "main" java.lang.IllegalArgumentException: The value 'DynamicObject<JSFunction>@4550bb58' cannot be passed from one context to another. The current context is 0x4ec4f3a0 and the argument value originates from context 0x223191a6.
at com.oracle.truffle.polyglot.PolyglotLanguageContext.migrateValue(PolyglotLanguageContext.java:696)
at com.oracle.truffle.polyglot.PolyglotLanguageContext.toGuestValue(PolyglotLanguageContext.java:654)
at com.oracle.truffle.polyglot.PolyglotLanguageContext$ToGuestValueNode.apply(PolyglotLanguageContext.java:543)
at com.oracle.truffle.polyglot.PolyglotValue$InteropCodeCache$PutMemberNode.executeImpl(PolyglotValue.java:1092)
at com.oracle.truffle.polyglot.HostRootNode.execute(HostRootNode.java:94)
at com.oracle.truffle.api.impl.DefaultCallTarget.call(DefaultCallTarget.java:98)
at com.oracle.truffle.api.impl.TVMCI.callProfiled(TVMCI.java:263)
at com.oracle.truffle.api.impl.Accessor.callProfiled(Accessor.java:724)
at com.oracle.truffle.polyglot.VMAccessor.callProfiled(VMAccessor.java:91)
at com.oracle.truffle.polyglot.PolyglotValue$InteropValue.putMember(PolyglotValue.java:2466)
at org.graalvm.polyglot.Value.putMember(Value.java:286)
at GraalVmTest.main(GraalVmTest.java:27)
Caused by: Attached Guest Language Frames (1)
Using whatever JS library in my embedded code is a blocker for our project. Is there any way I can achieve this besides the approach I've tried here ? @chumer @wirthi
I think the current supported approach for js code, rather than data is to use the code caching and just load it into every context, from my experience that generally works? Do you dont need to share your common function you just share the Source.
Obviously this wouldn’t work if you wanted the common function to be stateful or mutable in some way such that all uses of the function were updated / changed at the same time, thus the issue 👍🏼
On 23 Jun 2019, 7:35 AM +1000, thomasreinecke notifications@github.com, wrote:
gents, I'm having the same problem like originally describes as 2), however my scenario is a little more complex. Briefly: I'd like to load a javascript library in context A, pass it into context B as a member and use it there as an "injected" js library. I'm trying to load "whatever" javascript library in my own js code that get embedded into java:
myLib.js - simple js library
(function () {
'use strict';
function MyLib() {
var factory = {};
factory.calcPlus10 = function(inputValue) {
return inputValue + 10;
}
return factory;
}
})
myGraalVmJsLib.js - my actual js code that gets embedded into Java and thats supposed to use the other js library
({
functionUseMyLib: function() {
console.log(myLib);
}
})
GraalVmTest - java snippet thats trying to embed myGraalVmJsLib.js
public class GraalVmTest {
public static void main(String[] args) throws IOException, URISyntaxException {
Context contextLib = Context.create();
URL resource = Thread.currentThread().getContextClassLoader().getResource("./myLib.js");
Value jsLib = contextLib.eval("js", new String(Files.readAllBytes(Paths.get(resource.toURI()))));
System.out.println("canExecute: "+jsLib.canExecute());Context contextEmbed = Context.create(); URL myResource = Thread.currentThread().getContextClassLoader().getResource("./myGraalVmJsLib.js"); contextEmbed.getBindings("js").putMember("myLib", jsLib ); Value result = contextEmbed.eval("js", new String(Files.readAllBytes(Paths.get(myResource.toURI())))); result.getMember("functionUseMyLib").executeVoid();}
}
Running this will produce the following outputs
canExecute: true
Exception in thread "main" java.lang.IllegalArgumentException: The value 'DynamicObject@4550bb58' cannot be passed from one context to another. The current context is 0x4ec4f3a0 and the argument value originates from context 0x223191a6.
at com.oracle.truffle.polyglot.PolyglotLanguageContext.migrateValue(PolyglotLanguageContext.java:696)
at com.oracle.truffle.polyglot.PolyglotLanguageContext.toGuestValue(PolyglotLanguageContext.java:654)
at com.oracle.truffle.polyglot.PolyglotLanguageContext$ToGuestValueNode.apply(PolyglotLanguageContext.java:543)
at com.oracle.truffle.polyglot.PolyglotValue$InteropCodeCache$PutMemberNode.executeImpl(PolyglotValue.java:1092)
at com.oracle.truffle.polyglot.HostRootNode.execute(HostRootNode.java:94)
at com.oracle.truffle.api.impl.DefaultCallTarget.call(DefaultCallTarget.java:98)
at com.oracle.truffle.api.impl.TVMCI.callProfiled(TVMCI.java:263)
at com.oracle.truffle.api.impl.Accessor.callProfiled(Accessor.java:724)
at com.oracle.truffle.polyglot.VMAccessor.callProfiled(VMAccessor.java:91)
at com.oracle.truffle.polyglot.PolyglotValue$InteropValue.putMember(PolyglotValue.java:2466)
at org.graalvm.polyglot.Value.putMember(Value.java:286)
at GraalVmTest.main(GraalVmTest.java:27)
Caused by: Attached Guest Language Frames (1)
Using whatever JS library in my embedded code is a blocker for our project. Is there any way I can achieve this besides the approach I've tried here ? @chumer @wirthi
—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub, or mute the thread.
@nhoughto I dont need stateful or mutable, thats fine. When you mention _"code caching and just load it into every context"_, can you please make an example ? Thx
As per graal docs
https://www.graalvm.org/docs/reference-manual/embed/#code-caching-across-multiple-contexts
Basically construct a Source object that is marked cached true, then eval that into context. Should reuse prepared objects and save cycles 👍🏼
On 23 Jun 2019, 6:24 PM +1000, thomasreinecke notifications@github.com, wrote:
@nhoughto I dont need stateful or mutable, thats fine. When you mention "code caching and just load it into every context", can you please make an example ? Thx
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
great stuff, that worked. thank you @nhoughto
Does 19.1.0 fix this? Seems to be a possibly related reference..
Enabled code sharing across threads using ContextPolicy.SHARED
🤞🏼
@nhoughto that change means that code can now be cached across multiple parallel contexts [1]. We will improve the changelog to make this better understandable.
Sharing of non-primitive JS values across contexts is still not supported.
[1] https://www.graalvm.org/docs/reference-manual/embed/#code-caching-across-multiple-contexts
😞
Maybe soon? 🤞🏼 On the roadmap?
@nhoughto can't you just reuse one context in your use-case? looks like there is just one context available at a time.
We have tracked this, but it is currently not assigned to a planned version. So not on the roadmap atm.
So I need either this or https://github.com/graalvm/graaljs/issues/146
Either reuse a single context and be able to clear it / reset it OR be able to share a value across contexts.. currently my use case isn’t supported.
My use case is a 10MB json object in context, contexts come and go but json object remains. Copying giant object across contexts is prohibitively expensive, and can’t clear and reuse context because of linked issue. Stuck between two issues! 😬
FYI with the above workaround (using cached sources), that even though the source is cached it appears the instance of it isn't. So there is some cycles saved in parsing and turning arbitrary JS into Graal objects but each of the instances of them will be separate.
Imagine a library like Faker.js with significant amounts of reference data rather than pure functions, each instance of the Faker library in each context creates its own copy of the reference data. So in an extreme example instead of 1 re-used Faker instance by 1000 Contexts, we have 1000 instances used by 1000 contexts one each. Minified Faker source is ~1mb, so in-memory structure are likely to be north of ~2mb. In our 1000 context example, this means using ~2gb+ of heap just for one library.
@chumer is that right?
We're also running into this limitation. Our use-case it a server state stored in a JS object which is expensive to recompute as well as to serialize/deserialize. So it would make sense to be able to reuse it across contexts. For now we will be able to work around by managing one context per server state. But ideally we would just have a pool of contexts sharing the same engine and we can use the object with any of them, regardless of which context it created the object initially.
Tracking internally as GR-20912
Hi,
Any news or forecasts of whether 1 or 2 will be supported in the nearest future?
We develop a tool, which runs transformations on (potentially big) pieces of data. Each transformation step now uses a fresh Context, but the data from the previous step must somehow be available to it. Currently we copy the data via JSON, but this already created memory/performance issues. Would be very nice to have a possibility to clear context/reuse values across multiple contexts.
Adding on to the request for this to be fixed. Our project also relies on passing objects between contexts and serializing/deserializing adds other issues.
+1 for support for this. We've sorted out all of our issues of migrating from nashorn besides this issue. Without this being complete we can't migrate to graaljs. Thank you to all the people who work on graal.
+1 - I just spent 2 very frustrating weeks trying to solve this - just when I thought the approach here was working, I hit another dead end:
for the benefit of everyone here, it was recently announced that Nashorn will be revived as a stand-alone project and a JDK 15 compatible dependency has just been released: https://twitter.com/ptrthomas/status/1332547404923432961
Most helpful comment
We are looking to migrate off Nashorn onto GraalJS, being able to share objects across contexts as described is a showstopper for us, but Nashorn still works (however unloved it is with all its problems).
We would love to get onto Graal soon tho, lots of great stuff on offer =)