scanner.Scan()阻塞,直到退出Linux但不是Windows

时间:2018-01-29 09:56:48

标签: go io stdout child-process spawn

我正在开发一个devtool,这个工具的一个功能是生成一个子进程并读取该进程的stdout流。我需要将每行输出读入内存,以便以某种方式处理它(该工具的未来功能之一将涉及处理日志并将其发送到外部位置,如日志管理器和仪表板等),这就是我不喜欢的原因不要只做cmd.Stdout = os.Stdout

它工作正常,已经做了一段时间,但显然只在Windows上。最近我得到了一个相当令人困惑的bug report,用户报告输出不是“实时”,在Linux上进行测试时,我发现它是真的,输出只在进程退出时转储到控制台。 / p>

以下是扫描阅读器的代码,在Windows上按预期工作,但在Linux / MacOS上的Linux容器中无法正常工作(同时测试)

https://github.com/Southclaws/sampctl/blob/f639c941dd8f9ca7c7c819a152909044ad63be08/runtime/run.go#L133-L137

如果你在代码周围找到你会发现使用io.Pipe()创建阅读器的位置并绑定到cmd的Stdout / Stderr输出。

第134行是程序刚刚阻塞,直到下面的goroutine中的cmd停止运行,在第161行。

我认为这与缓冲区和刷新有关,但我不太了解Go的内部结构以确定问题。在Windows和Linux上,scanner.Scan()究竟有什么不同?为什么它在一个平台上阻止而在另一个平台上阻止?是否与线程/ goroutine的调度方式不同? (两台测试机都有多个内核,甚至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就是它挂起的地方。

2 个答案:

答案 0 :(得分:2)

默认情况下,Linux在不处于交互模式时(即不在终端中)缓冲输出,因此仅在缓冲区满时刷新输出(例如,每4096个字节,但这是定义的实现);当程序显式调用flush时(显然这里没有发生);或者当过程结束时(如你所见)。

您可以通过调整缓冲区大小来更改此默认行为。例如,通过stdbuf启动程序:

stdbuf -oO /root/.samp/runtime/0.3.7/samp03svr

-o代表stdout-e-i),O代表“关闭”(L代表“行缓存”或显式缓冲区大小的大小。)

或者,有一个unbuffer命令或script命令:

https://unix.stackexchange.com/questions/25372/turn-off-buffering-in-pipe/61833#61833

答案 1 :(得分:2)

从Y_Less的答案继续,一般的解决方案是使用伪终端。我想避免使用stdbuf或unbuffer,因为这需要依赖于外部命令。

所以我的最终解决方案是https://github.com/kr/pty,它是伪终端的Go实现。

我想我会自我回答,帮助其他Go用户通过搜索找到这个问题。