Fix race condition on command output streams copy
And add more preconditions to error out if the race condition happens.
This should fix the CI errors on linux.
diff --git a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/Command.java b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/Command.java
index 594fa39..6217ae0 100644
--- a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/Command.java
+++ b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/Command.java
@@ -64,11 +64,19 @@
executed = true;
ProcessBuilder builder = new ProcessBuilder(args);
builder.directory(directory);
+ builder.redirectOutput(ProcessBuilder.Redirect.PIPE);
+ builder.redirectError(ProcessBuilder.Redirect.PIPE);
Process process = builder.start();
- copyStream(process.getErrorStream(), stderr);
+ Thread err = copyStream(process.getErrorStream(), stderr);
// seriously? That's stdout, why is it called getInputStream???
- copyStream(process.getInputStream(), stdout);
+ Thread out = copyStream(process.getInputStream(), stdout);
int r = process.waitFor();
+ if (err != null) {
+ err.join();
+ }
+ if (out != null) {
+ out.join();
+ }
synchronized (stderr) {
stderr.close();
}
@@ -78,24 +86,39 @@
return r;
}
- // Launch a thread to copy all data from inputStream to outputStream
- private static void copyStream(InputStream inputStream, OutputStream outputStream) {
- if (outputStream != null) new Thread(new Runnable() {
- @Override
- public void run() {
- byte[] buffer = new byte[4096];
- int read;
- try {
- while ((read = inputStream.read(buffer)) > 0) {
- synchronized (outputStream) {
- outputStream.write(buffer, 0, read);
- }
+ private static class CopyStreamRunnable implements Runnable {
+ private InputStream inputStream;
+ private OutputStream outputStream;
+
+ CopyStreamRunnable(InputStream inputStream, OutputStream outputStream) {
+ this.inputStream = inputStream;
+ this.outputStream = outputStream;
+ }
+
+ @Override
+ public void run() {
+ byte[] buffer = new byte[4096];
+ int read;
+ try {
+ while ((read = inputStream.read(buffer)) > 0) {
+ synchronized (outputStream) {
+ outputStream.write(buffer, 0, read);
}
- } catch (IOException ex) {
- // we simply terminate the thread on exceptions
}
+ } catch (IOException ex) {
+ // we simply terminate the thread on exceptions
}
- }).start();
+ }
+ }
+
+ // Launch a thread to copy all data from inputStream to outputStream
+ private static Thread copyStream(InputStream inputStream, OutputStream outputStream) {
+ if (outputStream != null) {
+ Thread t = new Thread(new CopyStreamRunnable(inputStream, outputStream), "CopyStream");
+ t.start();
+ return t;
+ }
+ return null;
}
/**
diff --git a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/SelectOutputStream.java b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/SelectOutputStream.java
index 8514d5e..2f7c56e 100644
--- a/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/SelectOutputStream.java
+++ b/com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command/SelectOutputStream.java
@@ -58,6 +58,7 @@
@Override
public void write(int b) throws IOException {
+ Preconditions.checkState(!closed, "Attempted to write on a closed stream");
byte b0 = (byte) b;
if (b0 == '\n') {
select(true);
diff --git a/javatests/com/google/devtools/bazel/e4b/command/CommandTest.java b/javatests/com/google/devtools/bazel/e4b/command/CommandTest.java
index 247933c..426ac34 100644
--- a/javatests/com/google/devtools/bazel/e4b/command/CommandTest.java
+++ b/javatests/com/google/devtools/bazel/e4b/command/CommandTest.java
@@ -85,7 +85,7 @@
assertThat(cmd.run()).isEqualTo(0);
String stdoutStr = new String(stdout.toByteArray(), StandardCharsets.UTF_8).trim();
String stderrStr = new String(stderr.toByteArray(), StandardCharsets.UTF_8).trim();
-
+
assertThat(stdoutStr).isEqualTo("a");
assertThat(stderrStr).isEqualTo("b");
assertThat(cmd.getSelectedErrorLines()).containsExactly("a");
@@ -140,6 +140,7 @@
builder.addArguments("pwd");
Command cmd = builder.build();
assertThat(cmd.run()).isEqualTo(0);
+ assertThat(cmd.getSelectedErrorLines()).isEmpty();
assertThat(cmd.getSelectedOutputLines()).containsExactly(folder.getRoot().getCanonicalPath());
}
}