Python非阻塞管道通信中stdout/stderr内容丢失问题排查
问题
我有两个通过管道连接的Python进程,管道创建代码如下:
read_file_descriptor, write_file_descriptor = os.pipe() os.set_blocking(read_file_descriptor, False) os.set_inheritable(read_file_descriptor, True)
父进程fork出子进程,子进程从读取文件描述符读取数据,核心代码如下:
lines = [] read_handle = os.fdopen(read_file_descriptor) while True: line = read_handle.readline() if _TERMINATION_CHAR in line: # trigger final upload line = line[: line.index(_TERMINATION_CHAR)] received_stream_termination = True elif len(line) == 0: # The line would at least have the newline char if it was a blank. # no more to read right now; just keep looping and trying to read # until the timeout or the termination character tell us to stop time.sleep(0.01) continue fp.write(line) fp.flush() if received_stream_termination: break # handle lines...
同时父进程将stdout和stderr重定向到write_file_descriptor,完成任务后执行:
logger.info("Cleaning up") print(_TERMINATION_CHAR) # tell the reader that the stream is done sys.stdout.flush() sys.stderr.flush()
进程运行时已设置环境变量PYTHONUNBUFFERED=1,我通过让父进程写入约20k行数据(stdout和stderr各10k行交错)进行压力测试:
for i in range(10000): time.sleep(0.01) print(f"From stdout: {i}") print(f"From stderr: {i}", file=sys.stderr) return a + b
但子进程接收的内容末尾大量丢失,仅能看到:
# ... there are more lines before this. Nothing seems to be missing up to this point From stdout: 9012 From stderr: 9012 From stdout: 9013 From stderr: 9013 From stdout: 9014 From stderr: 9014 From stdout: 9015 From stderr: 9015 2022-11-15 23:01:28,536 - INFO : Cleaning up
日志信息能正常接收,请问该问题的原因是什么?
原因分析
- 管道写端未关闭,子进程无法感知数据结束:父进程写完终止符后仅flush了stdout和stderr,但未关闭
write_file_descriptor。子进程的非阻塞readline()遇到暂时无数据时只会sleep重试,不会认为传输结束。当父进程退出时系统会自动关闭写端,但此时子进程可能已经因收到终止符提前break,没机会读取管道中剩余的内容。 - 终止符发送时机过早:父进程写完测试数据后立刻发送终止符,此时stdout/stderr的内容可能还没完全写入管道(即使开启了
PYTHONUNBUFFERED,操作系统层面仍可能存在缓冲延迟)。子进程收到终止符后直接跳出循环,导致管道中未读取的末尾数据被丢弃。 - 非阻塞读取逻辑缺失EOF处理:子进程循环仅靠终止符触发break,没有处理管道写端关闭的情况(此时
readline()会返回空字符串代表EOF)。如果父进程退出后关闭写端,子进程应该在收到EOF时停止循环,而非一直sleep等待,但当前逻辑没做这个判断,导致部分数据没被处理就提前终止。
内容的提问来源于stack exchange,提问作者augray
相关产品推荐
相关产品推荐

