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

时间:2017-08-21 11:07:23

标签: c++ boost boost-process

根据Boost documentation(部分'为什么管道未关闭?'),以下代码将导致死锁:

#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;
}

文档说:

  

这也会死锁,因为子进程退出时管道不会关闭。因此,即使进程已经结束,ipstream仍会查找数据。

但是,我无法重现死锁(在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下进程产生的一些不可避免的属性。使用Facebook Subprocess库中的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;
}

1 个答案:

答案 0 :(得分:2)

  

子进程退出后,它将关闭管道的写入端。

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

如果/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中子进程的“关闭stdout”行为在被重定向到fd时应该有不同的行为,但是你去了。

另一种证明相关死锁的好方法是:

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

这个答案并不是决定性的,但确实观察了一些观点并在linux上进行了演示:

  • 并非所有文件描述符都与stdout相同。
  • 在子进程异步处理IO的许多场景中都会出现死锁,但调用进程会同步处理它们。

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

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

  

在此库中无法自动使用管道关闭,因为管道可能是文件句柄(对于Windows上的异步管道)