Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

shared_from_this causing bad_weak_ptr

I am trying to keep a list of connected clients in asio. I have adapted the chat server example from the docs (http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/example/cpp03/chat/chat_server.cpp) and here's the important part of what I ended up with:

#include <iostream> #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/asio.hpp> #include <set>  using boost::asio::ip::tcp;  class tcp_connection;  std::set<boost::shared_ptr<tcp_connection>> clients;  void add_client(boost::shared_ptr<tcp_connection> client) {     clients.insert(client); }  class tcp_connection : public boost::enable_shared_from_this<tcp_connection> { public:     tcp_connection(boost::asio::io_service& io_service) : socket_(io_service)     {     }      tcp::socket socket_;      void start()     {         add_client(shared_from_this());     }      tcp::socket& socket()     {         return socket_;     } };  class tcp_server { public:     tcp_server(boost::asio::io_service& io_service)         : io_service_(io_service),         acceptor_(io_service, tcp::endpoint(tcp::v4(), 6767))     {         tcp_connection* new_connection = new tcp_connection(io_service_);         acceptor_.async_accept(new_connection->socket(),                              boost::bind(&tcp_server::start_accept, this, new_connection,                                          boost::asio::placeholders::error));     }  private:     void start_accept(tcp_connection* new_connection,                       const boost::system::error_code& error)     {         if (!error)         {             new_connection->start();             new_connection = new tcp_connection(io_service_);             acceptor_.async_accept(new_connection->socket(),                                    boost::bind(&tcp_server::start_accept, this, new_connection,                                                boost::asio::placeholders::error));         }     }      boost::asio::io_service& io_service_;     tcp::acceptor acceptor_; };  int main() {     try     {         boost::asio::io_service io_service;         tcp_server server(io_service);         io_service.run();     }     catch (std::exception& e)     {         std::cerr << "Exception: " << e.what() << "\n";     }      return 0; } 

At the call to shared_from_this(), my server crashes with the message:

Exception: tr1::bad_weak_ptr

I have done some searching and it appears shared_from_this() is pretty particular, but I can't seem to find exactly what I need to change.

like image 613
chrisvj Avatar asked Dec 30 '14 00:12

chrisvj


2 Answers

John Zwinck's essential analysis is spot on:

The bug is that you're using shared_from_this() on an object which has no shared_ptr pointing to it. This violates a precondition of shared_from_this(), namely that at least one shared_ptr must already have been created (and still exist) pointing to this.

However, his advice seems completely beside the point and dangerous in Asio code.

You should solve this by - indeed - not handling raw pointers to tcp_connection in the first place but always using shared_ptr instead.

boost::bind has the awesome feature that it binds to shared_ptr<> just fine so it automagically keeps the pointed to object alive as long as some asynchronous operation is operating on it.

This - in your sample code - means you don't need the clients vector, going the opposite way from John's answer:

void start_accept() {     tcp_connection::sptr new_connection = boost::make_shared<tcp_connection>(io_service_);     acceptor_.async_accept(new_connection->socket(),             boost::bind(                 &tcp_server::handle_accept,                 this, new_connection, asio::placeholders::error             )         ); }  void handle_accept(tcp_connection::sptr client, boost::system::error_code const& error) {     if (!error)     {         client->start();         start_accept();     } } 

I've included a sample that makes tcp_connection do some trivial work (it loops writing 'hello world' to the client each second, until the client drops the connection. When it does, you can see the destructor of the tcp_connection operation being run:

Live On Coliru

#include <iostream> #include <boost/bind.hpp> #include <boost/make_shared.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/asio.hpp> #include <boost/thread.hpp>  namespace asio = boost::asio; using asio::ip::tcp;  class tcp_connection : public boost::enable_shared_from_this<tcp_connection> { public:     typedef boost::shared_ptr<tcp_connection> sptr;      tcp_connection(asio::io_service& io_service) : socket_(io_service), timer_(io_service)     {     }      void start()     {         std::cout << "Created tcp_connection session\n";          // post some work bound to this object; if you don't, the client gets         // 'garbage collected' as the ref count goes to zero         do_hello();     }      ~tcp_connection() {         std::cout << "Destroyed tcp_connection\n";     }      tcp::socket& socket()     {         return socket_;     }    private:     tcp::socket socket_;     asio::deadline_timer timer_;      void do_hello(boost::system::error_code const& ec = {}) {         if (!ec) {             asio::async_write(socket_, asio::buffer("Hello world\n"),                     boost::bind(&tcp_connection::handle_written, shared_from_this(), asio::placeholders::error, asio::placeholders::bytes_transferred)                 );         }     }      void handle_written(boost::system::error_code const& ec, size_t /*bytes_transferred*/) {         if (!ec) {             timer_.expires_from_now(boost::posix_time::seconds(1));             timer_.async_wait(boost::bind(&tcp_connection::do_hello, shared_from_this(), asio::placeholders::error));         }     } };  class tcp_server { public:     tcp_server(asio::io_service& io_service)         : io_service_(io_service),           acceptor_(io_service, tcp::endpoint(tcp::v4(), 6767))     {         start_accept();     }  private:     void start_accept()     {         tcp_connection::sptr new_connection = boost::make_shared<tcp_connection>(io_service_);         acceptor_.async_accept(new_connection->socket(),                 boost::bind(                     &tcp_server::handle_accept,                     this, new_connection, asio::placeholders::error                 )             );     }      void handle_accept(tcp_connection::sptr client, boost::system::error_code const& error)     {         if (!error)         {             client->start();             start_accept();         }     }      asio::io_service& io_service_;     tcp::acceptor acceptor_; };  int main() {     try     {         asio::io_service io_service;         tcp_server server(io_service);          boost::thread(boost::bind(&asio::io_service::run, &io_service)).detach();          boost::this_thread::sleep_for(boost::chrono::seconds(4));         io_service.stop();     }     catch (std::exception& e)     {         std::cerr << "Exception: " << e.what() << "\n";     } } 

Typical output:

sehe@desktop:/tmp$ time (./test& (for a in {1..4}; do nc 127.0.0.1 6767& done | nl&); sleep 2; killall nc; wait) Created tcp_connection session Created tcp_connection session      1  Hello world Created tcp_connection session      2  Hello world Created tcp_connection session      3  Hello world      4  Hello world      5  Hello world      6  Hello world      7  Hello world      8  Hello world      9  Hello world     10  Hello world     11  Hello world     12  Hello world     13   Destroyed tcp_connection Destroyed tcp_connection Destroyed tcp_connection Destroyed tcp_connection Destroyed tcp_connection  real    0m4.003s user    0m0.000s sys 0m0.015s 
like image 96
sehe Avatar answered Sep 22 '22 00:09

sehe


// Do not forget to ----v---- publicly inherit :) class tcp_connection : public boost::enable_shared_from_this<tcp_connection> 
like image 43
franckspike Avatar answered Sep 21 '22 00:09

franckspike