Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bad Access in boost::future<>.then() after accessing given future

I'm developing for iOS in XCode 4.6. I'm writing a library for a service and use boost to start threads. One of my methods looks like this:

void Service::start(boost::shared_ptr<ResultListener> listener) {

    boost::future<bool> con = boost::make_future(true);
    listener->onConnected(); // Works
    boost::future<bool> initDone = con.then([&, listener](boost::future<bool>& connected) {
         listener->onConnected(); // Works
         if (!connected.get()) {
             listener->onError("...");
             return false;
         }
         listener->onError("...");  // EXC_BAD_ACCESS
         /* ... */
         return true;
    });
}

When executing this on the device I get an EXC_BAD_ACCESS at the marked line. I'm very suprised by this, since the first call to onConnected is successful and even if I add an onError call before the if that one is also working.

Being quite inexperienced with C++ I would be happy about every piece of information on what the reason is, how to debug it and how to be aware if this issue the next time around. Also I'm not quite sure which information is relevant. What I figured can be relevant from what I've found so far out there, the following might: ResultListener and Service are boost::noncopyable. I checked the reference count of the shared_ptr (using use_count) and it increases within the continuation. I'm using boost 1.53. The method is called like this

Servuce reco(/* ... */);
boost::shared_ptr<foo> f(new foo());
reco.start(f);

foo being a simple class which does nothing else but print to std::cout if a method is called.

Edit: Further snooping around let me to inspect the get() call and I found the following code in future.hpp being executed:

    // retrieving the value
    move_dest_type get()
    {
        if(!this->future_)
        {
            boost::throw_exception(future_uninitialized());
        }

        future_ptr fut_=this->future_;
        this->future_.reset();
        return fut_->get();
    }

I think this is the problem. The call to reset() seems to frees the memory of the future_ shared_ptr. My guess is, this marks the memory the continuation is still running in as not used for the OS and thus invalidates the listener pointer which is then caugt as memory access out of its scope. Is this assumption correct? Can I somehow avoid this or is this a bug in boost?

Edit 2: The following is a minimal example creating the problem:

#define BOOST_THREAD_VERSION 4
#include <boost/thread.hpp>

class Test {

public:
    void test() {
        boost::shared_ptr<Test> listener(new Test());
        boost::future<bool> con = boost::make_future(true);
        listener->foo(); // Works
        boost::future<bool> initDone = con.then([listener](boost::future<bool>& connected) {
            listener->foo(); // Works
            if (!connected.get()) {
                listener->foo();
                return false;
            }
            listener->foo();  // EXC_BAD_ACCESS
            return true;
        });
    }

    void foo() {
        std::cout << "foo";
    }
};

I added two screenshots I took in XCode to show the situation in the future in which the conitnuation is running and it seems that Mankarnas (in the comments) and me (above) are correct: It seems the memory part in which the continuation is stored is freed and therefore undefined behaviour occurs.

This is the situation before get() is called: Situation before <code>get</code> is called

This is the situation after get() was called: Situation after <code>get</code> was called

The address px points to is 0x00 afterwards.

like image 829
Stephan Avatar asked Nov 04 '22 04:11

Stephan


1 Answers

I opened a ticket against boost 1.53 and it was confirmed as a bug. It seems that future.then is not yet stable and thus not ready for production use. One advice from there was to use

#define BOOST_THREAD_DONT_PROVIDE_FUTURE_INVALID_AFTER_GET

But it was clearly stated that this feature is not stable yet (and that the documentation is lacking this bit of information).

I have switched now to use a separate thread in which I'll wait for the future and execute the proper actions then.

like image 54
Stephan Avatar answered Nov 11 '22 18:11

Stephan