Flying Hat
Flying Hat

Reputation: 217

Asio calls handler after the class containing the handler gets destructed

I have a class X that takes a reference to boost::asio::io_service and a connected boost::astio::ip::tcp::socketin its constructor. That class handles sending and receiving of network data.

One issue I'm having is that the main code asks X to send a message X::sendMessage() and then the main code deletes X on the very next line. So, X calls boost::asio::async_write, using a lambda as a handler, then X gets deleted, its destructor is called, which closes the socket. There is no X anymore. But after a while boost::asio::io_service calls my lambda handler used in the boost::asio::async_write call, which is now in a destructed class, with boost::system::error_code set to "Success".

Is there some way to tell the lambda that the class was destructed and it shouldn't mess with class'es members and methods?

Maybe I can somehow cancel the lambda handler from being called in ~X()? Though the write operation could have already completed and scheduled the handler to be executed by boost::asio::io_service, so there is nothing really to cancel.

Note that I can't do anything with the boost::asio::io_service object, as it is passed to X by the main code and it handles much more than just networking.

class X
{
public:
    X(boost::asio::io_service &io, boost::asio::ip::tcp::socket socket)
    : io(io), socket(std::move(socket)){ }

    ~X()
    {
        if (socket.is_open()) {
            socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
            socket.close();
        }
    }

    void X::sendMessage()
    {
        boost::asio::async_write(socket, boost::asio::buffer(m.data, m.size),
            [this](boost::system::error_code ec, std::size_t /*length*/)
            {
                std::cout << ec.message() << std::endl; // Success!
                // `this' is invalid though

                if (!ec) {
                    // code
                } else {
                   // code
                }
            });
    }

private:
    boost::asio::io_service &io;
    boost::asio::ip::tcp::socket socket;

Edit:

For now I just created a shared pointer with a bool, which I pass to lambdas and which gets set to true in the destructor, so that lambdas could tell if the object was already destructed. It's a dirty hack, but it works for now. I would like to have a more elegant solution in the long run though.

Upvotes: 1

Views: 375

Answers (2)

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136425

If you would like to make sure that your object stays alive while asio framework has references to it, pass a shared ownership smart pointer to the object into the lambda expression. E.g.:

class X : public enable_shared_from_this<X>
// ...

    boost::shared_ptr<X> that = this->shared_from_this();
    boost::asio::async_write(socket, boost::asio::buffer(m.data, m.size),
        [that](boost::system::error_code ec, std::size_t /*length*/)

Upvotes: 1

megabyte1024
megabyte1024

Reputation: 8660

A solution is to declare the X class member as a shared pointer (i.e. boost::shared_ptr<X> m_x;) and pass the class instance to the lambda. See the modified code below.

It prevents the class instance destruction until Asio calls the lambda.

#include <boost/noncopyable.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>

class X : private boost::noncopyable, public boost::enable_shared_from_this<X>
{
private:
    struct Data {
        char *data;
        size_t size;
    };
    Data m;
public:
    X(boost::asio::io_service &io, boost::asio::ip::tcp::socket socket)
        : io(io), socket(std::move(socket)){ }

    ~X()
    {
        if (socket.is_open()) {
            socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
            socket.close();
        }
    }

    void X::sendMessage()
    {
        auto self = shared_from_this();
        boost::asio::async_write(socket, boost::asio::buffer(m.data, m.size),
            [self](boost::system::error_code ec, std::size_t /*length*/)
        {
            std::cout << ec.message() << std::endl; // Success!
            // `this' is invalid though

            if (!ec) {
                // code
            }
            else {
                // code
            }
        });
    }

private:
    boost::asio::io_service &io;
    boost::asio::ip::tcp::socket socket;
};

Upvotes: 0

Related Questions