5

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.

Stephan
  • 7,360
  • 37
  • 46
  • did you check `onError`? – Stephan Dollberg Feb 12 '13 at 18:05
  • What did you mean? That it contains erroneous code? It consists of `std::cout << "foo";` and is working if I put the call to `onError` before the `get()` call of the future. – Stephan Feb 12 '13 at 18:45
  • Is `listener.get()` non-null before the line that crashes? (I can't reproduce the problem locally; it would probably be helpful if you could construct a _complete_ testcase that exhibits the issue for you, so I don't have to guess about the parts that you haven't described). – Mankarse Feb 12 '13 at 19:15
  • @Stephan Can you tell if your lambda is running from the same thread as Service::start, or is future.then kicking off a separate thread? – Nathan Monteleone Feb 12 '13 at 19:15
  • @NathanMonteleone: Currently, `Boost.Thread` does not support `async` `then`. – Mankarse Feb 12 '13 at 19:21
  • @Mankarse ah I missed the big "EXTENSION NOT IMPLEMENTED" in the documentation. – Nathan Monteleone Feb 12 '13 at 19:23
  • @Mankarse I think I checked it and it was non-null (and how should it if the call to `onConnected` works? [that's a real question, is there a possibility that it could be null and the call be successful?]). When I am back in the office tomorrow I will try to create a complete example. Please note that the error occured when doing this on iOS (on a iPod touch to be exact). Thanks for your time already. – Stephan Feb 12 '13 at 20:34
  • @Stephan: It could become null if the call to `connected.get()` lead to the destruction of the lambda (because in that case, any further access to the lambda's members would invoke undefined behaviour, which could include `listener.get()` being null). – Mankarse Feb 13 '13 at 05:03
  • @Mankarse It seems to me that that is happening on the `reset()` call in `future.hpp` (since `future_` is a `shared_ptr` which points to the object holding the continuation and at the end of the method no reference exists to that object, therefore it is cleaned up). Would my assumption that this is a bug be correct? I am still trying to create a minimal example but my time for that is rather limited. – Stephan Feb 13 '13 at 09:35
  • @Stephan It definitely looks fishy. Have you tried using make_shared_future instead? It might be better about not nuking itself the first time you call get(). – Nathan Monteleone Feb 13 '13 at 14:56
  • @NathanMonteleone I don't think `shared_future` has the `then` method defined. – Stephan Feb 13 '13 at 18:48
  • @Mankarse I added a minimal example and some screenshots from xcode showing what you (and I in the previous) edit thought was going on. Now, the final question remains: Is that a bug or not? Is boost supposed to kill the memory while the continuation is still executing and triggering as such undefined behaviour? Or is this expected and what are then counter-measures against it? More a discussion for the mailing list / bugtracker? [I want to be sure to get what is going on right] – Stephan Feb 13 '13 at 18:52

1 Answers1

2

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.

Stephan
  • 7,360
  • 37
  • 46