Search code examples
c++boostboost-asioboost-process

Chaining Boost Process on_exit completion handlers


I'm trying to restart the same process right after the old one exits, but after few executions my program crashes. Valgrind is reporting invalid reads and writes related to the handler, but I'm having no luck with getting closer to a fix. The sample code shows only one process, but my goal is to have a few of these processes that restart themselves running at the same time.

std::function<void(int, const std::error_code&)> on_exit;
on_exit = [this, &on_exit](int exit, const std::error_code& ec_in) {
    // error handling
    client.wait();
    client = bp::child(path_to_client, io_context, bp::on_exit=on_exit);
};
client = bp::child(path_to_client, io_context, bp::on_exit=on_exit);
io_context.run();

The client, path_to_client, and io_context are all members of a class.

Removing the call to launch the new process inside the lambda fixes the valgrind errors.


Solution

  • on_exit is a local function, but you capture it by reference. Fix it by making it live long enough, e.g. by using a member variable like client instead.

    Here's a self-contained demo program:

    #include <boost/process.hpp>
    #include <iostream>
    namespace bp = boost::process;
    
    struct ProgramX {
        boost::asio::io_context                   io_context;
        bp::child                                 client;
        unsigned                                  remaining = 0;
    
        std::function<void(int, std::error_code)> on_exit = [this](int exit, std::error_code ec) {
            client.wait();
    
            std::cout << "exit code " << exit << " (" << ec.message() << ")" << std::endl;
            // error handling...
    
            if (--remaining)
                launch();
        };
    
        void launch() {
            boost::filesystem::path  path_to_client = "/bin/sleep";
            std::vector<std::string> args           = {"1"};
            client = bp::child(path_to_client, args, io_context, bp::on_exit = on_exit);
        }
    
        void foo(unsigned how_many_runs) {
            remaining = how_many_runs;
            launch();
            io_context.run();
        }
    };
    
    int main() {
        ProgramX x;
        x.foo(5);
    }
    

    UPDATE: Bugs in Earlier Boost Versions

    When trying things online, I noticed that... it didn't work as expected.

    • Boost 1.82.0.beta1 works fine for me
    • Boost 1.81.0 doesn't (Wandbox)
    • Boost 1.79.0 doesn't (Wandbox, Coliru, Compiler Explorer doesn't have /bin/sleep)

    Here's a side-by-side on my local machine¹:

    enter image description here

    So in all likelihood, you (also) need to update to Boost 1.82.0 sooner rather than later.


    ¹ note both say 108200 but you can see me switching the release tag for just the process submodule