Multi-threaded Java ←→JavaScript language interoperability in GraalVM

Daniele Bonetta
graalvm
Published in
5 min readFeb 11, 2019

JavaScript and Java are among today’s most popular programming languages. One of the greatest features of GraalVM is its extensive support for multi-language interoperability, which allows developers to combine languages, including Java and JavaScript, into powerful multi-language applications. Whether your Java code needs to be enriched with JavaScript functionalities, or you are looking for using a Java library in a Node.js application, GraalVM interoperability might be the perfect choice for your polyglot application.

One of the most crucial aspects in achieving a successful polyglot Java/JavaScript integration is multi-threading. In a polyglot world, using parallel threads can be quite tricky: while the Java language provides powerful built-in support for multi-threading, JavaScript was designed with a different share-nothing concurrency model. Such differences in each language’s approach to multi-threading make it challenging to implement polyglot applications that can benefit from parallel execution.

In this blog post, we are going to provide an overview of the multi-threading capabilities of GraalVM’s JavaScript runtime, showing how JavaScript can be used together with Java to build parallel, polyglot, applications.

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:

  1. In a polyglot application, an arbitrary number of JS runtimes can be created, but they should be used by one thread at a time.
  2. Concurrent access to Java objects is allowed: any Java object can be accessed by any Java or JavaScript thread, concurrently.
  3. 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:

  1. Using multiple, isolated, JS runtimes,
  2. 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 counteris a Java AtomicIntegerobject, 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 anAwesomeClass 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.

GraalVM’s JavaScript language runtime is designed to be embeddable and highly-flexible, allowing for it to be embedded in a wide variety of scenarios, from databases to Node.js and Java applications. Multi-threading support comes in the form of a simple share-nothing semantics for JavaScript, that can be extended with Java concurrency following these basic rules:

Graal.js concurrency rules

Several other multi-threaded embedding of GraalVM’s JavaScript runtime might exist (e.g., in Java applications as well as in Node.js ones), and we are excited to see how (and where!) JavaScript will be embedded using GraalVM in the future.

--

--

Daniele Bonetta
graalvm
Writer for

Researcher at Oracle Labs in the VM research group and member of the GraalVM project.