OLD | NEW |
1 # coding=utf8 | 1 # coding=utf8 |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 """Collection of subprocess wrapper functions. | 5 """Collection of subprocess wrapper functions. |
6 | 6 |
7 In theory you shouldn't need anything else in subprocess, or this module failed. | 7 In theory you shouldn't need anything else in subprocess, or this module failed. |
8 """ | 8 """ |
9 | 9 |
10 from __future__ import with_statement | 10 from __future__ import with_statement |
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
166 elif isinstance(args, (list, tuple)): | 166 elif isinstance(args, (list, tuple)): |
167 tmp_str = ' '.join(args) | 167 tmp_str = ' '.join(args) |
168 else: | 168 else: |
169 raise CalledProcessError(None, args, kwargs.get('cwd'), None, None) | 169 raise CalledProcessError(None, args, kwargs.get('cwd'), None, None) |
170 if kwargs.get('cwd', None): | 170 if kwargs.get('cwd', None): |
171 tmp_str += '; cwd=%s' % kwargs['cwd'] | 171 tmp_str += '; cwd=%s' % kwargs['cwd'] |
172 logging.debug(tmp_str) | 172 logging.debug(tmp_str) |
173 | 173 |
174 self.stdout_cb = None | 174 self.stdout_cb = None |
175 self.stderr_cb = None | 175 self.stderr_cb = None |
176 self.stdout_void = False | 176 self.stdin_is_void = False |
177 self.stderr_void = False | 177 self.stdout_is_void = False |
178 def fix(stream): | 178 self.stderr_is_void = False |
| 179 |
| 180 if kwargs.get('stdin') is VOID: |
| 181 kwargs['stdin'] = open(os.devnull, 'r') |
| 182 self.stdin_is_void = True |
| 183 |
| 184 for stream in ('stdout', 'stderr'): |
179 if kwargs.get(stream) in (VOID, os.devnull): | 185 if kwargs.get(stream) in (VOID, os.devnull): |
180 # Replaces VOID with handle to /dev/null. | |
181 # Create a temporary file to workaround python's deadlock. | |
182 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait | |
183 # When the pipe fills up, it will deadlock this process. Using a real | |
184 # file works around that issue. | |
185 kwargs[stream] = open(os.devnull, 'w') | 186 kwargs[stream] = open(os.devnull, 'w') |
186 setattr(self, stream + '_void', True) | 187 setattr(self, stream + '_is_void', True) |
187 if callable(kwargs.get(stream)): | 188 if callable(kwargs.get(stream)): |
188 # Callable stdout/stderr should be used only with call() wrappers. | |
189 setattr(self, stream + '_cb', kwargs[stream]) | 189 setattr(self, stream + '_cb', kwargs[stream]) |
190 kwargs[stream] = PIPE | 190 kwargs[stream] = PIPE |
191 | 191 |
192 fix('stdout') | |
193 fix('stderr') | |
194 | |
195 self.start = time.time() | 192 self.start = time.time() |
196 self.timeout = None | 193 self.timeout = None |
197 self.shell = kwargs.get('shell', None) | 194 self.shell = kwargs.get('shell', None) |
198 # Silence pylint on MacOSX | 195 # Silence pylint on MacOSX |
199 self.returncode = None | 196 self.returncode = None |
200 | 197 |
201 try: | 198 try: |
202 super(Popen, self).__init__(args, **kwargs) | 199 super(Popen, self).__init__(args, **kwargs) |
203 except OSError, e: | 200 except OSError, e: |
204 if e.errno == errno.EAGAIN and sys.platform == 'cygwin': | 201 if e.errno == errno.EAGAIN and sys.platform == 'cygwin': |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
281 if self.timeout is not None: | 278 if self.timeout is not None: |
282 threads['timeout'] = threading.Thread(target=timeout_fn) | 279 threads['timeout'] = threading.Thread(target=timeout_fn) |
283 if self.stdout_cb: | 280 if self.stdout_cb: |
284 threads['stdout'] = threading.Thread( | 281 threads['stdout'] = threading.Thread( |
285 target=_queue_pipe_read, args=(self.stdout, 'stdout')) | 282 target=_queue_pipe_read, args=(self.stdout, 'stdout')) |
286 if self.stderr_cb: | 283 if self.stderr_cb: |
287 threads['stderr'] = threading.Thread( | 284 threads['stderr'] = threading.Thread( |
288 target=_queue_pipe_read, args=(self.stderr, 'stderr')) | 285 target=_queue_pipe_read, args=(self.stderr, 'stderr')) |
289 if input: | 286 if input: |
290 threads['stdin'] = threading.Thread(target=write_stdin) | 287 threads['stdin'] = threading.Thread(target=write_stdin) |
| 288 elif self.stdin: |
| 289 # Pipe but no input, make sure it's closed. |
| 290 self.stdin.close() |
291 for t in threads.itervalues(): | 291 for t in threads.itervalues(): |
292 t.start() | 292 t.start() |
293 | 293 |
294 timed_out = False | 294 timed_out = False |
295 try: | 295 try: |
296 # This thread needs to be optimized for speed. | 296 # This thread needs to be optimized for speed. |
297 while threads: | 297 while threads: |
298 item = queue.get() | 298 item = queue.get() |
299 if item[0] is 'stdout': | 299 if item[0] is 'stdout': |
300 self.stdout_cb(item[1]) | 300 self.stdout_cb(item[1]) |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
339 | 339 |
340 if self.timeout and self.shell: | 340 if self.timeout and self.shell: |
341 raise TypeError( | 341 raise TypeError( |
342 'Using timeout and shell simultaneously will cause a process leak ' | 342 'Using timeout and shell simultaneously will cause a process leak ' |
343 'since the shell will be killed instead of the child process.') | 343 'since the shell will be killed instead of the child process.') |
344 | 344 |
345 stdout = None | 345 stdout = None |
346 stderr = None | 346 stderr = None |
347 # Convert to a lambda to workaround python's deadlock. | 347 # Convert to a lambda to workaround python's deadlock. |
348 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait | 348 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait |
349 # When the pipe fills up, it will deadlock this process. Using a thread | 349 # When the pipe fills up, it would deadlock this process. |
350 # works around that issue. No need for thread safe function since the call | 350 if self.stdout and not self.stdout_cb and not self.stdout_is_void: |
351 # backs are guaranteed to be called from the main thread. | 351 stdout = [] |
352 if self.stdout and not self.stdout_cb and not self.stdout_void: | 352 self.stdout_cb = stdout.append |
353 stdout = cStringIO.StringIO() | 353 if self.stderr and not self.stderr_cb and not self.stderr_is_void: |
354 self.stdout_cb = stdout.write | 354 stderr = [] |
355 if self.stderr and not self.stderr_cb and not self.stderr_void: | 355 self.stderr_cb = stderr.append |
356 stderr = cStringIO.StringIO() | |
357 self.stderr_cb = stderr.write | |
358 self._tee_threads(input) | 356 self._tee_threads(input) |
359 if stdout: | 357 if stdout is not None: |
360 stdout = stdout.getvalue() | 358 stdout = ''.join(stdout) |
361 if stderr: | 359 stderr = None |
362 stderr = stderr.getvalue() | 360 if stderr is not None: |
| 361 stderr = ''.join(stderr) |
363 return (stdout, stderr) | 362 return (stdout, stderr) |
364 | 363 |
365 | 364 |
366 def communicate(args, timeout=None, **kwargs): | 365 def communicate(args, timeout=None, **kwargs): |
367 """Wraps subprocess.Popen().communicate() and add timeout support. | 366 """Wraps subprocess.Popen().communicate() and add timeout support. |
368 | 367 |
369 Returns ((stdout, stderr), returncode). | 368 Returns ((stdout, stderr), returncode). |
370 | 369 |
371 - The process will be killed after |timeout| seconds and returncode set to | 370 - The process will be killed after |timeout| seconds and returncode set to |
372 TIMED_OUT. | 371 TIMED_OUT. |
373 - Automatically passes stdin content as input so do not specify stdin=PIPE. | 372 - Automatically passes stdin content as input so do not specify stdin=PIPE. |
374 """ | 373 """ |
375 stdin = kwargs.pop('stdin', None) | 374 stdin = kwargs.pop('stdin', None) |
376 if stdin is not None: | 375 if stdin is not None: |
377 if stdin is VOID: | 376 if isinstance(stdin, basestring): |
378 kwargs['stdin'] = open(os.devnull, 'r') | |
379 stdin = None | |
380 else: | |
381 assert isinstance(stdin, basestring) | |
382 # When stdin is passed as an argument, use it as the actual input data and | 377 # When stdin is passed as an argument, use it as the actual input data and |
383 # set the Popen() parameter accordingly. | 378 # set the Popen() parameter accordingly. |
384 kwargs['stdin'] = PIPE | 379 kwargs['stdin'] = PIPE |
| 380 else: |
| 381 kwargs['stdin'] = stdin |
| 382 stdin = None |
385 | 383 |
386 proc = Popen(args, **kwargs) | 384 proc = Popen(args, **kwargs) |
387 if stdin not in (None, VOID): | 385 if stdin: |
388 return proc.communicate(stdin, timeout), proc.returncode | 386 return proc.communicate(stdin, timeout), proc.returncode |
389 else: | 387 else: |
390 return proc.communicate(None, timeout), proc.returncode | 388 return proc.communicate(None, timeout), proc.returncode |
391 | 389 |
392 | 390 |
393 def call(args, **kwargs): | 391 def call(args, **kwargs): |
394 """Emulates subprocess.call(). | 392 """Emulates subprocess.call(). |
395 | 393 |
396 Automatically convert stdout=PIPE or stderr=PIPE to VOID. | 394 Automatically convert stdout=PIPE or stderr=PIPE to VOID. |
397 In no case they can be returned since no code path raises | 395 In no case they can be returned since no code path raises |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
443 | 441 |
444 - Throws if return code is not 0. | 442 - Throws if return code is not 0. |
445 - Works even prior to python 2.7. | 443 - Works even prior to python 2.7. |
446 - Blocks stdin by default if not specified since no output will be visible. | 444 - Blocks stdin by default if not specified since no output will be visible. |
447 - As per doc, "The stdout argument is not allowed as it is used internally." | 445 - As per doc, "The stdout argument is not allowed as it is used internally." |
448 """ | 446 """ |
449 kwargs.setdefault('stdin', VOID) | 447 kwargs.setdefault('stdin', VOID) |
450 if 'stdout' in kwargs: | 448 if 'stdout' in kwargs: |
451 raise ValueError('stdout argument not allowed, it will be overridden.') | 449 raise ValueError('stdout argument not allowed, it will be overridden.') |
452 return check_call_out(args, stdout=PIPE, **kwargs)[0] | 450 return check_call_out(args, stdout=PIPE, **kwargs)[0] |
OLD | NEW |