如何重现 Boost 流程文档提示的死锁?

How to reproduce deadlock hinted to by Boost process documentation?

根据Boost documentation(第'Why does the pipe not close?'节),以下代码将导致死锁:

#include <boost/process.hpp>

#include <iostream>

namespace bp = ::boost::process;

int main(void)
{
  bp::ipstream is;
  bp::child c("ls", bp::std_out > is);

  std::string line;
  while (std::getline(is, line))
  {
    std::cout << line << "\n";
  }

  return 0;
}

文档说:

This will also deadlock, because the pipe does not close when the subprocess exits. So the ipstream will still look for data even though the process has ended.

但是,我无法重现死锁(在 Linux 下)。此外,我不明白为什么首先会出现死锁。一旦子进程退出,它就会关闭管道的 write-end。管道的读端仍然可供父进程读取,并且一旦管道缓冲区中没有更多数据可用并且写端已关闭,std::getline() 将失败,对吗?如果在子进程执行期间管道缓冲区被填满,子进程将阻塞等待父进程从管道中读取足够的数据以便它可以继续。

万一上面的代码死锁,有没有简单的方法重现死锁场景?

更新:

确实,以下代码段使用 Boost 进程死锁:

#include <boost/process.hpp>
#include <iostream>

namespace bp = ::boost::process;

int main() 
{
    bp::ipstream is;
    bp::child c("/bin/bash", bp::args({"-c", "ls >&40"}), bp::posix::fd.bind(40, is.rdbuf()->pipe().native_sink()));

    std::string line;
    while (std::getline(is, line))
    {
        std::cout << line << "\n";
    }

    c.wait();

    return 0;
}

我想知道这是否真的是 Linux 下不可避免的进程生成 属性。使用 Subprocess from Facebook's Folly 库重现上述示例至少不会死锁:

#include <folly/Subprocess.h>
#include <iostream>

int main()
{
   std::vector<std::string> arguments = {"/bin/bash", "-c", "ls >&40"};

   folly::Subprocess::Options options;
   options.fd(40, STDOUT_FILENO);

   folly::Subprocess p(arguments, options);
   std::cout << p.communicate().first;
   p.wait();

   return 0;
}

Once the child process exits it closes the write-end of the pipe.

这似乎是假设。什么程序关闭什么管道?

如果 /bin/ls 这样做,

会发生什么
bp::child c("/bin/bash", bp::args({"-c", "ls; ls"}));

如果ls真的关闭了,那应该关闭两次。

也许bash 复制引擎盖下的句柄,因此子进程关闭同一管道的不同 个副本。我不确定这些语义的可靠性¹

所以,stdout 显然很适合。但是,在 linux:

上使用非标准文件描述符进行输出时,我可以重现死锁
#include <boost/process.hpp>
#include <iostream>

namespace bp = ::boost::process;

int main() {
    bp::ipstream is;
    bp::child c("/bin/bash", bp::args({"-c", "exec >&40; ls"}), bp::posix::fd.bind(40, is.rdbuf()->pipe().native_sink()));

    std::string line;
    while (std::getline(is, line)) {
        std::cout << line << "\n";
    }
}

我不确定为什么 bash 中的子进程的 "closing stdout" 行为在被重定向到 fd 时应该有不同的行为,但就是这样。

另一个演示相关死锁的好方法是:

{
    bp::child c("/bin/bash", bp::args({"-c", "ls -R /"}), bp::std_out > is);
    c.wait();
    return c.exit_code();
}

这个答案不是决定性的,但确实观察到了一些要点并在 linux 上证明了它们:

  • 似乎并非所有文件描述符都与标准输出一样涵盖
  • 在子进程异步处理它们的 IO 但调用进程同步处理它们的许多情况下都会发生死锁。

我认为后者是文档中的重点。


¹ 事实上,文档明确表明这些语义的差异是 Win32 中的问题:

It is not possible to use automatically pipe-closing in this library, because a pipe might be a file-handle (as for async pipes on windows)