Runtime Architecture
Generated Libraries
When using jsii-pacmak to generate libraries in different programming
languages, the Javascript code is bundled within the generated library, so
that it can be used at runtime. This is the reason why a node runtime
needs to be available in order to execute code that depends on jsii libraries.
The generated libraries have a dependency on a Runtime client library for the
language, which contains the necessary logic to start a child node process
with the jsii-runtime. The jsii-runtime manages JSON-based inter-process
communication over its STDIN, STDOUT and STDERR, and manages a
@jsii/kernel instance that acts as a container for the Javascript code
that backs the jsii libraries.
Architecture Overview
A representation of the execution environment of an application using jsii libraries from a different language follows:
┌─────────────────────────┐ ┌────────────┬────┬────┬────┐
│ │ │ │ │ │ │
│ Host Application │ │@jsii/kernel│LibA│LibB│... │
│ │ │ │ │ │ │
│ ┌──────────────────┤ ├────────────┴────┴────┴────┤
│ │ │ │ │
│ │Generated Bindings│ │ @jsii/runtime │
│ │ │ │ │
│ ├──────────────────┤ Requests ├──────┬────────────────────┤
│ │ ├───────────────▶STDIN │ │
│ │Host jsii Runtime │ Responses ├──────┤ │
│ │ Library ◀───────────────┤STDOUT│ │
│ │ │ Console ├──────┤ node │
│ │ ◀───────────────┤STDERR│ │
├──────┴──────────────────┤ ├──────┘ │
│ Host Runtime │ │ (Child Process) │
│ (JVM, .NET Core, ...) │ │ │
│ │ │ │
├─────────────────────────┴───────────────┴───────────────────────────┤
│ │
│ Operating System │
│ │
└─────────────────────────────────────────────────────────────────────┘
Communication Protocol
As shown in the architecture overview diagram, the
@jsii/runtime process receives requests via its STDIN stream, sends
responses via its STDOUT stream, and sends console output through the STDERR
stream.
All those messages are sent in JSON-encoded objects. On STDIN and STDOUT,
the request-response protocol is defined by the kernel api specification. On
STDERR messages are encoded in the following way:
{ "stderr": "<base64-encoded data>" }when the console data is to be written on the Host Application'sSTDERRstream.{ "stdout": "<base64-encoded data>" }when the console data is to be written on the Host Application'sSTDOUTstream.- Any data that is not valid JSON, or that does not match either of the formats
described above must be written as-is on the Host Application's
STDERRstream.
In order to allow the hosted original JavaScript libraries to naturally
interact with process.stdout, process.stderr and all other APIs that make
use of those streams (such as console.log and console.error), the
@jsii/runtime process does in fact spawn a second node process to allow
intercepting the console data to properly encode it. Below is a diagram
describing the process arrangement that achieves this:
┌────────────┬────┬────┬────┐
│ │ │ │ │
│@jsii/kernel│LibA│LibB│... │
│ │ │ │ │
┌─────────────────────────┐ ├────────────┴────┴────┴────┤
│ │ │ │
│ @jsii/runtime Wrapper │ │ @jsii/runtime Core │
│ │ │ │
├──────┬──────────────────┤ ├──────┬────────────────────┤
│STDIN │ │ X──────▶STDIN │ │
├──────┤ │ Console ├──────┤ │
│STDOUT│ ◀───────────────┤STDOUT│ │
├──────┤ │ Console ├──────┤ node │
│STDERR│ node ◀───────────────┤STDERR│ │
├──────┘ │ JSON ├──────┤ (Child Process) │
│ ◀───────────────▶ FD#3 │ │
│ │ ├──────┘ │
│ │ │ │
├─────────────────────────┴───────────────┴───────────────────────────┤
│ │
│ Operating System │
│ │
└─────────────────────────────────────────────────────────────────────┘
Missing Feature
As shown on the diagram above, there is nothing connected to the Core
process' FD#0 (STDIN). This feature will be added in the future, but
currently this means jsii libraries have no way of accepting input through
STDIN.
The Wrapper process manages the Core process such that:
Info
It would be possible to use a single node process (the @jsii/runtime
Core process) for any platform that supposed spawning child processes with
additional open file descriptors. This is for example not possible in
Java and C#, which is why this dual-process contraption was devised.
In such cases, the Host Application would spawn the Core process and directly operate on the file descriptors as described below.
- Any requests received from the Host Application through the Wrapper's
STDINstream is forwarded to the Core process'FD#3. - Any response written to the Core's
FD#3stream is forwarded to the Host Application though the Wrapper'sSTDOUT. - Any data sent to the Core's
STDERRis base64-encoded and wrapped in a JSON object with the"stderr"key, then forwarded to the Host Application through the Wrapper'sSTDERR - Any data sent to the Core's
STDOUTis base64-encoded and wrapped in a JSON object with the"stdout"key, then forwarded to the Host Application through the Wrapper'sSTDERR
Danger
As with any file descriptor besides FD#0 (STDIN), FD#1 (STDOUT) and
FD#2 (STDERR) that was not opened by the application, JavaScript
libraries loaded in the @jsii/kernel instance are not allowed to interact
directly with file descriptor FD#3.
Initialization Process
The initialization workflow can be described as:
- The host (Java, .NET, ...) application starts on its own runtime (JVM, .NET Runtime, ...)
- When the host code encounters a jsii entity for the first time (creating
an instance of a jsii type, loading a static constant, ...), the runtime
client library creates a child
nodeprocess, and loads thejsii-runtimelibrary (specified by theJSII_RUNTIMEenvironment variable, or the version that is bundled in the runtime client library) - The runtime client library interacts with the child
nodeprocess by exchanging JSON-encoded messages through thenodeprocess' STDIN andSTDOUT. It maintains a thread (or equivalent) that decodes messages from the child'sSTDERRstream, and forwards the decoded data to it's host process'STDERRandSTDOUTas needed. - The runtime client library automatically loads the Javascript modules
bundled within the generated bindings (and their dependencies, bundled in
other generated bindings) into the
nodeprocess when needed. - Calls into the Generated bindings are encoded into JSON requests and sent
to the child
nodeprocess, which will execute the corresponding Javascript code, then responds back. - Upon exiting, the host process closes the communication channels with the
child
nodeprocess, causing it to exit.