Search code examples
c++asio

C++/Asio : crash when sending data using async_write


I am using Asio to handle my network class. I have been troubling for a while and I can't get the reason why.

First, the code :

// .h
#pragma once

#include <asio.hpp>

class Connection : public std::enable_shared_from_this<Connection>
{
public:
    static std::shared_ptr<Connection> create(asio::io_context& IoContext);
    asio::ip::tcp::socket& getSocket();
    void start();

private:
    Connection(asio::io_context& IoContext);
    void startReading();
    void sendPacket(std::string Packet);
    void handle_read(const asio::error_code& error, size_t bytes);
    void handle_write(const asio::error_code& error, size_t bytes);
    void disconnect();

    asio::ip::tcp::socket socket;
    char packet[4096];
    bool started;
};

class Network
{
public:
    Network(asio::io_context& IoContext, unsigned short Port);
    void startServer();

private:
    void startAccept();
    void handle_accept(std::shared_ptr<Connection> connection, const asio::error_code& error);

    asio::io_context& ioContext;
    asio::ip::tcp::acceptor acceptor;
    bool started;
    std::vector<std::shared_ptr<Connection>> connections;
};
// .cpp

Connection::Connection(asio::io_context& IoContext)
    : socket(IoContext)
    , started(false)
    , packet("")
{

}

std::shared_ptr<Connection> Connection::create(asio::io_context& IoContext)
{
    return std::shared_ptr<Connection>(new Connection(IoContext));
}

asio::ip::tcp::socket& Connection::getSocket()
{
    return socket;
}

void Connection::start()
{
    std::cout << "Connection::start();" << std::endl;
    if (!started)
    {
        startReading();
        started = true;
    }
}

void Connection::sendPacket(std::string Packet)
{
    socket.async_send(asio::buffer(Packet), [this](const asio::error_code& error, size_t bytes)
        { handle_write(error, bytes); });
}

void Connection::startReading()
{
    socket.async_receive(asio::buffer(packet), [this](const asio::error_code& error, size_t bytes)
        { handle_read(error, bytes); });
}

void Connection::handle_write(const asio::error_code& error, size_t bytes)
{
    std::cout << "WRITE : " << error.message() << " size : " << bytes << std::endl;
}

void Connection::handle_read(const asio::error_code& error, size_t bytes)
{
    if (error)
    {
        std::cout << "error:" << error.message();
        disconnect();
    }
    else
    {
        if (bytes > 0)
        {
            packet[bytes] = 0;
            std::string response = ...;
            sendPacket(response);
        }
    }
}


Network::Network(asio::io_context& IoContext, unsigned short Port)
    : ioContext(IoContext)
    , acceptor(ioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), Port))
    , started(false)
{
    std::cout << "Server::Server();" << std::endl;
}

void Network::startServer()
{
    std::cout << "Server::startServer();" << std::endl;
    if (!started)
    {
        startAccept();
        started = true;
    }
}

void Network::startAccept()
{
    std::cout << "Server::startAccept();" << std::endl;
    std::shared_ptr<Connection> connection = Connection::create(ioContext);
    connections.push_back(connection);
    asio::error_code er;
    acceptor.async_accept(connection->getSocket(), std::bind(&Network::handle_accept, this, connection, er));
}

void Network::handle_accept(std::shared_ptr<Connection> connection, const asio::error_code& error)
{
    std::cout << "Server::handle_accept();" << std::endl;
    if (!error)
    {
        std::cout << "Ok" << std::endl;
        connection->start();
    }
    startAccept();
}

(I removed unnecessary function from the cpp in order to make it more condensed, don't be surprised if some are missing)

Basically, it crashes when I sendPacket(something) from the handle_read function. I found two fixes for it :

  • create and use a member std::string, then the code in handle_read(...) becomes
if (bytes > 0)
{
    packet[bytes] = 0;
    std::string response = ...;
    sendPacket(my_member_string);
}
  • or I create a std::string* in sendPacket(...) :
void Connection::sendPacket(std::string Packet)
{
    std::string *copy;
    socket.async_send(asio::buffer(copy), [this](const asio::error_code& error, size_t bytes)
        { handle_write(error, bytes); });
}

without deleting it (if I do, it crashes the same way).

So, my questions : why is response causing a crash ? I read a lot of same issue talking about "lifetime", could you tell me more about it ? What is really happening in this code with response's life ? Also, what is the clean way to fix this bug, if it is not one of the above ?

By the way, I am not using Boost.

Thank you by advance


Solution

  • You should not capture [this] pointer within your lambdas in ..._async functions. Because lambda will outlive the this pointer and this will point to invalid address within lambda call.

    Use [self=shared_from_this()] and work with self within lambda, so the object will live with shared pointer through async calls.