如果 stderr 和 stdout 被重定向,进程挂起
Process hangs, if stderr and stdout are redirected
我正在尝试调用 cmake 并将输出重定向到管道。
重现:
git clone https://github.com/avast/retdec
(似乎每个CMake-Project,gradle项目也不行)
mkdir build&&cd build
- 添加文件
test.hs
:
import System.Posix.IO
import System.Process
import System.Exit
import System.IO
main :: IO ()
main = do
(code, content) <- createProcessRedirected "cmake" ["..", "-DCMAKE_INSTALL_PREFIX=/opt/root/"]
case code of
ExitFailure _ -> do
putStrLn ("OOPS" ++ content)
return ()
_ -> do
putStrLn "After cmake"
(code2, content2) <- createProcessRedirected "make" ["install", "-j8"]
case code2 of
ExitFailure _ -> do
putStrLn ("OOPS" ++ content2)
return ()
_ -> do
putStrLn "SUCCESS"
return ()
-- Based on
createProcessRedirected :: String -> [String] -> IO (ExitCode, String)
createProcessRedirected command args = do
(p_r, p_w) <- System.Posix.IO.createPipe
h_r <- fdToHandle p_r
h_w <- fdToHandle p_w
(_, _, _, h_proc) <- createProcess (proc command args) {
std_out = UseHandle h_w,
std_err = UseHandle h_w,
create_group = False,
delegate_ctlc = True}
ret_code <- waitForProcess h_proc
content <- hGetContents h_r
return (ret_code, content)
编译这个文件(ghc test.hs -Wall -Wextra
)
如果我现在尝试执行 ./test
,它会构建一段时间(我可以看到 c++/make 进程是如何产生的),但在某一时刻它会简单地停止。
然后我有这个过程树:
test
-> make install -j8
-> /usr/bin/cmake -P cmake_install.cmake
如果我附加 gdb 和 运行 bt
(前 6 行):
#0 0x00007f4ac7f8c7f7 in __GI___libc_write (fd=1, buf=0x5607747dd2f0, nbytes=60) at ../sysdeps/unix/sysv/linux/write.c:26
#1 0x00007f4ac7f1068d in _IO_new_file_write (f=0x7f4ac80865a0 <_IO_2_1_stdout_>, data=0x5607747dd2f0, n=60) at fileops.c:1181
#2 0x00007f4ac7f0fa06 in new_do_write (fp=0x7f4ac80865a0 <_IO_2_1_stdout_>, data=0x5607747dd2f0 "-- Up-to-date: /opt/root/include/retdec/llvm/IR/OptBisect.h\ns.h\nexYAML.h\n.h\nnager.h\n\nayer.h\n.h\n\nt execute permission?\nif(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)\n set(CMAKE_INSTALL_SO_NO_EXE \"0\")\nendif()"..., to_do=to_do@entry=60) at /usr/src/debug/glibc-2.34-11.fc35.x86_64/libio/libioP.h:947
#3 0x00007f4ac7f11729 in _IO_new_do_write (to_do=60, data=<optimized out>, fp=<optimized out>) at fileops.c:423
#4 _IO_new_do_write (fp=<optimized out>, data=<optimized out>, to_do=60) at fileops.c:423
#5 0x00007f4ac7f0f828 in _IO_new_file_sync (fp=0x7f4ac80865a0 <_IO_2_1_stdout_>) at fileops.c:799
然后我尝试继续 运行 并在 10 秒后再次中断,我得到完全相同的回溯。
但是如果我注释掉这两行:
std_out = UseHandle h_w,
std_err = UseHandle h_w,
有效。
我做错了什么?
管道的缓冲区大小不是无限的。您正在创建一个死锁,子进程挂起,因为它试图写入已满的缓冲区,而您的父进程在子进程完成之前不会尝试从缓冲区读取任何内容。要解决此问题,您需要在子进程仍然 运行 时使用另一个线程从缓冲区中读取。最简单的方法是使用 readProcess
或类似函数代替 createProcess
。如果这不能给你足够的灵活性来做你想做的事,那么你需要自己创建和管理另一个线程,你可以通过查看 readProcess
的实现方式来了解如何做。
我正在尝试调用 cmake 并将输出重定向到管道。
重现:
git clone https://github.com/avast/retdec
(似乎每个CMake-Project,gradle项目也不行)mkdir build&&cd build
- 添加文件
test.hs
:
import System.Posix.IO
import System.Process
import System.Exit
import System.IO
main :: IO ()
main = do
(code, content) <- createProcessRedirected "cmake" ["..", "-DCMAKE_INSTALL_PREFIX=/opt/root/"]
case code of
ExitFailure _ -> do
putStrLn ("OOPS" ++ content)
return ()
_ -> do
putStrLn "After cmake"
(code2, content2) <- createProcessRedirected "make" ["install", "-j8"]
case code2 of
ExitFailure _ -> do
putStrLn ("OOPS" ++ content2)
return ()
_ -> do
putStrLn "SUCCESS"
return ()
-- Based on
createProcessRedirected :: String -> [String] -> IO (ExitCode, String)
createProcessRedirected command args = do
(p_r, p_w) <- System.Posix.IO.createPipe
h_r <- fdToHandle p_r
h_w <- fdToHandle p_w
(_, _, _, h_proc) <- createProcess (proc command args) {
std_out = UseHandle h_w,
std_err = UseHandle h_w,
create_group = False,
delegate_ctlc = True}
ret_code <- waitForProcess h_proc
content <- hGetContents h_r
return (ret_code, content)
编译这个文件(ghc test.hs -Wall -Wextra
)
如果我现在尝试执行 ./test
,它会构建一段时间(我可以看到 c++/make 进程是如何产生的),但在某一时刻它会简单地停止。
然后我有这个过程树:
test
-> make install -j8
-> /usr/bin/cmake -P cmake_install.cmake
如果我附加 gdb 和 运行 bt
(前 6 行):
#0 0x00007f4ac7f8c7f7 in __GI___libc_write (fd=1, buf=0x5607747dd2f0, nbytes=60) at ../sysdeps/unix/sysv/linux/write.c:26
#1 0x00007f4ac7f1068d in _IO_new_file_write (f=0x7f4ac80865a0 <_IO_2_1_stdout_>, data=0x5607747dd2f0, n=60) at fileops.c:1181
#2 0x00007f4ac7f0fa06 in new_do_write (fp=0x7f4ac80865a0 <_IO_2_1_stdout_>, data=0x5607747dd2f0 "-- Up-to-date: /opt/root/include/retdec/llvm/IR/OptBisect.h\ns.h\nexYAML.h\n.h\nnager.h\n\nayer.h\n.h\n\nt execute permission?\nif(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)\n set(CMAKE_INSTALL_SO_NO_EXE \"0\")\nendif()"..., to_do=to_do@entry=60) at /usr/src/debug/glibc-2.34-11.fc35.x86_64/libio/libioP.h:947
#3 0x00007f4ac7f11729 in _IO_new_do_write (to_do=60, data=<optimized out>, fp=<optimized out>) at fileops.c:423
#4 _IO_new_do_write (fp=<optimized out>, data=<optimized out>, to_do=60) at fileops.c:423
#5 0x00007f4ac7f0f828 in _IO_new_file_sync (fp=0x7f4ac80865a0 <_IO_2_1_stdout_>) at fileops.c:799
然后我尝试继续 运行 并在 10 秒后再次中断,我得到完全相同的回溯。
但是如果我注释掉这两行:
std_out = UseHandle h_w,
std_err = UseHandle h_w,
有效。
我做错了什么?
管道的缓冲区大小不是无限的。您正在创建一个死锁,子进程挂起,因为它试图写入已满的缓冲区,而您的父进程在子进程完成之前不会尝试从缓冲区读取任何内容。要解决此问题,您需要在子进程仍然 运行 时使用另一个线程从缓冲区中读取。最简单的方法是使用 readProcess
或类似函数代替 createProcess
。如果这不能给你足够的灵活性来做你想做的事,那么你需要自己创建和管理另一个线程,你可以通过查看 readProcess
的实现方式来了解如何做。