scanner.Scan() 阻塞直到 Linux 退出,但 Windows 不退出
scanner.Scan() blocking until exit on Linux but not Windows
我正在开发一个 devtool,该工具的功能之一涉及生成子进程并读取该进程的标准输出流。我需要将每一行输出读入内存以便以某种方式处理它(该工具的未来功能之一将涉及处理日志并将它们发送到外部位置,例如日志管理器和仪表板等)所以这就是我不这样做的原因不要简单地做 cmd.Stdout = os.Stdout
)
它运行良好,已经运行了一段时间,但显然只在 Windows 上运行。我最近遇到一个相当混乱的问题 bug report,用户报告输出不是 "real time",在 Linux 上测试后,我发现这是真的,并且输出仅在以下情况下转储到控制台进程退出。
这是扫描 reader 的代码,在 Windows 上按预期工作,但在 Linux 上或在 Windows/MacOS 上的 Linux 容器中不工作(两者都测试了)
如果您查看代码,您会发现 reader 是使用 io.Pipe() 创建并绑定到 cmd 的 Stdout/Stderr 输出的位置。
第 134 行是程序阻塞的地方,直到下面 goroutine 中的 cmd
在第 161 行停止 运行。
我假设这与缓冲区和刷新有关,但我对 Go 的内部结构了解不够,无法真正查明问题所在。 Windows 和 Linux 上的 scanner.Scan()
到底有什么不同?为什么它在一个平台上阻塞而在另一个平台上不阻塞?是否与 threads/goroutines 的安排不同有关? (两台测试机都有多个核心,即使 Docker 容器也有 4 个 vCPU)
这里的问题供参考:https://github.com/Southclaws/sampctl/issues/100
我真的被这个问题难住了,希望得到一些帮助来解决这个问题!
编辑:
于是又折腾了一番,还是没有解决。我尝试使用 Python 脚本并得到相同的结果,stdout 在定向到 tty 时工作正常,但当它被进程读取时它只是挂起:
from subprocess import Popen, PIPE
from time import sleep
p = Popen(
['/root/.samp/runtime/0.3.7/samp03svr'],
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
shell=False,
cwd="/root/.samp/runtime/0.3.7/")
while True:
print "attempting to read a line"
output = p.stdout.read()
print "read a line"
if not output:
print '[No more data]'
break
print output
attempting to read a line
挂在那里。
默认情况下,Linux 在不处于交互模式时(即不在终端中)缓冲输出,因此只有在缓冲区已满时才会刷新输出(例如,每 4096 字节,但这是实现定义的);当程序显式调用 flush
时(这里显然没有发生);或者当进程结束时(如您所见)。
您可以通过调整缓冲区大小来更改此默认行为。例如,通过 stdbuf
:
启动程序
stdbuf -oO /root/.samp/runtime/0.3.7/samp03svr
-o
对于 stdout
(还有 -e
和 -i
),O
对于 "Off"(对于 L
"line buffered" 或显式缓冲区大小的大小)。
或者有一个 unbuffer
命令或 script
命令:
https://unix.stackexchange.com/questions/25372/turn-off-buffering-in-pipe/61833#61833
继续 Y_Less 的回答,一般的解决方案是使用 pseudo-terminal。我想避免使用 stdbuf 或 unbuffer,因为这需要取决于存在的外部命令。
所以我的最终解决方案是 https://github.com/kr/pty 这是 pseudo-terminal.
的 Go 实现
只是想 self-answer 帮助任何其他 Go 用户通过搜索找到这个问题。
我正在开发一个 devtool,该工具的功能之一涉及生成子进程并读取该进程的标准输出流。我需要将每一行输出读入内存以便以某种方式处理它(该工具的未来功能之一将涉及处理日志并将它们发送到外部位置,例如日志管理器和仪表板等)所以这就是我不这样做的原因不要简单地做 cmd.Stdout = os.Stdout
)
它运行良好,已经运行了一段时间,但显然只在 Windows 上运行。我最近遇到一个相当混乱的问题 bug report,用户报告输出不是 "real time",在 Linux 上测试后,我发现这是真的,并且输出仅在以下情况下转储到控制台进程退出。
这是扫描 reader 的代码,在 Windows 上按预期工作,但在 Linux 上或在 Windows/MacOS 上的 Linux 容器中不工作(两者都测试了)
如果您查看代码,您会发现 reader 是使用 io.Pipe() 创建并绑定到 cmd 的 Stdout/Stderr 输出的位置。
第 134 行是程序阻塞的地方,直到下面 goroutine 中的 cmd
在第 161 行停止 运行。
我假设这与缓冲区和刷新有关,但我对 Go 的内部结构了解不够,无法真正查明问题所在。 Windows 和 Linux 上的 scanner.Scan()
到底有什么不同?为什么它在一个平台上阻塞而在另一个平台上不阻塞?是否与 threads/goroutines 的安排不同有关? (两台测试机都有多个核心,即使 Docker 容器也有 4 个 vCPU)
这里的问题供参考:https://github.com/Southclaws/sampctl/issues/100
我真的被这个问题难住了,希望得到一些帮助来解决这个问题!
编辑:
于是又折腾了一番,还是没有解决。我尝试使用 Python 脚本并得到相同的结果,stdout 在定向到 tty 时工作正常,但当它被进程读取时它只是挂起:
from subprocess import Popen, PIPE
from time import sleep
p = Popen(
['/root/.samp/runtime/0.3.7/samp03svr'],
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
shell=False,
cwd="/root/.samp/runtime/0.3.7/")
while True:
print "attempting to read a line"
output = p.stdout.read()
print "read a line"
if not output:
print '[No more data]'
break
print output
attempting to read a line
挂在那里。
默认情况下,Linux 在不处于交互模式时(即不在终端中)缓冲输出,因此只有在缓冲区已满时才会刷新输出(例如,每 4096 字节,但这是实现定义的);当程序显式调用 flush
时(这里显然没有发生);或者当进程结束时(如您所见)。
您可以通过调整缓冲区大小来更改此默认行为。例如,通过 stdbuf
:
stdbuf -oO /root/.samp/runtime/0.3.7/samp03svr
-o
对于 stdout
(还有 -e
和 -i
),O
对于 "Off"(对于 L
"line buffered" 或显式缓冲区大小的大小)。
或者有一个 unbuffer
命令或 script
命令:
https://unix.stackexchange.com/questions/25372/turn-off-buffering-in-pipe/61833#61833
继续 Y_Less 的回答,一般的解决方案是使用 pseudo-terminal。我想避免使用 stdbuf 或 unbuffer,因为这需要取决于存在的外部命令。
所以我的最终解决方案是 https://github.com/kr/pty 这是 pseudo-terminal.
的 Go 实现只是想 self-answer 帮助任何其他 Go 用户通过搜索找到这个问题。