| // Copyright 2016 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.windows; | |
| import com.google.common.base.Throwables; | |
| import com.google.devtools.build.lib.shell.Subprocess; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.OutputStream; | |
| import java.util.concurrent.ExecutionException; | |
| import java.util.concurrent.ExecutorService; | |
| import java.util.concurrent.Executors; | |
| import java.util.concurrent.Future; | |
| import java.util.concurrent.ThreadFactory; | |
| import java.util.concurrent.atomic.AtomicInteger; | |
| /** A Windows subprocess backed by a native object. */ | |
| public class WindowsSubprocess implements Subprocess { | |
| // For debugging purposes. | |
| private String commandLine; | |
| private static enum WaitResult { | |
| SUCCESS, | |
| TIMEOUT | |
| } | |
| /** Output stream for writing to the stdin of a Windows process. */ | |
| private class ProcessOutputStream extends OutputStream { | |
| private ProcessOutputStream() {} | |
| @Override | |
| public void write(int b) throws IOException { | |
| byte[] buf = new byte[] {(byte) b}; | |
| write(buf, 0, 1); | |
| } | |
| @Override | |
| public void write(byte[] b, int off, int len) throws IOException { | |
| writeStream(b, off, len); | |
| } | |
| } | |
| /** | |
| * Input stream for reading the stdout or stderr of a Windows process. | |
| * | |
| * <p>This class is non-static for debugging purposes. | |
| */ | |
| private static final class ProcessInputStream extends InputStream { | |
| private long nativeStream; | |
| ProcessInputStream(long nativeStream) { | |
| this.nativeStream = nativeStream; | |
| } | |
| @Override | |
| public int available() throws IOException { | |
| if (nativeStream == WindowsProcesses.INVALID) { | |
| throw new IllegalStateException("Stream already closed"); | |
| } | |
| int result = WindowsProcesses.streamBytesAvailable(nativeStream); | |
| if (result == -1) { | |
| throw new IOException(WindowsProcesses.streamGetLastError(nativeStream)); | |
| } | |
| return result; | |
| } | |
| @Override | |
| public int read() throws IOException { | |
| byte[] buf = new byte[1]; | |
| if (read(buf, 0, 1) != 1) { | |
| return -1; | |
| } else { | |
| return buf[0] & 0xff; | |
| } | |
| } | |
| @Override | |
| public synchronized int read(byte[] b, int off, int len) throws IOException { | |
| if (nativeStream == WindowsProcesses.INVALID) { | |
| throw new IllegalStateException(); | |
| } | |
| int result = WindowsProcesses.readStream(nativeStream, b, off, len); | |
| if (result == 0) { | |
| return -1; // EOF | |
| } | |
| if (result == -1) { | |
| throw new IOException(WindowsProcesses.streamGetLastError(nativeStream)); | |
| } | |
| return result; | |
| } | |
| @Override | |
| public synchronized void close() { | |
| if (nativeStream != WindowsProcesses.INVALID) { | |
| WindowsProcesses.closeStream(nativeStream); | |
| nativeStream = WindowsProcesses.INVALID; | |
| } | |
| } | |
| @Override | |
| protected void finalize() throws Throwable { | |
| close(); | |
| super.finalize(); | |
| } | |
| } | |
| private static final AtomicInteger THREAD_SEQUENCE_NUMBER = new AtomicInteger(1); | |
| private static final ExecutorService WAITER_POOL = | |
| Executors.newCachedThreadPool( | |
| new ThreadFactory() { | |
| @Override | |
| public Thread newThread(Runnable runnable) { | |
| Thread thread = | |
| new Thread( | |
| null, | |
| runnable, | |
| "Windows-Process-Waiter-Thread-" + THREAD_SEQUENCE_NUMBER.getAndIncrement()); | |
| thread.setDaemon(true); | |
| return thread; | |
| } | |
| }); | |
| private volatile long nativeProcess; | |
| private final OutputStream stdinStream; | |
| private final ProcessInputStream stdoutStream; | |
| private final ProcessInputStream stderrStream; | |
| private final Future<WaitResult> processFuture; | |
| private final long timeoutMillis; | |
| private boolean timedout = false; | |
| WindowsSubprocess( | |
| long nativeProcess, | |
| String commandLine, | |
| boolean stdoutRedirected, | |
| boolean stderrRedirected, | |
| long timeoutMillis) { | |
| this.commandLine = commandLine; | |
| this.nativeProcess = nativeProcess; | |
| // As per the spec of Command, we should only apply timeouts that are > 0. | |
| this.timeoutMillis = timeoutMillis <= 0 ? -1 : timeoutMillis; | |
| stdoutStream = | |
| stdoutRedirected ? null : new ProcessInputStream(WindowsProcesses.getStdout(nativeProcess)); | |
| stderrStream = | |
| stderrRedirected ? null : new ProcessInputStream(WindowsProcesses.getStderr(nativeProcess)); | |
| stdinStream = new ProcessOutputStream(); | |
| // Every Windows process we start consumes a thread here. This is suboptimal, but seems to be | |
| // the sanest way to reconcile WaitForMultipleObjects() and Java-style interruption. | |
| processFuture = WAITER_POOL.submit(this::waiterThreadFunc); | |
| } | |
| // Waits for the process to finish. | |
| private WaitResult waiterThreadFunc() { | |
| switch (WindowsProcesses.waitFor(nativeProcess, timeoutMillis)) { | |
| case 0: | |
| // Excellent, process finished in time. | |
| return WaitResult.SUCCESS; | |
| case 1: | |
| // Timeout. We don't need to call `terminate` here, because waitFor | |
| // automatically terminates the process in case of a timeout. | |
| return WaitResult.TIMEOUT; | |
| default: | |
| // Error. There isn't a lot we can do -- the process is still alive but | |
| // WaitForSingleObject() failed for some odd reason. This should | |
| // basically never happen, but if it does... let's get a stack trace. | |
| String errorMessage = WindowsProcesses.processGetLastError(nativeProcess); | |
| throw new IllegalStateException( | |
| "Waiting for process " | |
| + WindowsProcesses.getProcessPid(nativeProcess) | |
| + " failed: " | |
| + errorMessage); | |
| } | |
| } | |
| @Override | |
| public synchronized void finalize() throws Throwable { | |
| if (nativeProcess != WindowsProcesses.INVALID) { | |
| close(); | |
| } | |
| super.finalize(); | |
| } | |
| @Override | |
| public synchronized boolean destroy() { | |
| checkLiveness(); | |
| return WindowsProcesses.terminate(nativeProcess); | |
| } | |
| @Override | |
| public synchronized int exitValue() { | |
| checkLiveness(); | |
| int result = WindowsProcesses.getExitCode(nativeProcess); | |
| String error = WindowsProcesses.processGetLastError(nativeProcess); | |
| if (!error.isEmpty()) { | |
| throw new IllegalStateException(error); | |
| } | |
| return result; | |
| } | |
| @Override | |
| public boolean finished() { | |
| return processFuture.isDone(); | |
| } | |
| @Override | |
| public boolean isAlive() { | |
| return !processFuture.isDone(); | |
| } | |
| @Override | |
| public boolean timedout() { | |
| return timedout; | |
| } | |
| @Override | |
| public void waitFor() throws InterruptedException { | |
| try { | |
| timedout = processFuture.get() == WaitResult.TIMEOUT; | |
| } catch (ExecutionException e) { | |
| Throwables.throwIfUnchecked(e.getCause()); | |
| // This should never happen, because waiterThreadFunc does not throw any | |
| // checked exceptions. | |
| throw new IllegalStateException("Unexpected exception", e); | |
| } | |
| } | |
| @Override | |
| public synchronized void close() { | |
| if (nativeProcess != WindowsProcesses.INVALID) { | |
| stdoutStream.close(); | |
| stderrStream.close(); | |
| long process = nativeProcess; | |
| nativeProcess = WindowsProcesses.INVALID; | |
| WindowsProcesses.deleteProcess(process); | |
| } | |
| } | |
| @Override | |
| public OutputStream getOutputStream() { | |
| return stdinStream; | |
| } | |
| @Override | |
| public InputStream getInputStream() { | |
| return stdoutStream; | |
| } | |
| @Override | |
| public InputStream getErrorStream() { | |
| return stderrStream; | |
| } | |
| private synchronized void writeStream(byte[] b, int off, int len) throws IOException { | |
| checkLiveness(); | |
| int remaining = len; | |
| int currentOffset = off; | |
| while (remaining != 0) { | |
| int written = WindowsProcesses.writeStdin(nativeProcess, b, currentOffset, remaining); | |
| // I think the Windows API never returns 0 in dwNumberOfBytesWritten | |
| // Verify.verify(written != 0); | |
| if (written == -1) { | |
| throw new IOException(WindowsProcesses.processGetLastError(nativeProcess)); | |
| } | |
| remaining -= written; | |
| currentOffset += written; | |
| } | |
| } | |
| private void checkLiveness() { | |
| if (nativeProcess == WindowsProcesses.INVALID) { | |
| throw new IllegalStateException(); | |
| } | |
| } | |
| @Override | |
| public String toString() { | |
| return String.format("%s:[%s]", super.toString(), commandLine); | |
| } | |
| @Override | |
| public long getProcessId() { | |
| return this.nativeProcess; | |
| } | |
| } |