| #!/usr/bin/env python |
| # |
| # Copyright 2007 Google Inc. |
| # |
| # 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. |
| # |
| """A thread-safe wrapper for the subprocess module.""" |
| |
| import logging |
| import subprocess |
| import sys |
| import tempfile |
| import threading |
| |
| # Subprocess creation is not threadsafe in Python. See |
| # http://bugs.python.org/issue1731717. |
| _popen_lock = threading.Lock() |
| |
| # The provided Python binary on OS X also requires _popen_lock be held while |
| # writing to and closing the stdin of the subprocess. |
| if sys.platform == 'darwin': |
| _SUBPROCESS_STDIN_IS_THREAD_HOSTILE = True |
| else: |
| _SUBPROCESS_STDIN_IS_THREAD_HOSTILE = False |
| |
| |
| def start_process(args, input_string='', env=None, cwd=None, stdout=None, |
| stderr=None): |
| """Starts a subprocess like subprocess.Popen, but is threadsafe. |
| |
| The value of input_string is passed to stdin of the subprocess, which is then |
| closed. |
| |
| Args: |
| args: A string or sequence of strings containing the program arguments. |
| input_string: A string to pass to stdin of the subprocess. |
| env: A dict containing environment variables for the subprocess. |
| cwd: A string containing the directory to switch to before executing the |
| subprocess. |
| stdout: A file descriptor, file object or subprocess.PIPE to use for the |
| stdout descriptor for the subprocess. |
| stderr: A file descriptor, file object or subprocess.PIPE to use for the |
| stderr descriptor for the subprocess. |
| |
| Returns: |
| A subprocess.Popen instance for the created subprocess. |
| """ |
| with _popen_lock: |
| logging.debug('Starting process %r with input=%r, env=%r, cwd=%r', |
| args, input_string, env, cwd) |
| |
| # Suppress the display of the console window on Windows. |
| # Note: subprocess.STARTF_USESHOWWINDOW & subprocess.SW_HIDE are only |
| # availalbe after Python 2.7.2 on Windows. |
| if (hasattr(subprocess, 'SW_HIDE') and |
| hasattr(subprocess, 'STARTF_USESHOWWINDOW')): |
| startupinfo = subprocess.STARTUPINFO() |
| startupinfo.dwFlags = subprocess.STARTF_USESHOWWINDOW |
| startupinfo.wShowWindow = subprocess.SW_HIDE |
| else: |
| startupinfo = None |
| |
| p = subprocess.Popen(args, env=env, cwd=cwd, stdout=stdout, stderr=stderr, |
| stdin=subprocess.PIPE, startupinfo=startupinfo) |
| if _SUBPROCESS_STDIN_IS_THREAD_HOSTILE: |
| p.stdin.write(input_string) |
| p.stdin.close() |
| p.stdin = None |
| if not _SUBPROCESS_STDIN_IS_THREAD_HOSTILE: |
| p.stdin.write(input_string) |
| p.stdin.close() |
| p.stdin = None |
| return p |
| |
| |
| def start_process_file(args, input_string, env, cwd, stdin=None, stdout=None, |
| stderr=None): |
| """Starts a subprocess thread safely with temporary files for communication. |
| |
| An alternate version of start_process that allows for the preservation |
| of stdin and stdout by creating two files that can be used for communication |
| between the processes. The paths to these files are added to the command |
| line after any args provided by the caller. The first file is written with |
| the value of input_string and the second file is returned to the caller. |
| |
| Args: |
| args: A string or sequence of strings containing the program arguments. |
| input_string: A string to pass to stdin of the subprocess. |
| env: A dict containing environment variables for the subprocess. |
| cwd: A string containing the directory to switch to before executing the |
| subprocess. |
| stdin: A file descriptor, file object or subprocess.PIPE to use for the |
| stdin descriptor for the subprocess. |
| stdout: A file descriptor, file object or subprocess.PIPE to use for the |
| stdout descriptor for the subprocess. |
| stderr: A file descriptor, file object or subprocess.PIPE to use for the |
| stderr descriptor for the subprocess. |
| |
| Returns: |
| A subprocess.Popen instance for the created subprocess. In addition to |
| the standard attributes, an additional child_out attribute is attached |
| that references a NamedTemporaryFile that the child process may write |
| and this process may read; it is up to the caller to delete the file |
| (path available as p.child_out.name). |
| """ |
| # In addition to needing to control deletion time, we need delete=False |
| # in order to allow multiple files to open the process on Windows. |
| child_in = tempfile.NamedTemporaryFile(mode='wb', delete=False) |
| child_out = tempfile.NamedTemporaryFile(mode='rb', delete=False) |
| |
| child_in.write(input_string) |
| child_in.close() |
| |
| # pylint: disable=g-no-augmented-assignment |
| # += modifies the original args which we don't want. |
| args = args + [child_in.name, child_out.name] |
| |
| with _popen_lock: |
| logging.debug('Starting process %r with input=%r, env=%r, cwd=%r', |
| args, input_string, env, cwd) |
| p = subprocess.Popen(args, env=env, cwd=cwd, stdin=stdin, stdout=stdout, |
| stderr=stderr) |
| |
| p.child_out = child_out |
| return p |
| |