简体   繁体   中英

boost::asio hangs _endthreadx

int main(){
  boost::asio::io_context io_context;
  Server server(io_context, SOCKET_ADDRESS, SOCKET_PORT);

  std::thread thread_server([&]() {
    server.start();
    io_context.run();
  });

  std::thread thread_client([&]() {
    Client &client = Client::create(SOCKET_ADDRESS, SOCKET_PORT);
    client.start();
    done = true; // <-----atomic
  });

  std::thread thread_stop([&]() {
    while (done == false) {
      std::this_thread::sleep_for(std::chrono::milliseconds(5));
    }
    server.stop();
  });

  thread_server.join();
  thread_client.join();
  thread_stop.join();
}

I am experimenting with boost::asio and encountered problem which I am unable to solve. When I run program(simpler example above) on Linux(compiled with gcc) everything is fine. Same when I run it on Release in VS2017CE. However when I run it on Debug(VS2017CE as well) it crash with and exception:

cannot dereference string iterator because string iterator was invalidated

It crash on _endthreadx either when exiting thread_stop or thread_server (most likely second one). Here are my questions then:

  1. What are the differences between Release and Debug basic configurations which might affect code execution and point me where should I look.(I am aware of some but couldn't find anything connected with this particular problem.)

  2. What mistakes were made by me which affects code execution.

I've made some classes so I will provide more code if neccessary, but code basically works so I am starting with just a piece of it.

The code shown doesn't do anything with strings. Also you don't show what io_context is being used in Client instances.

As given, everything is a giant race-condition, because none of the client work might ever get run, but you always set done = true immediately after posting the (supposedly) asynchronous operation of Client::start .

(The potentially sane interpretation here would be if Client::start() were actually completely synchronous, but that really would make the whole existence of static Client& Client::create(...) pretty strange and useless?).

Using a thread to sleep is an anti-pattern, and doubly so in asynchronous code.


cannot dereference string iterator because string iterator was invalidated

This is a clear sign that MSVC's Iterator Debugging is working. It just tells you you have a programming error.

Your error causes string iterators to be used when they're no longer valid. I can't see it, but 99% of the time this would be caused by asynchronous operations using a buffer that gets destroyed (or modified) before the async operation completes. In a nutshell:

void foo() {
    std::string msg = "message";
    boost::asio::async_write(_socket, boost::asio::buffer(msg), /*...*/);
}

Suggested Code

Simplifying from your code, and showing some hints:

Live On Coliru

#include <boost/asio.hpp>
#include <iostream>
#include <iomanip>

using boost::asio::ip::tcp;
using boost::system::error_code;

static std::string const SOCKET_ADDRESS = "127.0.0.1";
static unsigned short const SOCKET_PORT = 6767;

bool check(error_code const& ec, char const* message) {
    std::cout << message << " (" << ec.message() << ")\n";
    return !ec;
}

struct Server {
    boost::asio::io_context& io_;
    Server(boost::asio::io_context& io, std::string host, unsigned short port) : io_(io), host_(host), port_(port) {}

    void start() {
        acc_.set_option(tcp::acceptor::reuse_address(true));
        acc_.listen(5);

        accept_loop();
    }

    void stop() {
        io_.post([this] { // thread safety
            acc_.cancel();
            acc_.close();
        });
    }

  private:
    void accept_loop() {
        acc_.async_accept(sock_, [this](error_code ec) {
            if (check(ec, "accepted")) {
                std::make_shared<Connection>(std::move(sock_))->start();
                accept_loop();
            }
        });
    }

    struct Connection : std::enable_shared_from_this<Connection> {
        tcp::socket sock_;
        std::string buffer_;

        Connection(tcp::socket&& sock) : sock_(std::move(sock)) {}

        ~Connection() {
            error_code ec;
            std::cout << "Disconnected " << sock_.remote_endpoint(ec) << "\n";
        }

        void start() {
            auto self = shared_from_this();

            async_read_until(sock_, boost::asio::dynamic_buffer(buffer_), "\n", [self,this](error_code ec, size_t bytes) {
                 if (check(ec, "received request")) {
                     std::cout << "Request: " << std::quoted(buffer_.substr(0, bytes), '\'') << "\n";
                     if (bytes > 0)
                         std::reverse(buffer_.begin(), buffer_.begin() + bytes - 1); // reverse the request for the response

                     async_write(sock_, boost::asio::buffer(buffer_, bytes), [self,this](error_code ec, size_t bytes) {
                          if (check(ec, "response sent")) {
                              buffer_.erase(0, bytes);
                              start(); // handle more requests, if any
                          }
                     });
                 }
            });
        }
    };

    std::string host_;
    unsigned short port_;

    tcp::acceptor acc_{io_, {boost::asio::ip::address_v4::from_string(host_), port_}};
    tcp::socket sock_{io_};
};

struct Client {
    Client(std::string host, std::string port) : host_(host), port_(port) {}

    void start() { 
        boost::asio::io_context io;

        tcp::socket s(io);
        tcp::resolver r(io);
        connect(s, r.resolve(host_, port_));

        send_request(s, "hello world\n");
        send_request(s, "bye world\n");
    }

  private:
    void send_request(tcp::socket& s, std::string const& request) {
        write(s, boost::asio::buffer(request));

        boost::asio::streambuf sb;
        read_until(s, sb, "\n");

        std::cout << "Received server response: '" << &sb << "'\n";
    }

    std::string host_;
    std::string port_;
};

int main(){
    boost::asio::io_context io_context;
    Server server(io_context, SOCKET_ADDRESS, SOCKET_PORT);
    server.start();

    std::thread thread_server([&]() { io_context.run(); });

    {
        Client client {SOCKET_ADDRESS, std::to_string(SOCKET_PORT)};
        client.start();
    }

    {
        Client client {SOCKET_ADDRESS, std::to_string(SOCKET_PORT)};
        client.start();
    }

    server.stop();
    thread_server.join();
}

Prints

accepted (Success)
received request (Success)
Request: 'hello world
'
response sent (Success)
Received server response: 'dlrow olleh
'
received request (Success)
Request: 'bye world
'
response sent (Success)
Received server response: 'dlrow eyb
'
received request (End of file)
Disconnected 127.0.0.1:49778
accepted (Success)
received request (Success)
Request: 'hello world
'
response sent (Success)
Received server response: 'dlrow olleh
'
received request (Success)
Request: 'bye world
'
response sent (Success)
Received server response: 'dlrow eyb
'
received request (End of file)
Disconnected 127.0.0.1:49780
accepted (Operation canceled)

Note There's a startup race. Depending on your luck, the first Client might try to connect before the Server has started listening. I'm assuming this is not your worst worry, and I'll leave it as an exercise for the reader.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM