-
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
Sharing / Re-using values across Contexts #631
Comments
You are hitting two limitations here:
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?
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
|
To answer my own question, yes this is now fixed in |
Nope, totes wrong, just my initial test is slightly wrong, needs to use
stack:
|
Ah so the behaviour described in the recent release notes indicate that just primitive, host and proxy |
Hi, I just checked the advanced example I end up with not being able to access the properties of the
|
sorry for the delayed response. You cannot access the fields of the 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, |
Hi @wirthi |
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
myGraalVmJsLib.js - my actual js code that gets embedded into Java and thats supposed to use myLib library
GraalVmTest - java snippet thats trying to embed myGraalVmJsLib.js
Running this will produce the following outputs
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 ***@***.***>, 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 ***@***.***' 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 ***@***.***>, 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 🤞🏼 |
Any update on this? Is there any plan to fix this issue? Is there any workaround to share value across context? |
+1 sharing value across context should be supported for performance reason and memory optimization. |
Sorry for letting you wait for such a long time. Here is a workaround using proxies how you can make JSON like values be sharable across context. Note that the contexts both must not be closed in order for this to work: public static void main(String[] args) {
Engine engine = Engine.create();
try(Context context1 = Context.newBuilder("js").engine(engine).allowAllAccess(true).build();
Context context2 = Context.newBuilder("js").engine(engine).allowAllAccess(true).build()) {
Value value1 = context1.eval("js", "({data:42, array:[], object:{data:42},})");
Value result = context2.eval("js", "e => {return e.object.data;}").execute(createSharable(value1));
System.out.println(result); // prints 42
}
}
private static Object createSharable(Value v) {
if (v.isBoolean()) {
return v.asBoolean();
} else if (v.isNumber()) {
return v.as(Number.class);
} else if (v.isString()) {
return v.asString();
} else if (v.isHostObject()) {
return v.asHostObject();
} else if (v.isProxyObject()) {
return v.asProxyObject();
} else if (v.hasArrayElements()) {
return new ProxyArray() {
@Override
public void set(long index, Value value) {
v.setArrayElement(index, createSharable(value));
}
@Override
public long getSize() {
return v.getArraySize();
}
@Override
public Object get(long index) {
return createSharable(v.getArrayElement(index));
}
@Override
public boolean remove(long index) {
return v.removeArrayElement(index);
}
};
} else if (v.hasMembers()) {
return new ProxyObject() {
@Override
public void putMember(String key, Value value) {
v.putMember(key, createSharable(value));
}
@Override
public boolean hasMember(String key) {
return v.hasMember(key);
}
@Override
public Object getMemberKeys() {
return v.getMemberKeys().toArray(new String[0]);
}
@Override
public Object getMember(String key) {
return createSharable(v.getMember(key));
}
@Override
public boolean removeMember(String key) {
return v.removeMember(key);
}
};
} else {
throw new UnsupportedOperationException("Unsupported value");
}
} Also note that both contexts must not be used from multiple threads at the same time. Otherwise you will get another error. |
Does this maintain a single instance where updates are visible across both (many) contexts? That was the problem I had with Proxies was that you end up passing by value/cloning and updates aren't visible across all contexts undermining the whole thing. |
@nhoughto In this workaround, objects and lists are not cloned. Proxies are designed to enable exactly that, passing objects without needing to clone. Note that in the example all return values are wrapped in proxies again. So there might be an issue with object identity, if you are relying on that. The proper implementation will do something similar automatically without the identity problem. |
ok great, well i think that solves my problem. Extra internet points for you for solving a vintage issue 👍 |
I think we should keep this open until we have a better solution than this workaround. There are other use-cases, like using the polyglot API from guest languages directly, where this workaround does not work so easily. |
@chumer The workaround looks good for a lot of use cases, nice job! I am running into an issue with trying to run a function in javascript from a different context. The exact message I am getting is private static Context createContext() {
Context context = Context
.newBuilder()
.option("js.nashorn-compat", "true")
.allowExperimentalOptions(true)
.allowIO(true)
.allowCreateThread(true)
.allowAllAccess(true)
.build();
return context;
}
Context context = GraalTest.createContext();
Value printMe = context.eval("js", "function() {console.log('Hello World!');}");
Context newContext = GraalTest.createContext();
Object wrappedPrint = createSharable(printMe);
newContext.getBindings("js").putMember("printIt", wrappedPrint);
newContext.eval("js", "printIt()"); |
@virustotalop yes that is expected the workaround only covers members and arrays, no functions. You can extend the workaround arbitrarily to cover more types. The real implementation will of course cover all types automatically. Here is the updated example: public static void main(String[] args) {
Context context = createContext();
Value printMe = context.eval("js", "function() {console.log('Hello World!');}");
Context newContext = createContext();
Object wrappedPrint = createSharable(printMe);
newContext.getBindings("js").putMember("printIt", wrappedPrint);
newContext.eval("js", "printIt()");
}
private static Object createSharable(Value v) {
if (v.isNull()) {
return null;
} else if (v.isBoolean()) {
return v.asBoolean();
} else if (v.isNumber()) {
return v.as(Number.class);
} else if (v.isString()) {
return v.asString();
} else if (v.isHostObject()) {
return v.asHostObject();
} else if (v.isProxyObject()) {
return v.asProxyObject();
} else if (v.hasArrayElements()) {
return new ProxyArray() {
@Override
public void set(long index, Value value) {
v.setArrayElement(index, createSharable(value));
}
@Override
public long getSize() {
return v.getArraySize();
}
@Override
public Object get(long index) {
return createSharable(v.getArrayElement(index));
}
@Override
public boolean remove(long index) {
return v.removeArrayElement(index);
}
};
} else if (v.hasMembers()) {
if (v.canExecute()) {
if (v.canInstantiate()) {
// js functions have members, can be executed and are instantiable
return new SharableMembersAndInstantiable(v);
} else {
return new SharableMembersAndExecutable(v);
}
} else {
return new SharableMembers(v);
}
} else {
throw new AssertionError("Uncovered shared value. ");
}
}
static class SharableMembers implements ProxyObject {
final Value v;
SharableMembers(Value v) {
this.v = v;
}
@Override
public void putMember(String key, Value value) {
v.putMember(key, createSharable(value));
}
@Override
public boolean hasMember(String key) {
return v.hasMember(key);
}
@Override
public Object getMemberKeys() {
return v.getMemberKeys().toArray(new String[0]);
}
@Override
public Object getMember(String key) {
return createSharable(v.getMember(key));
}
@Override
public boolean removeMember(String key) {
return v.removeMember(key);
}
}
static class SharableMembersAndExecutable extends SharableMembers implements ProxyExecutable {
SharableMembersAndExecutable(Value v) {
super(v);
}
@Override
public Object execute(Value... arguments) {
Object[] newArgs = new Value[arguments.length];
for (int i = 0; i < newArgs.length; i++) {
newArgs[i] = createSharable(arguments[i]);
}
return createSharable(v.execute(newArgs));
}
}
static class SharableMembersAndInstantiable extends SharableMembersAndExecutable implements ProxyInstantiable {
SharableMembersAndInstantiable(Value v) {
super(v);
}
@Override
public Object newInstance(Value... arguments) {
Object[] newArgs = new Value[arguments.length];
for (int i = 0; i < newArgs.length; i++) {
newArgs[i] = createSharable(arguments[i]);
}
return createSharable(v.execute(newArgs));
}
} |
B"H BTW, whith Rhino i didnt need to declare the binding scope, as Rhino automatically go to global scope when not find any. EDIT: I tried same only with Engine scope. and it didnt get this issue. it does get another issue cause need to enable the js to java connection, and this can be done when declaring the context. via jsr223 i dont see how i can control the context variables. |
how to share javascript utility code in different context? |
Proper support for value sharing was merged and will land in 21.3. Here is an example how to embed contexts inside guest languages: https://github.com/oracle/graal/blob/master/docs/reference-manual/embedding/embed-languages.md#embed-guest-languages-in-guest-languages There is some Javadoc on the details: Also with the capability to disable it again. There should be a CE build arriving soon where you can try this: |
@chumer - Will it solve the issue : Value form context1 - thread1 will be shared in context2 - thread2 if conext1 is closed?? |
@pramodanarase as long as an object from another context is still referenced it might consume the memory of the original context even if it is closed. We are not eagerly breaking references currently, that would be pretty inefficient in the current architecture. You should avoid references between contexts when other contexts are closed anyway, as any access to such objects can fail. |
@chumer Thank you that helped a lot. I did notice one issue that I'm not sure whether it will affect the official implementation but the |
I'm testing the JS language on GraalVM and getting an exception when trying to run public class App2
{
static String JS_CODE = """
function a(){
print("Hello world");
}
function b(){
a();
}
b();
""";
public static void main(String[] args) {
try (Context context = Context.newBuilder("js").allowAllAccess(true)
.allowHostClassLookup(s -> true)
.allowHostAccess(HostAccess.ALL)
.build();) {
Value value = context.eval("js", JS_CODE);
System.err.println( value.canExecute());
value.execute();
}
} and I got this exception: |
I believe you should open a new ticket for questions like that (or ask on our Slack channel, see https://www.graalvm.org/slack-invitation/)
You cannot execute Or use something like Best, |
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.
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:
The text was updated successfully, but these errors were encountered: