Peter
Peter

Reputation: 11890

Memory leak in boost asio async tcp echo server example

The following code is based on boost documentation example at http://www.boost.org/doc/libs/1_46_1/doc/html/boost_asio/example/echo/async_tcp_echo_server.cpp. I just changed it a little so that I can run the server in a different thread and quit after running for a few seconds.

This code works as expected. When a client connects and sends some text, a copy is echoed back to the client.

There is just one problem. There is one instance of class session that never gets deleted. If you run the code, you can witness the number of times the constructor and the destructor is called. It is always the last session instance that does not get deleted.

Wondering if there is a simple way to fix this. Perhaps I can use smart_ptr class.

Note that I have to use boost library 1.46. There are some other examples I found that are based on the newer library version. But these examples don't compile under older boost library. Regards.

#include <cstdlib>
#include <iostream>
#include <vector>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

using boost::asio::ip::tcp;

class session 
{
public:
  session(boost::asio::io_service& io_service)
    : socket_(io_service)
  {
     char buf[100]; sprintf(buf, "%p", this);
     std::cout << "Session created: " << buf << std::endl;
  }

  ~session() {
     char buf[100]; sprintf(buf, "%p", this);
     std::cout << "Session destroyed: " << buf << std::endl;
  }

  tcp::socket& socket()
  {
    return socket_;
  }

  void start()
  {
    socket_.async_read_some(boost::asio::buffer(data_, max_length),
        boost::bind(&session::handle_read, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

  void handle_read(const boost::system::error_code& error,
      size_t bytes_transferred)
  {
    if (!error)
    {
      boost::asio::async_write(socket_,
          boost::asio::buffer(data_, bytes_transferred),
          boost::bind(&session::handle_write, this,
            boost::asio::placeholders::error));
    }
    else
    {
      delete this;
    }
  }

  void handle_write(const boost::system::error_code& error)
  {
    if (!error)
    {
      socket_.async_read_some(boost::asio::buffer(data_, max_length),
          boost::bind(&session::handle_read, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }
    else
    {
      delete this;
    }
  }

private:
  tcp::socket socket_;
  enum { max_length = 1024 };
  char data_[max_length];
};

class server
{
public:
  server(boost::asio::io_service& io_service, short port)
    : io_service_(io_service),
      acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
  {
    session* new_session =  new session(io_service_);
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

  ~server() {
  }

  void handle_accept(session* new_session,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_session->start(); // ownership passed
      new_session = new session(io_service_);
      acceptor_.async_accept(new_session->socket(),
          boost::bind(&server::handle_accept, this, new_session,
            boost::asio::placeholders::error));
    }
    else
    {
      std::cout << "Error in HA: " << error.message() << std::endl;
    }
  }

private:
  boost::asio::io_service& io_service_;
  tcp::acceptor acceptor_;
};

/*
int main(int argc, char* argv[])
{
  try
  {
    if (argc != 2)
    {
      std::cerr << "Usage: async_tcp_echo_server <port>\n";
      return 1;
    }

    boost::asio::io_service io_service;

    using namespace std; // For atoi.
    server s(io_service, atoi(argv[1]));

    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}
*/

static void serverThread(boost::asio::io_service* io_service) {
    server s(*io_service, 5000);
    io_service->run();
}

int main(int argc, char* argv[])
{
    boost::asio::io_service io_service;
    boost::thread t(serverThread, &io_service);

    ::sleep(10);
    io_service.stop();
    t.join();
    std::cout << "Quitting" << std::endl;
    return 0;
}

Upvotes: 1

Views: 661

Answers (1)

Arunmu
Arunmu

Reputation: 6901

The one that does not get deleted is the one that gets passed to the asynchronous accept handler i.e handler_accept. I would recommend heavily to make use of smart pointer. shared_ptr is a good candidate here:

#include <cstdlib>
#include <iostream>
#include <vector>
#include <thread>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <memory>

using boost::asio::ip::tcp;

class session: public std::enable_shared_from_this<session>
{
public:
  session(boost::asio::io_service& io_service)
    : socket_(io_service)
  {
     char buf[100]; sprintf(buf, "%p", this);
     std::cout << "Session created: " << buf << std::endl;
  }

  ~session() {
     char buf[100]; sprintf(buf, "%p", this);
     std::cout << "Session destroyed: " << buf << std::endl;
  }

  tcp::socket& socket()
  {
    return socket_;
  }

  void start()
  {
    socket_.async_read_some(boost::asio::buffer(data_, max_length),
        boost::bind(&session::handle_read, shared_from_this(),
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

  void handle_read(const boost::system::error_code& error,
      size_t bytes_transferred)
  {
    if (!error)
    {
      boost::asio::async_write(socket_,
          boost::asio::buffer(data_, bytes_transferred),
          boost::bind(&session::handle_write, shared_from_this(),
            boost::asio::placeholders::error));
    }
  }

  void handle_write(const boost::system::error_code& error)
  {
    if (!error)
    {
      socket_.async_read_some(boost::asio::buffer(data_, max_length),
          boost::bind(&session::handle_read, shared_from_this(),
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }
  }

private:
  tcp::socket socket_;
  enum { max_length = 1024 };
  char data_[max_length];
};

using session_ptr = std::shared_ptr<session>;

class server
{
public:
  server(boost::asio::io_service& io_service, short port)
    : io_service_(io_service),
      acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
  {
    auto new_session =  std::make_shared<session>(io_service_);
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

  ~server() {
  }

  void handle_accept(session_ptr new_session,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_session->start(); // ownership passed
      new_session.reset();
      new_session = std::make_shared<session>(io_service_);
      acceptor_.async_accept(new_session->socket(),
          boost::bind(&server::handle_accept, this, new_session,
            boost::asio::placeholders::error));
    }
    else
    {
      std::cout << "Error in HA: " << error.message() << std::endl;
    }
  }

private:
  boost::asio::io_service& io_service_;
  tcp::acceptor acceptor_;
};

/*
int main(int argc, char* argv[])
{
  try
  {
    if (argc != 2)
    {
      std::cerr << "Usage: async_tcp_echo_server <port>\n";
      return 1;
    }

    boost::asio::io_service io_service;

    using namespace std; // For atoi.
    server s(io_service, atoi(argv[1]));

    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}
*/

static void serverThread(boost::asio::io_service* io_service) {
    server s(*io_service, 5000);
    io_service->run();
}

int main(int argc, char* argv[])
{
    boost::asio::io_service io_service;
    std::thread t(serverThread, &io_service);

    ::sleep(10);
    io_service.stop();
    t.join();
    std::cout << "Quitting" << std::endl;
    return 0;
}

I have made use of c++11 features, namely std::thread and std::shared_ptr. std::enable_shared_from_this gives you the ability to create a shared_ptr object of this. Checkout how it is used inside async function calls of session class.

Also, looking at the code it makes me feel that you are expecting the handlers to be called with some error code once you call io_service::stop ? No, that is not the case. It will simple calls scheduler::shutdown which simply destroys the async handlers, nothing else.

Upvotes: 2

Related Questions