Bill Bill - 3 months ago 40
C++ Question

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;
}

Answer

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.

Comments