73

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.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
chrisvj
  • 895
  • 1
  • 6
  • 9
  • 1
    Why do you store the result of `new` in a raw pointer only to use `shared_from_this()` later? It seems your design could be simplified to eliminate this problem altogether. – John Zwinck Dec 30 '14 at 00:32
  • 4
    The boost docs for `enable_shared_from_this` say `There must exist at least one shared_ptr instance p that owns t`, which you don't seem to have. – Jonathan Potter Dec 30 '14 at 00:33
  • @JonathanPotter I read that, but I don't understand it. – chrisvj Dec 30 '14 at 00:34
  • 2
    @chrisvj My interpretation is you need to already have a `shared_ptr` that holds the object before you can make more using `shared_from_this`. Never used it myself though so it's just a guess. – Jonathan Potter Dec 30 '14 at 00:36
  • @JonathanPotter Hmm, not sure, don't see anything involving shared_ptr in the example that I don't have. – chrisvj Dec 30 '14 at 00:40
  • @JohnZwinck Oh, I didn't even think about that. Works perfectly, thanks. Submit as an answer and I'll mark it correct. EDIT: Oh, except I now need an alternative for this `if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec)) { clients.erase(shared_from_this()); }` – chrisvj Dec 30 '14 at 00:41
  • 1
    It only makes sense to call `shared_from_this` on an object whose lifetime is managed by shared pointers. Otherwise, it's impossible to have a shared pointer whose lifetime is guaranteed to be at least as great as the life of the object and the sole purpose of `shared_from_this` is to return such a thing. So, in sum, you are asking `shared_from_this` to do the impossible. – David Schwartz Dec 30 '14 at 01:24
  • @chrisvj a general question "At the call to shared_from_this" did you use gdb to find errror, I m facing this same issue, but not able to find with gdb even after attaching it through the pid process :) – Abhinav Singh Aug 09 '20 at 04:04

4 Answers4

63

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
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Great answer. However, AFAIK the reason all connections are stored in a vector is for the server to be able to shut them down when exiting (e.g. in a signal handler). This is how I discovered this issue -- I cleared the vector in the signal handler in addition to closing the sockets. Do you know a vector-less solution for this use-case? – David Nemeskey Jul 27 '18 at 08:56
  • @DavidNemeskey Of course, if you need the session-list, by all means keep it (using `weak_ptr`, though). And yes, I've come up with a vector-less solution which is actually more versatile because of the inversion of control: https://stackoverflow.com/questions/49394277/boost-asio-send-message-to-all-connected-clients/49396653#49396653 (note the answer centers on different operations on all sessions, but it also links to other answers that do focus on the shutdown sequence) – sehe Jul 28 '18 at 15:11
  • 26
    I've got 'bad_weak_ptr' exception because I forgot to add 'public' in front of the 'std::enable_shared_from_this()'. – Rostfrei Sep 05 '18 at 09:04
  • @Rostfrei yup, that happens "frequently" - I've been trying to get a "canonical" answer for that, but I realize it will always be hard to find because the symptoms are surprising, and the ingredients easy to miss – sehe Sep 05 '18 at 11:34
  • @Rostfrei Thanks man, this is an important note! I struggled about 30 minutes, because I thought that public is not needed... – Zdravko Donev Apr 14 '19 at 16:15
  • 2
    @Rostfrei thanks a ton! I have been debugging like crazy for 2 hours and it was al because of this missing `public` – CSawy Sep 24 '19 at 20:02
  • This is such an important answer. It needs to be carved into stone and placed on the mount. – Theko Lekena Jun 26 '20 at 18:38
  • 1
    @ThekoLekena I took the liberty of applying the [tag:c++-faq] tag – sehe Jun 26 '20 at 18:41
  • remote how lhow to remove a object froma list based on the pointer to the object – SushiWaUmai May 28 '21 at 19:10
27
// Do not forget to ----v---- publicly inherit :)
class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
franckspike
  • 2,039
  • 25
  • 18
  • For anyone like me wondering why it is so: https://stackoverflow.com/questions/39937112/stdenable-shared-from-this-public-vs-private – Andrei Sep 14 '21 at 04:47
  • Wow, I feel so stupid, this would have saved me a lot of trouble the last few times I tried to use `shared_from_this()`... – itmuckel Jul 18 '22 at 20:42
