All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.devtools.build.lib.windows.WindowsSubprocess Maven / Gradle / Ivy

// 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 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;

import com.google.devtools.build.lib.shell.Subprocess;
import com.google.devtools.build.lib.windows.jni.WindowsProcesses;

/**
 * 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.
   *
   * 

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 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; } } } 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(), 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 Future 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 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 timedout() { return timedout; } @Override public void waitFor() throws InterruptedException { try { timedout = processFuture.get() == WaitResult.TIMEOUT; } catch (ExecutionException e) { if (e.getCause() instanceof RuntimeException) { throw (RuntimeException) 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) { if ( stdoutStream != null ) { stdoutStream.close(); } if ( stderrStream != null ) { 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); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy