特定SVN路径下proc.stderr.readlines()调用挂起问题排查
问题分析与解决方案
哎,这个问题我太熟了!你碰到的stderr.readlines()永久挂起,本质是Python子进程管道的缓冲区阻塞导致的,结合你的代码和场景,具体原因和解决办法如下:
为什么会挂起?
- 管道读写顺序踩坑:你用
Popen同时捕获了stdout和stderr的管道,但代码里先读stderr。如果那个特定路径下svn ls的stdout输出量比较大,stdout的缓冲区会被填满,子进程就会卡在那里没法继续输出,自然stderr也没法关闭管道,你的readlines()就会一直等EOF,永远挂着。而终端执行没问题,是因为终端会同时处理两个输出流,不会出现这种单向缓冲区堵死的情况。 readlines()的特性:这个方法会一直等到子进程彻底关闭对应管道才会返回,要是子进程因为stdout阻塞没退出,stderr管道就关不了,那你就只能一直等。shell=True的潜在干扰:虽然不是主因,但用shell=True会多套一层shell进程,可能带来额外的IO不确定性,没必要的话尽量关掉。
怎么解决?
给你两个靠谱的方案,优先试第一个,简单又高效:
方案1:用communicate()替代单独读管道(推荐)
Popen.communicate()会同时处理stdout和stderr的读取,完美避免管道阻塞问题,而且它会自动等待子进程结束。修改你的list方法:
def list(self, rel_path=None, retries=5): url = self.url if self.url.endswith('/') else self.url + '/' if rel_path: url = '{}{}'.format(url, rel_path) retries_max = retries + 1 for i in range(1, retries_max): # 把shell=True改成shell=False,更安全且避免额外进程 proc = Popen(['svn', 'ls', url], shell=False, stdout=PIPE, stderr=PIPE, universal_newlines=True) # 用communicate同时读取两个流,还能加timeout防止极端情况(Python3.3+支持) output, error = proc.communicate(timeout=30) exit_code = proc.returncode if exit_code != 0: if i == retries_max - 1: raise SvnException(error) logger.warning('svn error occurred, retrying {}/{}'.format(i, retries_max - 1)) sleep(1) continue else: # 按行处理输出并yield for line in output.strip().split('\n'): if line: yield line break
方案2:用线程并行读两个管道(适合需要实时输出的场景)
如果必须实时处理输出,不能等子进程结束再读,可以用两个线程分别读取stdout和stderr,避免单线程的顺序问题:
import threading def read_stream(stream, collector): # 迭代读取每一行,直到流结束 for line in iter(stream.readline, ''): collector.append(line.strip()) stream.close() def list(self, rel_path=None, retries=5): url = self.url if self.url.endswith('/') else self.url + '/' if rel_path: url = '{}{}'.format(url, rel_path) retries_max = retries + 1 for i in range(1, retries_max): proc = Popen(['svn', 'ls', url], shell=False, stdout=PIPE, stderr=PIPE, universal_newlines=True) error_lines = [] output_lines = [] # 启动线程读取stderr stderr_thread = threading.Thread(target=read_stream, args=(proc.stderr, error_lines)) stderr_thread.start() # 主线程读取stdout for line in iter(proc.stdout.readline, ''): stripped_line = line.strip() if stripped_line: output_lines.append(stripped_line) # 等待stderr线程完成,再获取退出码 stderr_thread.join() exit_code = proc.wait() if exit_code != 0: if i == retries_max - 1: raise SvnException(error_lines) logger.warning('svn error occurred, retrying {}/{}'.format(i, retries_max - 1)) sleep(1) continue else: for line in output_lines: yield line break
额外小建议
- 加上
timeout:不管用哪种方法,都尽量加个超时时间,防止极端情况下无限等待,比如communicate(timeout=30)或者proc.wait(timeout=30)。 - 检查特定路径的输出:可以先在终端执行
svn ls那个路径,看看输出量是不是特别大,或者有没有特殊的错误输出,这能帮你更好地定位问题。
先试试方案1,应该能直接解决你那个特定路径的挂起问题!
内容的提问来源于stack exchange,提问作者user2678074
相关产品推荐
相关产品推荐

