Reputation: 33
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
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