Search code examples
c++boostfuturefreezeboost-process

Program hangs after calling std::future.get() using Boost.Process for asynchronous


I am creating a program that executes multiple executables asynchronously. My problem is that when I call the get() function from std::future, my program hangs up with no error.

I am using Boost.Process for managing the processes and also wxWidgets elsewhere.

My program creates a pointer to a new BProcess containing an std::future. A timer function later checks every few milliseconds to see if each process has completed.

class BProcess
{
public:
    BProcess();
    boost::asio::io_service ios;
    boost::process::child child_process;
    std::future<std::string> future_result;
};

void Module::Execute() //done for each process
{
    this->process_handle = new BProcess();
    this->process_handle->child_process = boost::process::child(sfilename, boost::process::std_out > this->process_handle->future_result, this->process_handle->ios);
}

void Timer::Notify()
{
    //done in loop for each Module - hangs after first module finishes
    if (!Modules[i].process_handle->child_process.running())
    {
        std::string testStr = Modules[i].process_handle->future_result.get();
    }
}

EDIT: Here is the output of 'where' from gdb: I wanted to try to figure out what was happening. Does this make sense to anyone?

Timer Started!
Module 0 set to run.
Module 1 set to run.
Module 2 set to run.
You checked/unchecked that item: 1
Executing Module #0
Detaching after fork from child process 17066.
Detaching after fork from child process 17069.
You checked/unchecked that item: 1
Executing Module #1
Detaching after fork from child process 17071.
Detaching after fork from child process 17072.
You checked/unchecked that item: 1
Executing Module #2
Detaching after fork from child process 17074.
Detaching after fork from child process 17075.
[New Thread 0xf5bffb40 (LWP 17078)]
[Thread 0xf33ffb40 (LWP 17063) exited]
Done Executing Module #0
^C
Thread 1 "SecureIT" received signal SIGINT, Interrupt.
0xf7fd3db9 in __kernel_vsyscall ()
Missing separate debuginfos, use: dnf debuginfo-install expat-2.2.4-1.fc26.i686 fontconfig-2.12.6-3.fc26.i686 gdk-pixbuf2-2.36.9-1.fc26.i686 gdk-pixbuf2-modules-2.36.9-1.fc26.i686 libblkid-2.30.2-1.fc26.i686 libffi-3.1-12.fc26.i686 libgcc-7.2.1-2.fc26.i686 libglvnd-0.2.999-24.20170818git8d4d03f.fc26.i686 libglvnd-egl-0.2.999-24.20170818git8d4d03f.fc26.i686 libglvnd-glx-0.2.999-24.20170818git8d4d03f.fc26.i686 libmount-2.30.2-1.fc26.i686 libselinux-2.6-7.fc26.i686 libstdc++-7.2.1-2.fc26.i686 libuuid-2.30.2-1.fc26.i686 pango-1.40.12-1.fc26.i686 pcre-8.41-1.fc26.i686
(gdb) where
#0  0xf7fd3db9 in __kernel_vsyscall ()
#1  0xf6f6e327 in syscall () at /lib/libc.so.6
#2  0xf7166fdb in std::__atomic_futex_unsigned_base::_M_futex_wait_until(unsigned int*, unsigned int, bool, std::chrono::duration<long long, std::ratio<1ll, 1ll> >, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> >) () at /lib/libstdc++.so.6
#3  0x080cf683 in std::__atomic_futex_unsigned<2147483648u>::_M_load_and_test_until(unsigned int, unsigned int, bool, std::memory_order, bool, std::chrono::duration<long long, std::ratio<1ll, 1ll> >, std::chrono::duration<long long, std::ratio<1ll, 1000000000ll> >) (this=0x8b804e4, __assumed=0, __operand=1, __equal=true, __mo=std::memory_order_acquire, __has_timeout=false, __s=..., __ns=...) at /usr/include/c++/7/bits/atomic_futex.h:102
#4  0x080cf09b in std::__atomic_futex_unsigned<2147483648u>::_M_load_and_test(unsigned int, unsigned int, bool, std::memory_order) (this=0x8b804e4, __assumed=0, __operand=1, __equal=true, __mo=std::memory_order_acquire) at /usr/include/c++/7/bits/atomic_futex.h:122
#5  0x080ce61a in std::__atomic_futex_unsigned<2147483648u>::_M_load_when_equal(unsigned int, std::memory_order) (__mo=std::memory_order_acquire, __val=1, this=0x8b804e4) at /usr/include/c++/7/bits/atomic_futex.h:162
#6  0x080ce61a in std::__future_base::_State_baseV2::wait() (this=0x8b804dc) at /usr/include/c++/7/future:337
#7  0x080cf592 in std::__basic_future<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::_M_get_result() const (this=0x8b80c48) at /usr/include/c++/7/future:717
#8  0x080ceff8 in std::future<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::get() (this=0x8b80c48)
    at /usr/include/c++/7/future:796
