diff --git a/packages/@jsii/java-runtime/pom.xml.t.js b/packages/@jsii/java-runtime/pom.xml.t.js
index ae0381d875..13b82e58c9 100644
--- a/packages/@jsii/java-runtime/pom.xml.t.js
+++ b/packages/@jsii/java-runtime/pom.xml.t.js
@@ -65,6 +65,7 @@ process.stdout.write(`
[13.0.0,20.0-a0)
[5.7.0,5.8-a0)
[3.5.13,4.0-a0)
+ [1.12,2.0-a0)
@@ -127,6 +128,13 @@ process.stdout.write(`
javax.annotation-api
\${javax-annotations.version}
+
+
+
+ org.zeroturnaround
+ zt-exec
+ \${zt-exec.version}
+
diff --git a/packages/@jsii/java-runtime/project/src/main/java/software/amazon/jsii/JsiiRuntime.java b/packages/@jsii/java-runtime/project/src/main/java/software/amazon/jsii/JsiiRuntime.java
index b079d11281..3db9444423 100644
--- a/packages/@jsii/java-runtime/project/src/main/java/software/amazon/jsii/JsiiRuntime.java
+++ b/packages/@jsii/java-runtime/project/src/main/java/software/amazon/jsii/JsiiRuntime.java
@@ -2,10 +2,12 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jetbrains.annotations.Nullable;
+import org.zeroturnaround.exec.ProcessExecutor;
+import org.zeroturnaround.exec.StartedProcess;
+import org.zeroturnaround.exec.stream.LogOutputStream;
import software.amazon.jsii.api.Callback;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
@@ -13,7 +15,12 @@
import java.io.*;
import java.lang.reflect.InvocationTargetException;
+import java.nio.channels.Channels;
+import java.nio.channels.Pipe;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import static software.amazon.jsii.JsiiVersion.JSII_RUNTIME_VERSION;
@@ -41,7 +48,7 @@ public final class JsiiRuntime {
/**
* The child procesds.
*/
- private Process childProcess;
+ private StartedProcess childProcess;
/**
* Child's standard output.
@@ -51,7 +58,7 @@ public final class JsiiRuntime {
/**
* Child's standard input.
*/
- private BufferedWriter stdin;
+ private Writer stdin;
/**
* Handler for synchronous callbacks. Must be set using setCallbackHandler.
@@ -173,45 +180,53 @@ protected void finalize() throws Throwable {
}
synchronized void terminate() {
- try {
- // The jsii Kernel process exists after having received the "exit" message
- if (stdin != null) {
+ // The jsii Kernel process exists after having received the "exit" message
+ if (stdin != null) {
+ try {
stdin.write("{\"exit\":0}\n");
stdin.close();
+ } catch (final IOException ioe) {
+ // Ignore - the stream might have already been closed, if the child exited already.
+ } finally {
stdin = null;
}
+ }
- if (childProcess != null) {
- // Wait for the child process to complete
- try {
- // Giving the process up to 5 seconds to clean up and exit
- if (!childProcess.waitFor(5, TimeUnit.SECONDS)) {
- // If it's still not done, forcibly terminate it at this point.
- childProcess.destroy();
- }
- } catch (final InterruptedException ie) {
- throw new RuntimeException(ie);
+ if (childProcess != null) {
+ // Wait for the child process to complete
+ try {
+ // Giving the process up to 5 seconds to clean up and exit
+ if (!childProcess.getProcess().waitFor(5, TimeUnit.SECONDS)) {
+ // If it's still not done, forcibly terminate it at this point.
+ childProcess.getProcess().destroyForcibly();
}
+ } catch (final InterruptedException ie) {
+ throw new RuntimeException(ie);
+ } finally {
childProcess = null;
}
+ }
- // Cleaning up stdout (ensuring buffers are flushed, etc...)
- if (stdout != null) {
+ // Cleaning up stdout (ensuring buffers are flushed, etc...)
+ if (stdout != null) {
+ try {
stdout.close();
+ } catch (final IOException ioe) {
+ // Ignore - the stream might have already been closed.
+ } finally {
stdout = null;
}
+ }
- // We shut down already, no need for the shutdown hook anymore
- if (this.shutdownHook != null) {
- try {
- Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
- } catch (final IllegalStateException ise) {
- // VM Shutdown is in progress, removal is now impossible (and unnecessary)
- }
+ // We shut down already, no need for the shutdown hook anymore
+ if (this.shutdownHook != null) {
+ try {
+ Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
+ } catch (final IllegalStateException ise) {
+ // VM Shutdown is in progress, removal is now impossible (and unnecessary)
+ } finally {
this.shutdownHook = null;
}
- } catch (final IOException ioe) {
- throw new UncheckedIOException(ioe);
}
}
@@ -224,49 +239,44 @@ private synchronized void startRuntimeIfNeeded() {
}
// If JSII_DEBUG is set, enable traces.
- String jsiiDebug = System.getenv("JSII_DEBUG");
- boolean traceEnabled = jsiiDebug != null
+ final String jsiiDebug = System.getenv("JSII_DEBUG");
+ final boolean traceEnabled = jsiiDebug != null
&& !jsiiDebug.isEmpty()
&& !jsiiDebug.equalsIgnoreCase("false")
&& !jsiiDebug.equalsIgnoreCase("0");
// If JSII_RUNTIME is set, use it to find the jsii-server executable
// otherwise, we default to "jsii-runtime" from PATH.
- String jsiiRuntimeExecutable = System.getenv("JSII_RUNTIME");
- if (jsiiRuntimeExecutable == null) {
- jsiiRuntimeExecutable = BundledRuntime.extract(getClass());
- }
+ final String jsiiRuntimeEnv = System.getenv("JSII_RUNTIME");
+ final List jsiiRuntimeCommand = jsiiRuntimeEnv == null
+ ? Arrays.asList("node", BundledRuntime.extract(getClass()))
+ : Collections.singletonList(jsiiRuntimeEnv);
if (traceEnabled) {
- System.err.println("jsii-runtime: " + jsiiRuntimeExecutable);
+ System.err.println("jsii-runtime: " + String.join(" ", jsiiRuntimeCommand));
}
- ProcessBuilder pb = new ProcessBuilder("node", jsiiRuntimeExecutable)
- .redirectInput(ProcessBuilder.Redirect.PIPE)
- .redirectOutput(ProcessBuilder.Redirect.PIPE)
- .redirectError(ProcessBuilder.Redirect.PIPE);
+ try {
+ final Pipe stdin = Pipe.open();
+ this.stdin = Channels.newWriter(stdin.sink(), StandardCharsets.UTF_8.newEncoder(), -1);
- if (traceEnabled) {
- pb.environment().put("JSII_DEBUG", "1");
- }
+ final Pipe stdout = Pipe.open();
+ this.stdout = new BufferedReader(Channels.newReader(stdout.source(), StandardCharsets.UTF_8.newDecoder(), -1));
- pb.environment().put("JSII_AGENT", "Java/" + System.getProperty("java.version"));
+ final ProcessExecutor executor = new ProcessExecutor(jsiiRuntimeCommand)
+ .environment("JSII_AGENT", String.format("Java/%s", System.getProperty("java.version")))
+ .environment("JSII_DEBUG", jsiiDebug)
+ .redirectInput(Channels.newInputStream(stdin.source()))
+ .redirectOutput(Channels.newOutputStream(stdout.sink()))
+ .redirectError(new ErrorStreamSink());
- try {
- this.childProcess = pb.start();
- this.shutdownHook = new Thread(this::terminate, "Terminate jsii client");
- Runtime.getRuntime().addShutdownHook(this.shutdownHook);
- } catch (IOException e) {
- throw new JsiiException("Cannot find the 'jsii-runtime' executable (JSII_RUNTIME or PATH)", e);
+ this.childProcess = executor.start();
+ } catch (final IOException ioe) {
+ throw new UncheckedIOException(ioe);
}
- OutputStreamWriter stdinStream = new OutputStreamWriter(this.childProcess.getOutputStream(), StandardCharsets.UTF_8);
- InputStreamReader stdoutStream = new InputStreamReader(this.childProcess.getInputStream(), StandardCharsets.UTF_8);
-
- new ErrorStreamSink(this.childProcess.getErrorStream()).start();
-
- this.stdout = new BufferedReader(stdoutStream);
- this.stdin = new BufferedWriter(stdinStream);
+ this.shutdownHook = new Thread(this::terminate, "Terminate jsii client");
+ Runtime.getRuntime().addShutdownHook(this.shutdownHook);
handshake();
@@ -346,40 +356,22 @@ private static void notifyInspector(final JsonNode message, final MessageInspect
inspector.inspect(message, type);
}
- private static final class ErrorStreamSink extends Thread {
- private final InputStream inputStream;
-
- public ErrorStreamSink(final InputStream inputStream) {
- super("JsiiRuntime.ErrorStreamSink");
- // This is a daemon thread, shouldn't keep the VM alive.
- this.setDaemon(true);
+ private static final class ErrorStreamSink extends LogOutputStream {
+ private final ObjectMapper objectMapper = new ObjectMapper();
- this.inputStream = inputStream;
- }
-
- public void run() {
- try (final InputStreamReader inputStreamReader = new InputStreamReader(this.inputStream);
- final BufferedReader reader = new BufferedReader(inputStreamReader)) {
- String line;
- final ObjectMapper objectMapper = new ObjectMapper();
- while ((line = reader.readLine()) != null) {
- try {
- final JsonNode tree = objectMapper.readTree(line);
- final ConsoleOutput consoleOutput = objectMapper.treeToValue(tree, ConsoleOutput.class);
- if (consoleOutput.stderr != null) {
- System.err.write(consoleOutput.stderr, 0, consoleOutput.stderr.length);
- }
- if (consoleOutput.stdout != null) {
- System.out.write(consoleOutput.stdout, 0, consoleOutput.stdout.length);
- }
- } catch (final JsonParseException | JsonMappingException exception) {
- // If not JSON, then this goes straight to stderr without touches...
- System.err.println(line);
- }
+ public void processLine(final String line) {
+ try {
+ final JsonNode tree = objectMapper.readTree(line);
+ final ConsoleOutput consoleOutput = objectMapper.treeToValue(tree, ConsoleOutput.class);
+ if (consoleOutput.stderr != null) {
+ System.err.write(consoleOutput.stderr, 0, consoleOutput.stderr.length);
+ }
+ if (consoleOutput.stdout != null) {
+ System.out.write(consoleOutput.stdout, 0, consoleOutput.stdout.length);
}
- } catch (final IOException error) {
- System.err.printf("I/O Error reading jsii Kernel's STDERR: %s%n", error);
- throw new UncheckedIOException(error);
+ } catch (final JsonProcessingException exception) {
+ // If not JSON, then this goes straight to stderr without touches...
+ System.err.println(line);
}
}
}