凉城旧巷
Python从入门到自闭,Java从自闭到放弃,数据库从删库到跑路,Linux从rm -rf到完犊子!!!

subprocess配合logging stream出现卡死

复现

def run_it(self, cmd):
    # 这里的cmd中会产生大量out数据输出到PIPE中
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True,
                         stderr=subprocess.PIPE, close_fds=True)
    log.debug('running:%s' % cmd)
    p.wait()
    if p.returncode != 0:
        log.critical("Non zero exit code:%s executing: %s" % (p.returncode, cmd))
    return p.stdout

 

原因

可能是stdout都输出在subprocess.PIPE中,导致PIPE满了

Popen.wait()
    Wait for child process to terminate. Set and return returncode attribute.
 
    Warning This will deadlock when using stdout=PIPE and/or stderr=PIPE and the child process generates enough output to a pipe such that it blocks waiting for the OS pipe buffer to accept more data. Use communicate() to avoid that.
    # 警告 当使用 stdout=PIPE 和/或 stderr=PIPE 并且子进程生成足够的输出到管道(PIPE)时,这将死锁,从而阻止等待 OS 管道缓冲区接受更多数据。 使用communication() 来避免这种情况。

 

Popen.communicate(input=None)
    Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optional input argument should be a string to be sent to the child process, or None, if no data should be sent to the child.
    # 与进程交互:将数据发送到标准输入。 从 stdout 和 stderr 读取数据,直到到达文件尾。 等待进程终止。 可选的输入参数应该是要发送到子进程的字符串,或者 None,如果没有数据应该发送到子进程
 
    communicate() returns a tuple (stdoutdata, stderrdata).
 
    Note that if you want to send data to the process’s stdin, you need to create the Popen object with stdin=PIPE. Similarly, to get anything other than None in the result tuple, you need to give stdout=PIPE and/or stderr=PIPE too.
    # 请注意,如果要将数据发送到进程的 stdin,则需要使用 stdin=PIPE 创建 Popen 对象。 同样,要在结果元组中获得除 None 之外的任何内容,您还需要提供 stdout=PIPE 和/或 stderr=PIPE 。
 
    Note The data read is buffered in memory, so do not use this method if the data size is large or unlimited.
    # 注意 读取的数据是缓存在内存中的,所以如果数据量很大或者没有限制,不要使用这种方法。

 

解决

  1. 尽量不要将大量数据输出在PIPE中

     

  2. 及时将输出数据从PIPE中取走,使用 communicate() 将输出数据从PIPE中读取出来,将数据放在内存中,此时上限便与内存相关;

    # 使用communicate()
    def run_it(self, cmd):
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True,
                             stderr=subprocess.PIPE, close_fds=True)
        
        out, err = p.communicate()
        
        p.wait()
        if p.returncode != 0:
            log.critical("Non zero exit code:%s executing: %s" % (p.returncode, cmd))
        return p.stdout
    
    # 及时从管道中取走数据, communicate()内部就是将stdout/stderr读取出来到一个list变量中
    def run_it(self, cmd):
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True,
                             stderr=subprocess.PIPE, close_fds=True)
        
        for line in iter(p.stdout.readline, b''):
            print line          # print to stdout immediately
        p.stdout.close()
        
        p.wait()
        if p.returncode != 0:
            log.critical("Non zero exit code:%s executing: %s" % (p.returncode, cmd))
        return p.stdout
    

     

  3. 如果stdout超出内存,可以考虑将输出数据写在文件中,stdout=open('out.out', 'w')

    def run_it(self, cmd):
        p = subprocess.Popen(cmd, 
                             shell=True,
                             stdout=open('out.out', 'w'), 
                             stderr=subprocess.PIPE, 
                             close_fds=True)
        
        p.wait()
        if p.returncode != 0:
            log.critical("Non zero exit code:%s executing: %s" % (p.returncode, cmd))
        return p.stdout
    
posted on 2021-09-29 14:10  凉城旧巷  阅读(670)  评论(0编辑  收藏  举报