oracle/graal

Sharing / Re-using values across Contexts

nhoughto opened this issue Β· 48 comments

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

@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

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 oracle/graaljs#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

It is very important to share binding value across context. if we take example 1MB JSON data evaluation repeatedly with new context , it will hurt the performance.
There should be way to share data across context.

Another vote to share binding across contexts. Finding that when I have functions jumping between Java -> JavaScript -> back to Java Graal passes them as Polyglots which are internal classess of the truffle API so even trying the workarounds of obtaining the Source, as described in this thread, is incredibly complex (edit: Value.asValue(object) does the trick) This seems like imposing a "limitation"/design of a language (JS) into an a framework that abstract itself from a specific language?

Any update on this? Is there any plan to fix this issue? Is there any workaround to share value across context?

@wirthi @chumer Any update? Any workaround ? Please share..

+1 sharing value across context should be supported for performance reason and memory optimization.
This way, you can have a global context and reusable objects can be placed in the global context and its value shared across VMs.
Rhino use to have such a shared scope feature which is very useful to avoid overhead of multithread having to keep the same data separately in each context.

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 failed due to: Message not supported. Any idea how I can accomplish that with creating a shareable context? It looks like the function is a MetaObject and I tried to check for it and create a proxy but that didn't work so I scrapped it but now I'm stumped where to go from here. Any suggestions on what to try to do about this particular issue? Sample code below.

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
Hi, I got same exception but in regular jsr223 implementation (while first try to merge from Rhino to Graal):
e.g.
I have a js prototype that already compiled into global scope. now I want to delegate to js via java interface which similar to the js.
so i am using the getInterface engine implementation to do the connectivity between my already compiled context and the Java interface i want to use.
Invocable invocable = (Invocable) engine; Observer observer = invocable .getInterface(engine.eval("Observer", engine.getBindings(ScriptContext.GLOBAL_SCOPE)), Observer.class);

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.
8d70605

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:
https://www.graalvm.org/sdk/javadoc/org/graalvm/polyglot/Context.Builder.html#allowValueSharing-boolean-

Also with the capability to disable it again.

There should be a CE build arriving soon where you can try this:
https://github.com/graalvm/graalvm-ce-dev-builds/releases/

@chumer - Will it solve the issue : Value form context1 - thread1 will be shared in context2 - thread2 if conext1 is closed??
Otherwise need to maintain all the open context which leads to high memory usage or leaks.

@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 Value arrays seemed to cause a ArrayStoreException like in oracle/graaljs#36. Once those are swapped out for generic Object arrays the issue seems to be fixed. I figured this would help anyone who is trying to run the workaround temporarily.

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:
Unsupported operation Value.execute(Object...) for 'undefined'(language: JavaScript, type: undefined). You can ensure that the operation is supported using Value.canExecute().
at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotEngineException.unsupported(PolyglotEngineException.java:147)
at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotValueDispatch.unsupported(PolyglotValueDispatch.java:1257)
at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotValueDispatch.executeUnsupported(PolyglotValueDispatch.java:595)
at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotValueDispatch$InteropValue$AbstractExecuteNode.executeShared(PolyglotValueDispatch.java:4277)
at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotValueDispatch$InteropValue$ExecuteNoArgsNode.executeImpl(PolyglotValueDispatch.java:4375)
at org.graalvm.truffle/com.oracle.truffle.polyglot.HostToGuestRootNode.execute(HostToGuestRootNode.java:124)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.executeRootNode(OptimizedCallTarget.java:709)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.profiledPERoot(OptimizedCallTarget.java:632)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.callBoundary(OptimizedCallTarget.java:565)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.doInvoke(OptimizedCallTarget.java:549)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.GraalRuntimeSupport.callProfiled(GraalRuntimeSupport.java:256)
at org.graalvm.truffle/com.oracle.truffle.polyglot.PolyglotValueDispatch$InteropValue.execute(PolyglotValueDispatch.java:2401)
at org.graalvm.sdk/org.graalvm.polyglot.Value.execute(Value.java:878)
at org.example.App2.main(App2.java:33)
Caused by: Attached Guest Language Frames (1)

Hi @abedinihamzeh

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/)

Unsupported operation Value.execute(Object...) for 'undefined' ...

You cannot execute undefined, which seems reasonable. Your JavaScript code returns the result of the call b(), which returns the result of the call of a(), which does not return anything, i.e., undefined. And undefined cannot be executed. Change the last line of your JavaScript code from b(); to b; and it might do exactly what you intend to.

Or use something like Value b = context.getBindings("js").getMember("b"); b.execute() on the Java side.

Best,
Christian