#9  0x080cdc8b in ExecuteTimer::Notify() (this=0x8bbd460) at ExecuteTimer.cpp:35
#10 0x0833efbf in timeout_callback ()
#11 0xf7415c13 in g_timeout_dispatch (source=source@entry=0x8b80c80, callback=0x833ef80 <timeout_callback>, user_data=0x8bbde00) at gmain.c:4715
#12 0xf7414fc9 in g_main_dispatch (context=0x8a1f698) at gmain.c:3234
#13 0xf7414fc9 in g_main_context_dispatch (context=context@entry=0x8a1f698) at gmain.c:3899
#14 0xf74153f0 in g_main_context_iterate (context=0x8a1f698, block=block@entry=1, dispatch=dispatch@entry=1, self=<optimized out>) at gmain.c:3972
#15 0xf74157a1 in g_main_loop_run (loop=loop@entry=0x8abf0f0) at gmain.c:4168
#16 0xf7afcc50 in IA__gtk_main () at gtkmain.c:1268
#17 0x08335fc5 in wxGUIEventLoop::DoRun() ()
#18 0x083c0393 in wxEventLoopBase::Run() ()
#19 0x08391921 in wxAppConsoleBase::MainLoop() ()
#20 0x084071db in wxEntry(int&, wchar_t**) ()
#21 0x08083e55 in main(int, char**) (argc=1, argv=0xffffd194) at main.cpp:13
(gdb) 

Any help would be appreciated. Tell me if I'm not showing enough code.


Solution

  • I think you might simply be forgetting/neglecting to run the io_service.

    Here's rough sketch.

    Note there are multiple design issues:

    • Checking running() is racy. If it never ran, you're toast. If you gad already gotten the future value before, you're toast.
    • Why are you polling on a timer, when you run processes asynchronously anyways? You could just respond to the completion event.
    • Law Of Demeter should probably apply to your types

    Live On Coliru

    #include <boost/process.hpp>
    #include <boost/process/async.hpp>
    #include <list>
    #include <memory>
    
    struct BProcess {
        BProcess(boost::asio::io_service& svc) : ios(svc) {}
    
        void Execute(std::string const& sfilename) {
            namespace bp = boost::process;
            child_process = bp::child(sfilename, bp::std_out > output, ios);
        }
    
        boost::asio::io_service& ios;
        boost::process::child child_process;
        std::future<std::string> output;
    };
    
    struct Module { 
        Module(boost::asio::io_service& svc) : ios(svc), process(new BProcess(svc)) {}
    
        void Execute(std::string const& sfilename) {
            process->Execute(sfilename);
        }
    
        bool running() const { return process->child_process.running(); }
        std::string get_output() const { return process->output.get(); }
    
      private:
        boost::asio::io_service& ios;
        std::unique_ptr<BProcess> process;
    };
    
    #include <iostream>
    
    struct Timer {
        std::list<Module> Modules;
    
        void Notify() {
            for (auto& m : Modules) {
                if (!m.running()) {
                    std::string testStr = m.get_output();
                    std::cout << "Got " << testStr.size() << " bytes of output\n";
                    std::cout << "---- '" << testStr << "' ---\n";
                }
            }
        }
    };
    
    int main() {
        boost::asio::io_service ios;
    
        Timer timer;
        timer.Modules.emplace_back(ios);
        timer.Modules.back().Execute("/usr/bin/find");
    
        timer.Modules.emplace_back(ios);
        timer.Modules.back().Execute("/usr/bin/gcc");
    
        for (auto& m : timer.Modules)
            m.Execute("/usr/bin/find");
    
        ios.run();
        timer.Notify();
    }