The basic model

GraalVM’s JavaScript runtime (also known as Graal.js) supports parallel execution via multiple threads in a simple yet powerful way, which we believe is convenient for a variety of embedding scenarios.

The model is based on the following three simple rules:

In a polyglot application, an arbitrary number of JS runtimes can be created, but they should be used by one thread at a time. Concurrent access to Java objects is allowed: any Java object can be accessed by any Java or JavaScript thread, concurrently. Concurrent access to JavaScript objects is not allowed: any JavaScript object cannot be accessed by more than one thread at a time.

GraalVM enforces these rules at runtime, therefore making it easier and safer to reason about parallel and concurrent execution in a polyglot application.

The main motivation for the model is that JavaScript code should reside as much as possible in the comfort zone where JavaScript execution is not subject to race conditions, and concurrency should be introduced selectively by exposing Java objects to JavaScript, where needed. On the contrary, Java developers should not be limited in what their multi-threaded application can do, as long as it does not try to create data races on JavaScript objects that have not been designed for concurrent access.

Embedding Graal.js in a Java multi-threaded application

GraalVM’s JavaScript runtime can be embedded in any Java application using the polyglot Context API. This is an example of a basic “hello world” embedding, where JavaScript is used to create some JSON data:

According to our simple rules, concurrent access to a shared JS object is not allowed in GraalVM. In fact, executing the following code:

will result in the following exception:

java.lang.IllegalStateException: Multi-threaded access requested by thread Thread[main,0,main] but is not allowed for language(s) js!

…

The same exception will be thrown if concurrent execution is attempted from JavaScript, using GraalVM’s Java interoperability:

as expected, in both examples GraalVM enforced rule #3: no concurrent access to JS objects can happen. This, however, does not mean that concurrent threads cannot execute JS code in parallel. In fact, what really matters is that concurrent threads do not access the same runtime concurrently. Intuitively, there are two alternative ways to achieve parallel execution without violating rule #3:

Using multiple, isolated, JS runtimes, Sharing a single JS runtime between threads, using proper Java synchronization to prevent concurrent access.

Depending on the embedding scenario, an approach might be more appropriate than the other one. Using multiple runtimes is straightforward:

As described in the GraalVM documentation, multiple contexts can share a same Engine. This way, the creation of new JavaScript runtimes can be performed efficiently, and GraalVM can apply cross-context optimizations (e.g., sharing compilation caches).

Sharing of a single context can also be implemented quite easily using Java synchronization:

Sharing Java objects between contexts

Our rule #2 states that GraalVM does not prevent concurrent access to Java objects from (parallel) JavaScript. The rationale is that Java objects are designed to allow for concurrent access anyway, as the behavior of concurrent accesses is ruled by the Java memory model.

A Java object can be shared between independent JS contexts via GraalVM’s polyglot bindings, in the following way:

Since counter is a Java AtomicInteger object, it can be accessed by multiple threads, concurrently, and no exception will be thrown.

Multi-threaded interop in Node.js

Node.js is a special case of a GraalVM embedding, where the JavaScript language runtime is not under control of the Java VM, but rather under the full control of the Node.js event loop. In this case, the creation of a new Context does not happen via the Context API, but happens implicitly together with the creation of a new Node.js event loop. Similarly, there is no direct way to create a new Java thread, and GraalVM must rely on the built-in threading support of Node.js.

Starting from version 10.x, Node.js has introduced a kind of multi-threaded execution in the form of Worker threads. Since version 1.0-RC11 GraalVM supports Node.js’ workers out of the box: nothing special is implemented in GraalVM to support them, except for the fact that a new JavaScript Context is created for every new worker. Node.js workers are a great way to offload CPU-intensive computation to another thread, without having to block the main Node.js event loop. Beyond “traditional” Node.js applications, Workers can be pretty convenient in polyglot applications, too. Here is an example of a Node.js application that uses Workers to perform a method call using GraalVM’s Java interoperability:

Sharing Java objects between workers

Like with Java applications and threads, sharing of Java objects between multiple workers is allowed in Node.js with GraalVM. This can be achieved using the standard built-in postMessage() API of Node.js’ workers. Here is an example of a parallel Node.js application that receives a Java random number generator from the main Node.js event loop, uses it to create a new Java object (an awt Point), which is then returned back to the main Node.js worker thread:

One of the nice advantages of being able to share Java objects between workers, is that Java objects can be used to achieve synchronization between the main Node.js event loop and Java code. For example, Java synchronization can be used to build a notification API to coordinate Java and the Node.js’ event loop. Here is a simple Node.js application that uses a (shared) Java concurrent queue to implement a notification API, which could be used to notify Node.js when something happens in Java space (e.g., a new request arrives, some long-running computation completes, etc.):

An implementation of an AwesomeClass could use the queue in the following way:

Quite obviously, this is a very naive implementation. More complex implementations could be developed and conveniently packed into npm modules, to be then re-used by other GraalVM applications.