1
votes

I created a static map that holds several Sessions of connected clients.

std::map<std::string, std::shared_ptr<Session>> Communication::m_appSockets;

The Listener that accepts incomming clients is implemented in the Communication class.

class Communication : public std::enable_shared_from_this<Communication>
{
private:
  boost::asio::io_context m_ioc;
  boost::asio::io_context::work m_work;
  boost::asio::streambuf m_buffer;
  boost::asio::ip::tcp::acceptor m_acceptor;
  std::thread m_senderThread;
  std::thread m_ioThread;

public:
  static std::map<std::string, std::shared_ptr<Session>> m_appSockets;

  Communication(uint16_t t_port);

  void accept();

  void doAccept();

  void senderThread();
};

After accepting a client the method "doAccept" creates a session object and moves the socket like this

  m_acceptor.async_accept(
    [this](boost::system::error_code t_ec, boost::asio::ip::tcp::socket t_socket) {
      if (!t_ec)
      {
            m_appSockets.emplace(std::pair<std::string, std::shared_ptr<Session>>(
              "app0", std::make_shared<Session>(std::move(t_socket))));
            m_appSockets["app0"]->start();
      }
      accept();
    });

Session.h looks like this:

class Session : public std::enable_shared_from_this<Session>
{
private:
  boost::asio::ip::tcp::socket m_socket;
  boost::asio::streambuf m_buffer;

public:

  Session(boost::asio::ip::tcp::socket t_socket);

  void write(std::string &t_msg);
  void doWrite(std::string &t_msg);
  void start();
...
};

void start() is used for starting the async read on the socket, which is working fine. A session object is created this way:

Session::Session(boost::asio::ip::tcp::socket t_socket) : m_socket(std::move(t_socket))
{}

What I need to do for my implementation is to access the write-method of session through the shared_ptr in the map of Communication.h. I tried it the following way

void Communication::senderThread()
{
  for (;;)
  {
    .... 
    //blocking until queue holds a message
    std::string buf = *message from queue*//pseudo code
    m_appSockets["app0"].get()->write(buf);

  }
}

A senderthread blocks until a message is available in a queue which will be forwarded to the write method of session

The write-method can be called but as soon as i try an operation on any member of the session it gives me a segmentation fault:

void Session::write(std::string &t_msg)
{
//here it crashes
  m_socket.get_executor().context().post(std::bind(&Session::doWrite, shared_from_this(), t_msg));
}

void Session::doWrite(std::string &t_msg)
{
  boost::asio::async_write(
    m_socket, boost::asio::buffer(t_msg),
    std::bind(&Session::onWrite, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}

It feels like the Session object runs out of scope as soon as I enter its method. I have tried creating dummy members in Session which all gave the same segmentation fault when accessing them. Am I getting the shared_ptr/object lifetime wrong at some point?

Thank you very much in advance.

EDIT 1: Running gdb ./programm.out core gave me this:

Thread 2 "programm.out" received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x7ffff58f1700 (LWP 5651)] 0x0000555555605932 in Session::write (this=0x0, t_msg="{\"destination\":\"app0\"}") at /usr/Sources/Session.cpp:58 58 std::cout << dummyMember << std::endl;

I added a member to Session (int dummyMember{5};).

How can it be that this is pointing to 0x0?

1

1 Answers

3
votes

Below line is suspicious

  boost::asio::buffer(t_msg)

asio::buffer returns object which holds a pointer to the content of string and length of string (a copy of t_msg is not created). You have to be careful when using asio::buffer with asynchronous operations because its return type is pair (pointer,length) to string, it doesn't extend the lifetime of string.

async_write function returns immediately and we don't know when handler will be called, so you have to make sure that msg is valid until handler is called.

  for(;;)
  {
    std::string buf = *message from queue*//pseudo code
    m_appSockets["app0"].get()->write(buf);

    // if you want to send original buf string
    // before this loop ends all asynchronous operation writing buf must be complete
  }

If your goal is to send msg you have missed to use ref wrapper, because bind takes parameters by value by default.

std::bind(&Session::doWrite, shared_from_this(), t_msg) // make copy of t_msg

above creates callback which holds copy of t_msg. When this callback is called copy of t_msg is passed into boost::asio::buffer(t_msg) in Session::doWrite. Probably, before callback created by std::bind(&Session::onWrite, shared_from_this(), std::placeholders::_1, std::placeholders::_2) is executed string copy is destroyed and buffer points to deleted data.

You can rewrite Session::write method using std::ref:

m_socket.get_executor().context().post(std::bind(&Session::doWrite, shared_from_this(), std::ref(t_msg)));

and ensure that all asynchronous operations which write this string are complete until for loop ends in senderThread. Or you need to find another way to hold t_msg string while asynchronous operations are executed.