| // 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.devtools.build.lib.shell.Subprocess; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.OutputStream; | |
| import java.util.concurrent.CountDownLatch; | |
| import java.util.concurrent.ExecutorService; | |
| import java.util.concurrent.Executors; | |
| import java.util.concurrent.ThreadFactory; | |
| import java.util.concurrent.atomic.AtomicBoolean; | |
| 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; | |
| /** | |
| * 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 class ProcessInputStream extends InputStream { | |
| private long nativeStream; | |
| ProcessInputStream(long nativeStream) { | |
| this.nativeStream = nativeStream; | |
| } | |
| @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.nativeReadStream(nativeStream, b, off, len); | |
| if (result == 0) { | |
| return -1; // EOF | |
| } | |
| if (result == -1) { | |
| throw new IOException(WindowsProcesses.nativeStreamGetLastError(nativeStream)); | |
| } | |
| return result; | |
| } | |
| @Override | |
| public synchronized void close() { | |
| if (nativeStream != WindowsProcesses.INVALID) { | |
| WindowsProcesses.nativeCloseStream(nativeStream); | |
| nativeStream = WindowsProcesses.INVALID; | |
| } | |
| } | |
| @Override | |
| protected void finalize() throws Throwable { | |
| close(); | |
| super.finalize(); | |
| } | |
| } | |
| private static 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(), | |
| 16 * 1024); | |
| thread.setDaemon(true); | |
| return thread; | |
| } | |
| }); | |
| private volatile long nativeProcess; | |
| private final OutputStream stdinStream; | |
| private final ProcessInputStream stdoutStream; | |
| private final ProcessInputStream stderrStream; | |
| private final CountDownLatch waitLatch; | |
| private final long timeoutMillis; | |
| private final AtomicBoolean timedout = new AtomicBoolean(false); | |
| WindowsSubprocess(long nativeProcess, String commandLine, boolean stdoutRedirected, | |
| boolean stderrRedirected, long timeoutMillis) { | |
| this.commandLine = commandLine; | |
| this.nativeProcess = nativeProcess; | |
| this.timeoutMillis = timeoutMillis; | |
| stdoutStream = | |
| stdoutRedirected | |
| ? null | |
| : new ProcessInputStream(WindowsProcesses.nativeGetStdout(nativeProcess)); | |
| stderrStream = | |
| stderrRedirected | |
| ? null | |
| : new ProcessInputStream(WindowsProcesses.nativeGetStderr(nativeProcess)); | |
| stdinStream = new ProcessOutputStream(); | |
| waitLatch = new CountDownLatch(1); | |
| // 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. | |
| WAITER_POOL.submit(new Runnable() { | |
| @Override public void run() { | |
| waiterThreadFunc(); | |
| } | |
| }); | |
| } | |
| private void waiterThreadFunc() { | |
| switch (WindowsProcesses.nativeWaitFor(nativeProcess, timeoutMillis)) { | |
| case 0: | |
| // Excellent, process finished in time. | |
| break; | |
| case 1: | |
| // Timeout. Terminate the process if we can. | |
| timedout.set(true); | |
| WindowsProcesses.nativeTerminate(nativeProcess); | |
| break; | |
| case 2: | |
| // Error. There isn't a lot we can do -- the process is still alive but | |
| // WaitForMultipleObjects() failed for some odd reason. We'll pretend it terminated and | |
| // log a message to jvm.out . | |
| System.err.println("Waiting for process " | |
| + WindowsProcesses.nativeGetProcessPid(nativeProcess) + " failed"); | |
| break; | |
| } | |
| waitLatch.countDown(); | |
| } | |
| @Override | |
| public synchronized void finalize() throws Throwable { | |
| if (nativeProcess != WindowsProcesses.INVALID) { | |
| close(); | |
| } | |
| super.finalize(); | |
| } | |
| @Override | |
| public synchronized boolean destroy() { | |
| checkLiveness(); | |
| if (!WindowsProcesses.nativeTerminate(nativeProcess)) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| @Override | |
| public synchronized int exitValue() { | |
| checkLiveness(); | |
| int result = WindowsProcesses.nativeGetExitCode(nativeProcess); | |
| String error = WindowsProcesses.nativeProcessGetLastError(nativeProcess); | |
| if (!error.isEmpty()) { | |
| throw new IllegalStateException(error); | |
| } | |
| return result; | |
| } | |
| @Override | |
| public boolean finished() { | |
| return waitLatch.getCount() == 0; | |
| } | |
| @Override | |
| public boolean timedout() { | |
| return timedout.get(); | |
| } | |
| @Override | |
| public void waitFor() throws InterruptedException { | |
| waitLatch.await(); | |
| } | |
| @Override | |
| public synchronized void close() { | |
| if (nativeProcess != WindowsProcesses.INVALID) { | |
| stdoutStream.close(); | |
| stderrStream.close(); | |
| long process = nativeProcess; | |
| nativeProcess = WindowsProcesses.INVALID; | |
| WindowsProcesses.nativeDeleteProcess(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.nativeWriteStdin( | |
| 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.nativeProcessGetLastError(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); | |
| } | |
| } |