手动捕获输出时`run(pipeline(`git --no-pager diff`))`命令为何挂起?
Git命令在Julia手动捕获输出时挂起的问题
这个问题此前在Julia社区论坛提出但未得到解决,出现在调试DrWatson.jl(Issue #300)的过程中。
在存在已修改文件的Git仓库中,以下代码可正常运行:
julia> run(`git --no-pager diff`)
然而,当手动捕获输出时
julia> out = Pipe(); err = Pipe(); julia> run(pipeline(`git --no-pager diff`, stderr=err, stdout=out), wait=true)
该命令永远无法执行完成,这是为什么?
复现步骤(需在带有已修改文件的Git仓库中操作)
shell> mkdir JuliaGitWithPipeHangs shell> cd JuliaGitWithPipeHangs shell> git init Initialized empty Git repository in /mnt/data/git/tmp/JuliaGitWithPipeHangs/.git/
julia> using Random; write("tmp.txt", string(rand(MersenneTwister(42), 10000)))
shell> git add tmp.txt shell> git commit -m"commit"
julia> write("tmp.txt", string(rand(MersenneTwister(42), 100)))
版本信息
julia> versioninfo() Julia Version 1.9.3 Commit bed2cd540a1 (2023-08-24 14:43 UTC) Build Info: Official julialang.org release Platform Info: OS: Linux (x86_64-linux-gnu) CPU: 8 × 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz WORD_SIZE: 64 LIBM: libopenlibm LLVM: libLLVM-14.0.6 (ORCJIT, tigerlake) Threads: 1 on 8 virtual cores
# git version 2.34.1
问题原因
核心原因是管道的读取端未被消费,导致Git进程的输出缓冲区被填满后阻塞。当你创建Pipe()对象但不读取其中的内容时,Git尝试向管道写入输出,一旦管道缓冲区耗尽,进程就会挂起等待缓冲区可用,而Julia的run命令会一直等待Git进程结束,形成死锁。
另外,--no-pager参数在这里没有起到预期的"避免阻塞"作用:即使禁用了分页器,Git在检测到标准输出不是终端(而是管道)时,输出逻辑和终端一致,但如果管道未被读取,依然会因缓冲区满而阻塞。
解决方法
有两种可靠的解决方式:
使用Julia内置的
read/readchomp捕获输出
这是Julia官方推荐的捕获外部命令输出的方式,底层会自动处理管道的读取、缓冲区管理和资源释放,无需手动操作:# 仅捕获标准输出 diff_output = read(`git --no-pager diff`, String) # 同时捕获标准输出和标准错误 diff_output, err_output = read(`git --no-pager diff`, String, stderr=String)手动处理管道时需主动读取并关闭
如果必须手动创建管道,需要在启动进程后立即异步读取管道内容,避免缓冲区阻塞,同时关闭管道的写入端告知Git输出已接收:out = Pipe() err = Pipe() # 启动进程但不等待 proc = run(pipeline(`git --no-pager diff`, stdout=out, stderr=err), wait=false) # 异步读取管道内容,避免主线程阻塞 @async begin close(out.in) # 关闭写入端,告知Git输出通道已准备好接收 read(out, String) end @async begin close(err.in) read(err, String) end # 等待进程完成 wait(proc)
内容的提问来源于stack exchange,提问作者Adomas Baliuka
相关产品推荐
相关产品推荐