15

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.

The root cause of your troubles seems to be the fact that you're storing the result of new in a raw pointer initially. You should store the result of new in a smart pointer (always, basically). Perhaps you can store the smart pointer in your clients list straight away, then.

Another approach which I mentioned in the comments is to stop using shared_from_this() entirely. You don't need it. As for this bit of code you mentioned:

if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec))
{
    clients.erase(shared_from_this());
}

You can replace it by:

if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec))
{
    boost::shared_ptr<tcp_connection> victim(this, boost::serialization::null_deleter());
    clients.erase(victim);
}

That is, create a "dumb" smart pointer which will never deallocate (https://stackoverflow.com/a/5233034/4323) but which will give you what you need to delete it from the list of clients. There are other ways to do it too, such as by searching the std::set using a comparison function which takes one shared_ptr and one raw pointer and knows to compare the addresses to which they point. It doesn't matter much which way you choose, but you escape the shared_from_this() situation entirely.

Community
  • 1
  • 1
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • 2
    The `shared_from_this` "situation" is idiomatic for session management in Boost Asio. It's purpose is to make lifetime management of asynchronous sessions easier. In fact the `clients` set here makes that very hard as it's virtually impossible to get cleanup right with all the places where asynchronous operations can fail (not even thinking about general exception safety) – sehe Dec 30 '14 at 13:04
0

The answers here are great, and revealed the solution to my problem. However,

  1. I searched for "bad_weak_ptr shared_from_this" and this was the top result, even though I made no mention of boost. I'm using standard C++17.
  2. The OP is a specific example; a bit involved, and you have to dig through some layers of irrelevant boost socket code to get to the core issue.

In light of these two points, I thought it might be helpful to post an MRE of the root issue, and what's needed to fix it. This code is sourced from cppreference:

Problematic Code

#include <iostream>
#include <memory>
 
struct Foo : public std::enable_shared_from_this<Foo> {
    Foo() { std::cout << "Foo::Foo\n"; }
    ~Foo() { std::cout << "Foo::~Foo\n"; } 
    std::shared_ptr<Foo> getFoo() { return shared_from_this(); }
};
 
int main()
{
    try
    {
        Foo *f = new Foo;
        // Oops! this throws std::bad_weak_ptr. f is a raw pointer to Foo (not
        // managed by a shared pointer), and calling getFoo tries to create a
        // shared_ptr from the internal weak_ptr. The internal weak_ptr is nullptr,
        // so this cannot be done, and the exception is thrown. Note, the
        // cppreference link above says that prior to C++17, trying to do this
        // is undefined behavior. However, in my testing on godbolt, an exception
        // is thrown for C++11, 14, and 17 with gcc.
        std::shared_ptr<Foo> sp = f->getFoo();
    }
    catch(const std::bad_weak_ptr& bwp)
    {
        // the exception is caught, and "bad_weak_ptr" is printed to stdout
        std::cout << bwp.what();
        exit(-1);
    }

    return 0;
}

Output:

Foo::Foo
bad_weak_ptr

Potential Fix

Make sure f is managed by a shared_ptr:

try
{
    Foo *f = new Foo;
    // this time, introduce a shared pointer
    std::shared_ptr<Foo> sp(f);
    // now, f is managed by a shared pointer. Its internal weak_ptr is valid,
    // and so the retrieval of a shared_ptr via shared_from_this works as
    // desired. We can get a weak_ptr or a shared_ptr
    std::weak_ptr<Foo> wp = f->getFoo();
    std::shared_ptr<Foo> sp2 = f->getFoo();
    // all pointers go out of scope and the Foo object is deleted once
    // the reference count reaches 0
}
catch(const std::bad_weak_ptr& bwp)
{
    std::cout << bwp.what();
    exit(-1);
}

Output:

Foo::Foo
Foo::~Foo

As the other answers state and as illustrated above, you must have an object managed by a shared pointer prior to calling shared_from_this, or you will get the bad_weak_ptr exception or UB, depending on your C++ standard. Here's a playground for anyone interested.

yano
  • 4,827
  • 2
  • 23
  • 35