oracle / graaljs

GraalJS – A high-performance, ECMAScript compliant, and embeddable JavaScript runtime for Java
https://www.graalvm.org/javascript/
Universal Permissive License v1.0
1.81k stars 190 forks source link

[feature request] expose Node event loop to Java code #321

Open larsrh opened 4 years ago

larsrh commented 4 years ago

I'm running some JS code through the Node binary that's shipped with Graal. This code calls to some async Java code. I use a similar trick like in the GraalJS tests to convert a Java CompletionStage to a JS Promise:

https://github.com/graalvm/graaljs/blob/278b71516f85fec61a5ca35353ab31dc637f31db/graal-js/src/com.oracle.truffle.js.test/src/com/oracle/truffle/js/test/interop/AsyncInteropTest.java#L211

Unfortunately, if the CompletionStage is completed on another thread, this fails because of concurrent access to JS objects.

The JSAgent class contains the top-level callback-loop for Node:

https://github.com/graalvm/graaljs/blob/vm-20.1.0/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSAgent.java

It would be gread if Java user code could dispatch callbacks on that loop. This would solve the concurrent access problem: I could register a handler on the CompletionStage that, instead of resolving the Promise directly (on the wrong thread), would schedule the resolution on the main Node thread.

eleinadani commented 4 years ago

Hi @larsrh, JSAgent is an internal implementation class, and we cannot expose it to user applications. In our concurrency model, the user application needs to ensure that no concurrent access on objects belonging to the same Context can happen. In this article we have a few examples showing how multiple threads can use promises concurrently

larsrh commented 4 years ago

Of course; I'm not arguing that JSAgent should be exposed just like that.

The article you linked isn't applicable in my scenario. The application is started through node, which means that the Context (and execution thread) is fully in control of the Node REPL. It is impossible to run any other code on that thread. Of course I could use synchronized or other things if I were to create a Context (or a pool) myself, but I don't.

larsrh commented 4 years ago

Addendum: You can imagine my feature request like a setImmediate equivalent that I can call from Java.

mikehearn commented 4 years ago

You can do that. Check out https://mikehearn.github.io/nodejvm/ or the articles in the GraalVM Medium blog. The linked program inverts NodeJS support so the JVM runs first, but it shows how to relay Java calls into the NodeJS thread with some transparency. The trick is to create a worker in the NodeJS world and then attach it to a Java LinkedBlockingQueue onto which you push closures. You can then schedule any Java closure to be executed in the NodeJS thread. It would of course be nice if the NodeJS GraalVM integration was more direct, but it is not too difficult to do this.

jpg0 commented 3 years ago

I'm not sure if this comment exactly reflects the desire of this issue (I'm happy to open a new one), however I think that it's important to disambiguate between: support for setTimeout (which implicitly requires an event loop), explicit introspection/management of the event loop, and support for other nodeJS APIs. These often seem to get mixed up and/or combined.

I would like to state that I believe that the first (support for setTimeout) should be part of the embedded JS runtime because it's part of every other JS runtime out there (including all the browsers). Lots of pure-JS things like Promises can be built on this.

The second thing (introspection and management of this loop) is something which could later be added, but I'm not sure it's expected so universally. (I don't personally need this.)

The last thing (general nodeJS APIs) is clearly a much larger endeavour here which should not be mixed up with the event loop which isn't node-specific afaics https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop.