I made my server based on boost coroutine echo server example, simply receives and writes back some data. It crashes when writing data to client, and more strangely, it only crashes when using mutiple cores.
Here's the server, it reads 4 bytes and write back "OK", within 1 second as timeout:
#include <winsock2.h>
#include <windows.h>
#include <iostream>
using namespace std;
#include <boost/thread/thread.hpp>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
using namespace boost;
using namespace boost::asio;
using namespace boost::asio::ip;
#define SERVER_PORT 1234
#define DATA_LEN_4 4
#define TIMEOUT_LIMIT 1 // second
struct session : public std::enable_shared_from_this<session>
{
tcp::socket socket_;
boost::asio::steady_timer timer_;
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
explicit session(boost::asio::io_context& io_context, tcp::socket socket)
: socket_(std::move(socket)),
timer_(io_context),
strand_(io_context.get_executor())
{ }
void go()
{
auto self(shared_from_this());
boost::asio::spawn(strand_, [this, self](boost::asio::yield_context yield)
{
try
{
timer_.expires_from_now(std::chrono::seconds(TIMEOUT_LIMIT));
// recv data
string packet;
packet.resize(DATA_LEN_4); // alloc memory
size_t received_len = 0;
// read data
{
size_t rs;
while(received_len < DATA_LEN_4) { // recv 4 bytes
boost::system::error_code ec;
rs = socket_.async_read_some(
boost::asio::buffer((char*)(packet.c_str()+received_len), DATA_LEN_4-received_len), yield[ec]);
if(ec==boost::asio::error::eof)
break; //connection closed cleanly by peer
else if(ec) {
throw "read_fail";
}
received_len += rs;
}
}
if(received_len < DATA_LEN_4) {
throw "recv too short, maybe timeout";
}
// write back "OK"
{
boost::system::error_code ecw;
boost::asio::async_write(socket_, boost::asio::buffer(string("OK")), yield[ecw]);
if(ecw==boost::asio::error::eof)
return; //connection closed cleanly by peer
else if(ecw)
throw "write_fail"; // some other error
}
}
catch (const char* reason)
{
printf("exception reason: %s\n", reason);
boost::system::error_code ecw;
/*
* Question 1: why this 'async_write' line causes crash?
*/
// write the error reason to client
boost::asio::async_write(socket_, boost::asio::buffer(string(reason)), yield[ecw]);
socket_.close();
timer_.cancel();
}
catch (...)
{
printf("unknown exception\n");
socket_.close();
timer_.cancel();
}
});
boost::asio::spawn(strand_, [this, self](boost::asio::yield_context yield)
{
while (socket_.is_open())
{
boost::system::error_code ignored_ec;
timer_.async_wait(yield[ignored_ec]);
if (timer_.expires_from_now() <= std::chrono::seconds(0))
socket_.close();
}
});
}
};
int main() {
boost::asio::io_context io_context;
boost::asio::spawn(io_context, [&](boost::asio::yield_context yield)
{
tcp::acceptor acceptor(io_context,
tcp::endpoint(tcp::v4(), SERVER_PORT));
for (;;)
{
boost::system::error_code ec;
tcp::socket socket(io_context);
acceptor.async_accept(socket, yield[ec]);
if (!ec)
std::make_shared<session>(io_context, std::move(socket))->go();
}
});
/*
* When run on 1 CPU, it runs fine, no Crash
*/
// io_context.run();
/*
* Question 2:
* But when run on multiple CPUs, it Crashes !!!
* Why?
*/
auto thread_count = std::thread::hardware_concurrency();
boost::thread_group tgroup;
for (auto i = 0; i < thread_count; ++i)
tgroup.create_thread(boost::bind(&boost::asio::io_context::run, &io_context));
tgroup.join_all();
}
Please note, 4-bytes-packet and 1 second timeout is just to illustrate the problem, the real server uses large packets which may cause timeout on bad network condition. To simulate this, client writes 1 byte per second to trigger the read timeout on server.
The client:
#include <iostream>
#include <boost/asio.hpp>
using namespace std;
using boost::asio::ip::tcp;
#define SERVER "127.0.0.1"
#define PORT "1234"
int main() {
boost::asio::io_context io_context;
unsigned i = 1;
while(1) {
try {
tcp::socket s(io_context);
tcp::resolver resolver(io_context);
boost::asio::connect(s, resolver.resolve(SERVER, PORT));
// to simulate the bad network condition,
// write 4 bytes in 4 seconds to trigger the receive timeout on server, which is 1 second
for(int i=0; i<4; i++) {
boost::asio::write(s, boost::asio::buffer(string("A")));
std::this_thread::sleep_for(std::chrono::seconds(1)); // sleep 1 second
}
// read echo
char x[64] = {0};
s.read_some(boost::asio::buffer(x, sizeof(x)));
cout << i++ << ". received: " << x << endl;
} catch (...) {
cout << i++ << " exception" << endl;
}
}
return 0;
}
Question 1:
Why this lines causes crash ?
boost::asio::async_write(socket_, boost::asio::buffer(string(reason)), yield[ecw]);
Question 2:
Why the server doesn't crash when it runs on 1 cpu: io_context.run();
?
And crashes on multiple CPUs using thread_group
?
My environment: Win10-64bit, boost-1.71.0-64bit, VisualStudio-2017-Community