Using module 'subprocess' with timeout

后端 未结 29 2406
旧巷少年郎
旧巷少年郎 2020-11-21 15:15

Here\'s the Python code to run an arbitrary command returning its stdout data, or raise an exception on non-zero exit codes:

proc = subprocess.P         


        
29条回答
  •  独厮守ぢ
    2020-11-21 16:04

    Once you understand full process running machinery in *unix, you will easily find simplier solution:

    Consider this simple example how to make timeoutable communicate() meth using select.select() (available alsmost everythere on *nix nowadays). This also can be written with epoll/poll/kqueue, but select.select() variant could be a good example for you. And major limitations of select.select() (speed and 1024 max fds) are not applicapable for your task.

    This works under *nix, does not create threads, does not uses signals, can be lauched from any thread (not only main), and fast enought to read 250mb/s of data from stdout on my machine (i5 2.3ghz).

    There is a problem in join'ing stdout/stderr at the end of communicate. If you have huge program output this could lead to big memory usage. But you can call communicate() several times with smaller timeouts.

    class Popen(subprocess.Popen):
        def communicate(self, input=None, timeout=None):
            if timeout is None:
                return subprocess.Popen.communicate(self, input)
    
            if self.stdin:
                # Flush stdio buffer, this might block if user
                # has been writing to .stdin in an uncontrolled
                # fashion.
                self.stdin.flush()
                if not input:
                    self.stdin.close()
    
            read_set, write_set = [], []
            stdout = stderr = None
    
            if self.stdin and input:
                write_set.append(self.stdin)
            if self.stdout:
                read_set.append(self.stdout)
                stdout = []
            if self.stderr:
                read_set.append(self.stderr)
                stderr = []
    
            input_offset = 0
            deadline = time.time() + timeout
    
            while read_set or write_set:
                try:
                    rlist, wlist, xlist = select.select(read_set, write_set, [], max(0, deadline - time.time()))
                except select.error as ex:
                    if ex.args[0] == errno.EINTR:
                        continue
                    raise
    
                if not (rlist or wlist):
                    # Just break if timeout
                    # Since we do not close stdout/stderr/stdin, we can call
                    # communicate() several times reading data by smaller pieces.
                    break
    
                if self.stdin in wlist:
                    chunk = input[input_offset:input_offset + subprocess._PIPE_BUF]
                    try:
                        bytes_written = os.write(self.stdin.fileno(), chunk)
                    except OSError as ex:
                        if ex.errno == errno.EPIPE:
                            self.stdin.close()
                            write_set.remove(self.stdin)
                        else:
                            raise
                    else:
                        input_offset += bytes_written
                        if input_offset >= len(input):
                            self.stdin.close()
                            write_set.remove(self.stdin)
    
                # Read stdout / stderr by 1024 bytes
                for fn, tgt in (
                    (self.stdout, stdout),
                    (self.stderr, stderr),
                ):
                    if fn in rlist:
                        data = os.read(fn.fileno(), 1024)
                        if data == '':
                            fn.close()
                            read_set.remove(fn)
                        tgt.append(data)
    
            if stdout is not None:
                stdout = ''.join(stdout)
            if stderr is not None:
                stderr = ''.join(stderr)
    
            return (stdout, stderr)
    

提交回复
热议问题