Согласно Повысить документацию (раздел «Почему канал не закрывается?»), следующий код приведет к тупику:
#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;
}
Как только дочерний процесс завершается, он закрывает конец записи канала.
Кажется, это предположение. Какая программа закрывает какой канал?
Если /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)
Других решений пока нет …