OLD | NEW |
(Empty) | |
| 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed by the Apache v2.0 license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 # This is a direct vendored copy of the original which is located at: |
| 6 # https://github.com/luci/luci-py/blob/master/client/utils/subprocess42.py |
| 7 # at commit 45eab10a17a55f1978f3bfa02c35457dee1c64e4. |
| 8 |
| 9 """subprocess42 is the answer to life the universe and everything. |
| 10 |
| 11 It has the particularity of having a Popen implementation that can yield output |
| 12 as it is produced while implementing a timeout and NOT requiring the use of |
| 13 worker threads. |
| 14 |
| 15 Example: |
| 16 Wait for a child process with a timeout, send SIGTERM, wait a grace period |
| 17 then send SIGKILL: |
| 18 |
| 19 def wait_terminate_then_kill(proc, timeout, grace): |
| 20 try: |
| 21 return proc.wait(timeout) |
| 22 except subprocess42.TimeoutExpired: |
| 23 proc.terminate() |
| 24 try: |
| 25 return proc.wait(grace) |
| 26 except subprocess42.TimeoutExpired: |
| 27 proc.kill() |
| 28 return proc.wait() |
| 29 |
| 30 |
| 31 TODO(maruel): Add VOID support like subprocess2. |
| 32 """ |
| 33 |
| 34 import contextlib |
| 35 import errno |
| 36 import os |
| 37 import signal |
| 38 import threading |
| 39 import time |
| 40 |
| 41 import subprocess |
| 42 |
| 43 from subprocess import CalledProcessError, PIPE, STDOUT # pylint: disable=W0611 |
| 44 from subprocess import list2cmdline |
| 45 |
| 46 |
| 47 # Default maxsize argument. |
| 48 MAX_SIZE = 16384 |
| 49 |
| 50 |
| 51 if subprocess.mswindows: |
| 52 import msvcrt # pylint: disable=F0401 |
| 53 from ctypes import wintypes |
| 54 from ctypes import windll |
| 55 |
| 56 |
| 57 # Which to be received depends on how this process was called and outside the |
| 58 # control of this script. See Popen docstring for more details. |
| 59 STOP_SIGNALS = (signal.SIGBREAK, signal.SIGTERM) |
| 60 |
| 61 |
| 62 def ReadFile(handle, desired_bytes): |
| 63 """Calls kernel32.ReadFile().""" |
| 64 c_read = wintypes.DWORD() |
| 65 buff = wintypes.create_string_buffer(desired_bytes+1) |
| 66 windll.kernel32.ReadFile( |
| 67 handle, buff, desired_bytes, wintypes.byref(c_read), None) |
| 68 # NULL terminate it. |
| 69 buff[c_read.value] = '\x00' |
| 70 return wintypes.GetLastError(), buff.value |
| 71 |
| 72 def PeekNamedPipe(handle): |
| 73 """Calls kernel32.PeekNamedPipe(). Simplified version.""" |
| 74 c_avail = wintypes.DWORD() |
| 75 c_message = wintypes.DWORD() |
| 76 success = windll.kernel32.PeekNamedPipe( |
| 77 handle, None, 0, None, wintypes.byref(c_avail), |
| 78 wintypes.byref(c_message)) |
| 79 if not success: |
| 80 raise OSError(wintypes.GetLastError()) |
| 81 return c_avail.value |
| 82 |
| 83 def recv_multi_impl(conns, maxsize, timeout): |
| 84 """Reads from the first available pipe. |
| 85 |
| 86 It will immediately return on a closed connection, independent of timeout. |
| 87 |
| 88 Arguments: |
| 89 - maxsize: Maximum number of bytes to return. Defaults to MAX_SIZE. |
| 90 - timeout: If None, it is blocking. If 0 or above, will return None if no |
| 91 data is available within |timeout| seconds. |
| 92 |
| 93 Returns: |
| 94 tuple(int(index), str(data), bool(closed)). |
| 95 """ |
| 96 assert conns |
| 97 assert timeout is None or isinstance(timeout, (int, float)), timeout |
| 98 maxsize = max(maxsize or MAX_SIZE, 1) |
| 99 |
| 100 # TODO(maruel): Use WaitForMultipleObjects(). Python creates anonymous pipes |
| 101 # for proc.stdout and proc.stderr but they are implemented as named pipes on |
| 102 # Windows. Since named pipes are not waitable object, they can't be passed |
| 103 # as-is to WFMO(). So this means N times CreateEvent(), N times ReadFile() |
| 104 # and finally WFMO(). This requires caching the events handles in the Popen |
| 105 # object and remembering the pending ReadFile() calls. This will require |
| 106 # some re-architecture to store the relevant event handle and OVERLAPPEDIO |
| 107 # object in Popen or the file object. |
| 108 start = time.time() |
| 109 handles = [ |
| 110 (i, msvcrt.get_osfhandle(c.fileno())) for i, c in enumerate(conns) |
| 111 ] |
| 112 while True: |
| 113 for index, handle in handles: |
| 114 try: |
| 115 avail = min(PeekNamedPipe(handle), maxsize) |
| 116 if avail: |
| 117 return index, ReadFile(handle, avail)[1], False |
| 118 except OSError: |
| 119 # The pipe closed. |
| 120 return index, None, True |
| 121 |
| 122 if timeout is not None and (time.time() - start) >= timeout: |
| 123 return None, None, False |
| 124 # Polling rocks. |
| 125 time.sleep(0.001) |
| 126 |
| 127 else: |
| 128 import fcntl # pylint: disable=F0401 |
| 129 import select |
| 130 |
| 131 |
| 132 # Signals that mean this process should exit quickly. |
| 133 STOP_SIGNALS = (signal.SIGINT, signal.SIGTERM) |
| 134 |
| 135 |
| 136 def recv_multi_impl(conns, maxsize, timeout): |
| 137 """Reads from the first available pipe. |
| 138 |
| 139 It will immediately return on a closed connection, independent of timeout. |
| 140 |
| 141 Arguments: |
| 142 - maxsize: Maximum number of bytes to return. Defaults to MAX_SIZE. |
| 143 - timeout: If None, it is blocking. If 0 or above, will return None if no |
| 144 data is available within |timeout| seconds. |
| 145 |
| 146 Returns: |
| 147 tuple(int(index), str(data), bool(closed)). |
| 148 """ |
| 149 assert conns |
| 150 assert timeout is None or isinstance(timeout, (int, float)), timeout |
| 151 maxsize = max(maxsize or MAX_SIZE, 1) |
| 152 |
| 153 # select(timeout=0) will block, it has to be a value > 0. |
| 154 if timeout == 0: |
| 155 timeout = 0.001 |
| 156 try: |
| 157 r, _, _ = select.select(conns, [], [], timeout) |
| 158 except select.error: |
| 159 r = None |
| 160 if not r: |
| 161 return None, None, False |
| 162 |
| 163 conn = r[0] |
| 164 # Temporarily make it non-blocking. |
| 165 # TODO(maruel): This is not very efficient when the caller is doing this in |
| 166 # a loop. Add a mechanism to have the caller handle this. |
| 167 flags = fcntl.fcntl(conn, fcntl.F_GETFL) |
| 168 if not conn.closed: |
| 169 # pylint: disable=E1101 |
| 170 fcntl.fcntl(conn, fcntl.F_SETFL, flags | os.O_NONBLOCK) |
| 171 try: |
| 172 try: |
| 173 data = conn.read(maxsize) |
| 174 except IOError as e: |
| 175 # On posix, this means the read would block. |
| 176 if e.errno == errno.EAGAIN: |
| 177 return conns.index(conn), None, False |
| 178 raise e |
| 179 |
| 180 if not data: |
| 181 # On posix, this means the channel closed. |
| 182 return conns.index(conn), None, True |
| 183 |
| 184 return conns.index(conn), data, False |
| 185 finally: |
| 186 if not conn.closed: |
| 187 fcntl.fcntl(conn, fcntl.F_SETFL, flags) |
| 188 |
| 189 |
| 190 class TimeoutExpired(Exception): |
| 191 """Compatible with python3 subprocess.""" |
| 192 def __init__(self, cmd, timeout, output=None, stderr=None): |
| 193 self.cmd = cmd |
| 194 self.timeout = timeout |
| 195 self.output = output |
| 196 # Non-standard: |
| 197 self.stderr = stderr |
| 198 super(TimeoutExpired, self).__init__(str(self)) |
| 199 |
| 200 def __str__(self): |
| 201 return "Command '%s' timed out after %s seconds" % (self.cmd, self.timeout) |
| 202 |
| 203 |
| 204 class Popen(subprocess.Popen): |
| 205 """Adds timeout support on stdout and stderr. |
| 206 |
| 207 Inspired by |
| 208 http://code.activestate.com/recipes/440554-module-to-allow-asynchronous-subpro
cess-use-on-win/ |
| 209 |
| 210 Unlike subprocess, yield_any(), recv_*(), communicate() will close stdout and |
| 211 stderr once the child process closes them, after all the data is read. |
| 212 |
| 213 Arguments: |
| 214 - detached: If True, the process is created in a new process group. On |
| 215 Windows, use CREATE_NEW_PROCESS_GROUP. On posix, use os.setpgid(0, 0). |
| 216 |
| 217 Additional members: |
| 218 - start: timestamp when this process started. |
| 219 - end: timestamp when this process exited, as seen by this process. |
| 220 - detached: If True, the child process was started as a detached process. |
| 221 - gid: process group id, if any. |
| 222 - duration: time in seconds the process lasted. |
| 223 |
| 224 Additional methods: |
| 225 - yield_any(): yields output until the process terminates. |
| 226 - recv_any(): reads from stdout and/or stderr with optional timeout. |
| 227 - recv_out() & recv_err(): specialized version of recv_any(). |
| 228 """ |
| 229 # subprocess.Popen.__init__() is not threadsafe; there is a race between |
| 230 # creating the exec-error pipe for the child and setting it to CLOEXEC during |
| 231 # which another thread can fork and cause the pipe to be inherited by its |
| 232 # descendents, which will cause the current Popen to hang until all those |
| 233 # descendents exit. Protect this with a lock so that only one fork/exec can |
| 234 # happen at a time. |
| 235 popen_lock = threading.Lock() |
| 236 |
| 237 def __init__(self, args, **kwargs): |
| 238 assert 'creationflags' not in kwargs |
| 239 assert 'preexec_fn' not in kwargs, 'Use detached=True instead' |
| 240 self.start = time.time() |
| 241 self.end = None |
| 242 self.gid = None |
| 243 self.detached = kwargs.pop('detached', False) |
| 244 if self.detached: |
| 245 if subprocess.mswindows: |
| 246 kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP |
| 247 else: |
| 248 kwargs['preexec_fn'] = lambda: os.setpgid(0, 0) |
| 249 with self.popen_lock: |
| 250 super(Popen, self).__init__(args, **kwargs) |
| 251 self.args = args |
| 252 if self.detached and not subprocess.mswindows: |
| 253 try: |
| 254 self.gid = os.getpgid(self.pid) |
| 255 except OSError: |
| 256 # sometimes the process can run+finish before we collect its pgid. fun. |
| 257 pass |
| 258 |
| 259 def duration(self): |
| 260 """Duration of the child process. |
| 261 |
| 262 It is greater or equal to the actual time the child process ran. It can be |
| 263 significantly higher than the real value if neither .wait() nor .poll() was |
| 264 used. |
| 265 """ |
| 266 return (self.end or time.time()) - self.start |
| 267 |
| 268 # pylint: disable=arguments-differ,redefined-builtin |
| 269 def communicate(self, input=None, timeout=None): |
| 270 """Implements python3's timeout support. |
| 271 |
| 272 Unlike wait(), timeout=0 is considered the same as None. |
| 273 |
| 274 Raises: |
| 275 - TimeoutExpired when more than timeout seconds were spent waiting for the |
| 276 process. |
| 277 """ |
| 278 if not timeout: |
| 279 return super(Popen, self).communicate(input=input) |
| 280 |
| 281 assert isinstance(timeout, (int, float)), timeout |
| 282 |
| 283 if self.stdin or self.stdout or self.stderr: |
| 284 stdout = '' if self.stdout else None |
| 285 stderr = '' if self.stderr else None |
| 286 t = None |
| 287 if input is not None: |
| 288 assert self.stdin, ( |
| 289 'Can\'t use communicate(input) if not using ' |
| 290 'Popen(stdin=subprocess42.PIPE') |
| 291 # TODO(maruel): Switch back to non-threading. |
| 292 def write(): |
| 293 try: |
| 294 self.stdin.write(input) |
| 295 except IOError: |
| 296 pass |
| 297 t = threading.Thread(name='Popen.communicate', target=write) |
| 298 t.daemon = True |
| 299 t.start() |
| 300 |
| 301 try: |
| 302 if self.stdout or self.stderr: |
| 303 start = time.time() |
| 304 end = start + timeout |
| 305 def remaining(): |
| 306 return max(end - time.time(), 0) |
| 307 for pipe, data in self.yield_any(timeout=remaining): |
| 308 if pipe is None: |
| 309 raise TimeoutExpired(self.args, timeout, stdout, stderr) |
| 310 assert pipe in ('stdout', 'stderr'), pipe |
| 311 if pipe == 'stdout': |
| 312 stdout += data |
| 313 else: |
| 314 stderr += data |
| 315 else: |
| 316 # Only stdin is piped. |
| 317 self.wait(timeout=timeout) |
| 318 finally: |
| 319 if t: |
| 320 try: |
| 321 self.stdin.close() |
| 322 except IOError: |
| 323 pass |
| 324 t.join() |
| 325 else: |
| 326 # No pipe. The user wanted to use wait(). |
| 327 self.wait(timeout=timeout) |
| 328 return None, None |
| 329 |
| 330 # Indirectly initialize self.end. |
| 331 self.wait() |
| 332 return stdout, stderr |
| 333 |
| 334 def wait(self, timeout=None): # pylint: disable=arguments-differ |
| 335 """Implements python3's timeout support. |
| 336 |
| 337 Raises: |
| 338 - TimeoutExpired when more than timeout seconds were spent waiting for the |
| 339 process. |
| 340 """ |
| 341 assert timeout is None or isinstance(timeout, (int, float)), timeout |
| 342 if timeout is None: |
| 343 super(Popen, self).wait() |
| 344 elif self.returncode is None: |
| 345 if subprocess.mswindows: |
| 346 WAIT_TIMEOUT = 258 |
| 347 result = subprocess._subprocess.WaitForSingleObject( |
| 348 self._handle, int(timeout * 1000)) |
| 349 if result == WAIT_TIMEOUT: |
| 350 raise TimeoutExpired(self.args, timeout) |
| 351 self.returncode = subprocess._subprocess.GetExitCodeProcess( |
| 352 self._handle) |
| 353 else: |
| 354 # If you think the following code is horrible, it's because it is |
| 355 # inspired by python3's stdlib. |
| 356 end = time.time() + timeout |
| 357 delay = 0.001 |
| 358 while True: |
| 359 try: |
| 360 pid, sts = subprocess._eintr_retry_call( |
| 361 os.waitpid, self.pid, os.WNOHANG) |
| 362 except OSError as e: |
| 363 if e.errno != errno.ECHILD: |
| 364 raise |
| 365 pid = self.pid |
| 366 sts = 0 |
| 367 if pid == self.pid: |
| 368 # This sets self.returncode. |
| 369 self._handle_exitstatus(sts) |
| 370 break |
| 371 remaining = end - time.time() |
| 372 if remaining <= 0: |
| 373 raise TimeoutExpired(self.args, timeout) |
| 374 delay = min(delay * 2, remaining, .05) |
| 375 time.sleep(delay) |
| 376 |
| 377 if not self.end: |
| 378 # communicate() uses wait() internally. |
| 379 self.end = time.time() |
| 380 return self.returncode |
| 381 |
| 382 def poll(self): |
| 383 ret = super(Popen, self).poll() |
| 384 if ret is not None and not self.end: |
| 385 self.end = time.time() |
| 386 return ret |
| 387 |
| 388 def yield_any(self, maxsize=None, timeout=None): |
| 389 """Yields output until the process terminates. |
| 390 |
| 391 Unlike wait(), does not raise TimeoutExpired. |
| 392 |
| 393 Yields: |
| 394 (pipename, data) where pipename is either 'stdout', 'stderr' or None in |
| 395 case of timeout or when the child process closed one of the pipe(s) and |
| 396 all pending data on the pipe was read. |
| 397 |
| 398 Arguments: |
| 399 - maxsize: See recv_any(). Can be a callable function. |
| 400 - timeout: If None, the call is blocking. If set, yields None, None if no |
| 401 data is available within |timeout| seconds. It resets itself after |
| 402 each yield. Can be a callable function. |
| 403 """ |
| 404 assert self.stdout or self.stderr |
| 405 if timeout is not None: |
| 406 # timeout=0 effectively means that the pipe is continuously polled. |
| 407 if isinstance(timeout, (int, float)): |
| 408 assert timeout >= 0, timeout |
| 409 old_timeout = timeout |
| 410 timeout = lambda: old_timeout |
| 411 else: |
| 412 assert callable(timeout), timeout |
| 413 |
| 414 if maxsize is not None and not callable(maxsize): |
| 415 assert isinstance(maxsize, (int, float)), maxsize |
| 416 |
| 417 last_yield = time.time() |
| 418 while self.poll() is None: |
| 419 to = (None if timeout is None |
| 420 else max(timeout() - (time.time() - last_yield), 0)) |
| 421 t, data = self.recv_any( |
| 422 maxsize=maxsize() if callable(maxsize) else maxsize, timeout=to) |
| 423 if data or to is 0: |
| 424 yield t, data |
| 425 last_yield = time.time() |
| 426 |
| 427 # Read all remaining output in the pipes. |
| 428 # There is 3 cases: |
| 429 # - pipes get closed automatically by the calling process before it exits |
| 430 # - pipes are closed automated by the OS |
| 431 # - pipes are kept open due to grand-children processes outliving the |
| 432 # children process. |
| 433 while True: |
| 434 ms = maxsize |
| 435 if callable(maxsize): |
| 436 ms = maxsize() |
| 437 # timeout=0 is mainly to handle the case where a grand-children process |
| 438 # outlives the process started. |
| 439 t, data = self.recv_any(maxsize=ms, timeout=0) |
| 440 if not data: |
| 441 break |
| 442 yield t, data |
| 443 |
| 444 def recv_any(self, maxsize=None, timeout=None): |
| 445 """Reads from the first pipe available from stdout and stderr. |
| 446 |
| 447 Unlike wait(), it does not throw TimeoutExpired. |
| 448 |
| 449 Arguments: |
| 450 - maxsize: Maximum number of bytes to return. Defaults to MAX_SIZE. |
| 451 - timeout: If None, it is blocking. If 0 or above, will return None if no |
| 452 data is available within |timeout| seconds. |
| 453 |
| 454 Returns: |
| 455 tuple(pipename or None, str(data)). pipename is one of 'stdout' or |
| 456 'stderr'. |
| 457 """ |
| 458 # recv_multi_impl will early exit on a closed connection. Loop accordingly |
| 459 # to simplify call sites. |
| 460 while True: |
| 461 pipes = [ |
| 462 x for x in ((self.stderr, 'stderr'), (self.stdout, 'stdout')) if x[0] |
| 463 ] |
| 464 # If both stdout and stderr have the exact file handle, they are |
| 465 # effectively the same pipe. Deduplicate it since otherwise it confuses |
| 466 # recv_multi_impl(). |
| 467 if len(pipes) == 2 and self.stderr.fileno() == self.stdout.fileno(): |
| 468 pipes.pop(0) |
| 469 |
| 470 if not pipes: |
| 471 return None, None |
| 472 start = time.time() |
| 473 conns, names = zip(*pipes) |
| 474 index, data, closed = recv_multi_impl(conns, maxsize, timeout) |
| 475 if index is None: |
| 476 return index, data |
| 477 if closed: |
| 478 self._close(names[index]) |
| 479 if not data: |
| 480 # Loop again. The other pipe may still be open. |
| 481 if timeout: |
| 482 timeout -= (time.time() - start) |
| 483 continue |
| 484 |
| 485 if self.universal_newlines and data: |
| 486 data = self._translate_newlines(data) |
| 487 return names[index], data |
| 488 |
| 489 def recv_out(self, maxsize=None, timeout=None): |
| 490 """Reads from stdout synchronously with timeout.""" |
| 491 return self._recv('stdout', maxsize, timeout) |
| 492 |
| 493 def recv_err(self, maxsize=None, timeout=None): |
| 494 """Reads from stderr synchronously with timeout.""" |
| 495 return self._recv('stderr', maxsize, timeout) |
| 496 |
| 497 def terminate(self): |
| 498 """Tries to do something saner on Windows that the stdlib. |
| 499 |
| 500 Windows: |
| 501 self.detached/CREATE_NEW_PROCESS_GROUP determines what can be used: |
| 502 - If set, only SIGBREAK can be sent and it is sent to a single process. |
| 503 - If not set, in theory only SIGINT can be used and *all processes* in |
| 504 the processgroup receive it. In practice, we just kill the process. |
| 505 See http://msdn.microsoft.com/library/windows/desktop/ms683155.aspx |
| 506 The default on Windows is to call TerminateProcess() always, which is not |
| 507 useful. |
| 508 |
| 509 On Posix, always send SIGTERM. |
| 510 """ |
| 511 try: |
| 512 if subprocess.mswindows and self.detached: |
| 513 return self.send_signal(signal.CTRL_BREAK_EVENT) |
| 514 super(Popen, self).terminate() |
| 515 except OSError: |
| 516 # The function will throw if the process terminated in-between. Swallow |
| 517 # this. |
| 518 pass |
| 519 |
| 520 def kill(self): |
| 521 """Kills the process and its children if possible. |
| 522 |
| 523 Swallows exceptions and return True on success. |
| 524 """ |
| 525 if self.gid: |
| 526 try: |
| 527 os.killpg(self.gid, signal.SIGKILL) |
| 528 except OSError: |
| 529 return False |
| 530 else: |
| 531 try: |
| 532 super(Popen, self).kill() |
| 533 except OSError: |
| 534 return False |
| 535 return True |
| 536 |
| 537 def _close(self, which): |
| 538 """Closes either stdout or stderr.""" |
| 539 getattr(self, which).close() |
| 540 setattr(self, which, None) |
| 541 |
| 542 def _recv(self, which, maxsize, timeout): |
| 543 """Reads from one of stdout or stderr synchronously with timeout.""" |
| 544 conn = getattr(self, which) |
| 545 if conn is None: |
| 546 return None |
| 547 _, data, closed = recv_multi_impl([conn], maxsize, timeout) |
| 548 if closed: |
| 549 self._close(which) |
| 550 if self.universal_newlines and data: |
| 551 data = self._translate_newlines(data) |
| 552 return data |
| 553 |
| 554 |
| 555 @contextlib.contextmanager |
| 556 def set_signal_handler(signals, handler): |
| 557 """Temporarilly override signals handler. |
| 558 |
| 559 Useful when waiting for a child process to handle signals like SIGTERM, so the |
| 560 signal can be propagated to the child process. |
| 561 """ |
| 562 previous = {s: signal.signal(s, handler) for s in signals} |
| 563 try: |
| 564 yield |
| 565 finally: |
| 566 for sig, h in previous.iteritems(): |
| 567 signal.signal(sig, h) |
| 568 |
| 569 |
| 570 def call(*args, **kwargs): |
| 571 """Adds support for timeout.""" |
| 572 timeout = kwargs.pop('timeout', None) |
| 573 return Popen(*args, **kwargs).wait(timeout) |
| 574 |
| 575 |
| 576 def check_call(*args, **kwargs): |
| 577 """Adds support for timeout.""" |
| 578 retcode = call(*args, **kwargs) |
| 579 if retcode: |
| 580 raise CalledProcessError(retcode, kwargs.get('args') or args[0]) |
| 581 return 0 |
| 582 |
| 583 |
| 584 def check_output(*args, **kwargs): |
| 585 """Adds support for timeout.""" |
| 586 timeout = kwargs.pop('timeout', None) |
| 587 if 'stdout' in kwargs: |
| 588 raise ValueError('stdout argument not allowed, it will be overridden.') |
| 589 process = Popen(stdout=PIPE, *args, **kwargs) |
| 590 output, _ = process.communicate(timeout=timeout) |
| 591 retcode = process.poll() |
| 592 if retcode: |
| 593 raise CalledProcessError(retcode, kwargs.get('args') or args[0], output) |
| 594 return output |
| 595 |
| 596 |
| 597 def call_with_timeout(args, timeout, **kwargs): |
| 598 """Runs an executable; kill it in case of timeout.""" |
| 599 proc = Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, **kwargs) |
| 600 try: |
| 601 out, err = proc.communicate(timeout=timeout) |
| 602 except TimeoutExpired as e: |
| 603 out = e.output |
| 604 err = e.stderr |
| 605 proc.kill() |
| 606 proc.wait() |
| 607 return out, err, proc.returncode, proc.duration() |
OLD | NEW |