Bill
Bill

Reputation: 33

boost::asio::strand wrap causing shared_ptr from shared_from_this to become NULL

When I tried to use boost::asio::strand wrap function, it seems to cause the shared_ptr that came from shared_from_this to become NULL, so when it is executed the second time, it will cause the program to crash.

However, this only happens if the function that was binded takes no arguments.

After setting the watch point and checking the callstack, this behavior seems to be caused by move constructor of shared_ptr swapping value, which originated from async_result_init with BOOST_ASIO_MOVE_CAST.

My question is, how should I properly use wrap to avoid this from happening?

Below is a simple sample to show you what I meant:

class object : public boost::enable_shared_from_this<object> {
public:
    object()
    : service(new boost::asio::io_service),
    work(new boost::asio::io_service::work(*service)),
    strand(*service),
    value(0) {}

    void workerThread() {
        service->run();
    }

    void run() {
        func_int = strand.wrap(boost::bind(&object::handler_int, shared_from_this(), _1));
        func_void = strand.wrap(boost::bind(&object::handler_void, shared_from_this()));

        std::thread thread(boost::bind(&object::workerThread, this));

        func_int(1);
        func_int(1);

        func_void();
        func_void();    // Will crash due to shared_ptr being NULL, hence "value" cannot be accessed in handler_void

        thread.join();
    }

    void handler_int(int parameter) {
        cout << "handler_int: " << value << endl;
    }

    void handler_void() {
        cout << "handler_void: " << value << endl;
    }

    boost::shared_ptr<boost::asio::io_service> service;
    boost::shared_ptr<boost::asio::io_service::work> work;
    boost::asio::strand strand;
    std::function<void(int)> func_int;
    std::function<void(void)> func_void;
    int value;
};


int main(int argc, char *argv[]) {    
    boost::shared_ptr<object> obj(new object());
    obj->run();
    return 0;
}

Upvotes: 3

Views: 937

Answers (1)

Richard Hodges
Richard Hodges

Reputation: 69902

This is due to a misunderstanding of what wrap does. This caught me out too recently. I wrote to the author of asio for clarification. What he told me surprised me.

wrap returns a 'wrapped_handler' but contrary to what you might expect, this is not a function object which performs a dispatch under the covers.

It is actually an object that contains the handler you bound plus a reference to the executor. Asio io objects use this information when completing async operations in order to execute the handler in the correct context.

Confusingly, this wrapped_handler also has an operator(). What this does is merely execute the bound function. It does not post or dispatch the function. In my view this operator() should not exist. I have mailed Christopher Kohlhoff expressing this view. I have not had a reply.

you can prove this by replacing this:

func_int(1);

with this:

service->post(strand->wrap(boost::bind(&object::handler_int, shared_from_this(), 1)));

for each of the invocations of the bound member function. You can then test that they are executing in the strand.

Note that it would not matter which io_service you posted/dispatched them to. They would be sent straight to the strand with which they have been associated by wrap.

Finally, on asio and style.

You may want to address the ownership of io_service/strand/work objects by shared_ptr. This is always un-necessary. An io_service is a fundamental component of an application - the central message loop. It will not have an indeterminate lifetime. Neither will the work or the strand. Provided you end its lifetime with:

service.stop();
thread(s).join();

All will be well.

Upvotes: 4

Related Questions