Как воспроизвести тупик, на который намекает документация процесса Boost?

Согласно Повысить документацию (раздел «Почему канал не закрывается?»), следующий код приведет к тупику:

#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). Кроме того, я не понимаю, почему тупик возник в первую очередь. Как только дочерний процесс завершается, он закрывает записи конец трубы. Конец чтения канала все еще будет доступен для чтения родительским процессом, и 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? Воспроизведение приведенного выше примера с использованием подпроцесс из Фейсбука безрассудство библиотека хотя бы не тупиковая

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

6

Решение

Как только дочерний процесс завершается, он закрывает конец записи канала.

Кажется, это предположение. Какая программа закрывает какой канал?

Если /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 должно вести себя по-другому, когда оно было перенаправлено на fd, но вы идете.

Еще один хороший способ продемонстрировать связанный тупик:

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

Этот ответ не является окончательным, но он соблюдает некоторые моменты и демонстрирует их в Linux:

  • не все файловые дескрипторы покрыты так же, как стандартный вывод
  • Взаимные блокировки могут возникать во многих случаях, когда подпроцессы обрабатывают свои операции ввода-вывода асинхронно, но вызывающий процесс обрабатывает их синхронно.

Я думаю, что последний был пункт в документации.


¹ действительно, документация явно предполагает, что разница в этих семантиках является проблемой в Win32:

Невозможно использовать автоматическое закрытие канала в этой библиотеке, потому что канал может быть дескриптором файла (как для асинхронных каналов в Windows)

2

Другие решения

Других решений пока нет …