使用boost :: process调用外部程序会导致调用者挂起(Linux)

时间:2016-07-19 11:31:02

标签: c++ linux boost boost-process

我使用boost :: process来调用外部程序 - 外部程序通过stdin读取输入,并写入stdout和stderr。外部程序如下(需要一个参数 - 用于调试的文件的路径)

#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

int main(int argc, char** argv)
{
    try
    {
        if (argc != 2)
        {
            throw std::logic_error("Expected two arguments");
        }

        std::ofstream ofs(argv[1]);

        std::vector<std::string> someTestInput;

        ofs << "Starting program..." << std::endl;

        // Read from cin
        {
            ofs << "Reading from cin..." << std::endl;
            std::string input;
            while (std::getline(std::cin, input))
            {
                ofs << "Received from cin: " << input << std::endl;
                someTestInput.emplace_back(input);
            }

            ofs << "Finished receiving from cin..." << std::endl;
        }

        // Error if nothing has been input
        if (someTestInput.empty())
        {
            throw std::logic_error("Expected some input and received nothing...");
        }

        ofs << "Writing to cout..." << std::endl;

        // Write to cout
        for (const auto& output : someTestInput)
        {
            std::cout << output << '\n';
        }

        ofs << "Finished!\n";
    }
    catch (std::exception& e)
    {
        std::cerr << "Error caught: " << e.what() << '\n';
        return 1;
    }

    return 0;
}

调用者需要2个以上的参数,其中一个是外部程序的路径,其余参数作为参数传递给外部程序。

它在等待进程退出时挂起,似乎外部程序正在等待stdin的EOF。

#include <memory>
#include <vector>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/process.hpp>

int main(int argc, char** argv)
{
    try
    {
        if (argc < 2)
        {
            throw std::logic_error("Expecting at least 2 arguments...");
        }

        std::vector<std::string> args;

        for (int i = 1; i < argc; ++i)
        {
            args.emplace_back(argv[i]);
        }

        std::cout << "Creating stdout, stderr pipes...\n";

        // Create pipes for stdout, stderr
        boost::process::pipe pstdout = boost::process::create_pipe();
        boost::process::pipe pstderr = boost::process::create_pipe();

        std::cout << "Mapping pipes to sources...\n";

        // Map pipe source from stdout and stderr to sources
        boost::iostreams::file_descriptor_source sourcestdout(pstdout.source, boost::iostreams::close_handle);
        boost::iostreams::file_descriptor_source sourcestderr(pstderr.source, boost::iostreams::close_handle);

        std::cout << "Setting up streams for the sources...\n";

        // And set up streams for the sources
        boost::iostreams::stream<boost::iostreams::file_descriptor_source> istdout(sourcestdout);
        boost::iostreams::stream<boost::iostreams::file_descriptor_source> istderr(sourcestderr);

        std::unique_ptr<boost::process::child> p;

        // Want to check for process result, but also need to ensure stdin handle is closed properly,
        // so place everything in separate scope
        {
            std::cout << "Mapping pipes to sinks...\n";

            // Map pipe sink from stdout and stderr to sinks
            boost::iostreams::file_descriptor_sink sinkstdout(pstdout.sink, boost::iostreams::close_handle);
            boost::iostreams::file_descriptor_sink sinkstderr(pstderr.sink, boost::iostreams::close_handle);

            std::cout << "Creating stdin pipe, mapping to source and sink...\n";

            boost::process::pipe pstdin = boost::process::create_pipe();

            // For stdin, map pipe to source and sink as before - want it to close on exiting this scope
            boost::iostreams::file_descriptor_sink sinkstdin(pstdin.sink, boost::iostreams::close_handle);
            boost::iostreams::file_descriptor_source sourcestdin(pstdin.source, boost::iostreams::close_handle);
            boost::iostreams::stream<boost::iostreams::file_descriptor_sink> ostdin(sinkstdin);

            std::cout << "Calling process... \n";

            // Call process
            p = std::unique_ptr<boost::process::child>(new boost::process::child(boost::process::execute(
                boost::process::initializers::set_args(args),
                boost::process::initializers::throw_on_error(),
                boost::process::initializers::bind_stdout(sinkstdout),
                boost::process::initializers::bind_stderr(sinkstderr),
                boost::process::initializers::bind_stdin(sourcestdin)
                )));

            std::cout << "Sending test data...\n";

            // Send some test data to cin - comment out the below to test for error case
            ostdin << "Test Input 1\n";
            ostdin << "Some\n";
            ostdin << "Useful\n";
            ostdin << "Data\n";

            std::cout << "Test data sent, exiting scope...\n";
        }

        std::cout << "Check if process has exited...\n";

        // Check if process has exited OK - if not, report errors
        if (boost::process::wait_for_exit(*p))
        {
            std::cout << "Has not exited OK, reporting problems...\n";

            // Gather output from stderr
            std::string error;
            while (std::getline(istderr, error))
            {
                std::cout << "Error: " << error << '\n';
            }

            throw std::logic_error("Problem executing TestProgram...");
        }

        std::cout << "Exited OK, here is output from the callee...\n";

        // Gather the output
        std::string output;
        while (std::getline(istdout, output))
        {
            std::cout << output << '\n';
        }
    }
    catch (std::exception& e)
    {
        std::cerr << "Error: " << e.what() << '\n';
        return 1;
    }
}

我的印象是,将我的stdin管道和相关的源/接收器放在一个范围内将保证它们被关闭,因此发送EOF。

相同的代码在Windows下完美运行(VS2013,boost_1_53)。

我正在使用boost_1_53,boost-process 0.5,gcc 4.8.2。

1 个答案:

答案 0 :(得分:1)

这种情况不会发生,因为在子进程中仍然有一个管道句柄打开;如果你明确地设置它,它只在posix上关闭(在Windows上它是自动完成的)。所以你需要添加类似的东西:

#if defined (BOOST_POSIX_API)
fcntl(pstdout.sink, F_SETFD, FD_CLOEXEC);
fcntl(pstderr.sink, F_SETFD, FD_CLOEXEC);
#endif

但是我建议使用boost.asio并异步等待子进程的退出并关闭那里的管道。

仅供参考:我一直在使用boost-process 0.6,它具有不同的界面,但使asio的内容更容易。这有望在10月/ 11月进行审核,因此很快就可能成为官方推广库。它目前处于测试阶段,因此您可能需要检查一下。