asio
asio copied to clipboard
Read error on windows on half open socket
I am using boost::asio to implement a simple http client. The client implements HTTP/1.0, so it shuts down the TX side of the socket after sending the request.
On Windows systems, I get a receive error, if the transmission of the response pauses for more than 120 seconds. I get an error code 121 (ERROR_SEM_TIMEOUT). I crafted a little demo for this issue.My http client uses boost::asio::ip::tcp::iostream, but boost::asio::ip::tcp::socket shows the same problem. I have both versions in my demo:
#include <boost/asio.hpp>
#include <thread>
#include <chrono>
#include <stdio.h>
using namespace boost::asio::ip;
void server()
{
boost::asio::io_service io_service;
boost::system::error_code ec;
tcp::acceptor server(io_service, tcp::endpoint(tcp::v4(), 2000));
tcp::socket s(io_service);
server.accept(s, ec);
if (ec)
{
fprintf(stderr, "accept() failed: %s\n", ec.message().c_str());
return;
}
boost::asio::write(s, boost::asio::buffer("start\n", 6), ec);
if (ec)
{
fprintf(stderr, "write() failed: %s\n", ec.message().c_str());
return;
}
std::this_thread::sleep_for(std::chrono::seconds(130));
boost::asio::write(s, boost::asio::buffer("end\n", 4), ec);
if (ec)
{
fprintf(stderr, "write() failed: %s\n", ec.message().c_str());
return;
}
}
int main(int argc, char *argtv[])
{
std::thread(server).detach();
#ifdef RAW
boost::asio::io_service io_service;
boost::system::error_code ec;
boost::asio::ip::tcp::socket s(io_service);
s.connect(tcp::endpoint(address_v4::loopback(), 2000), ec);
if (ec)
{
fprintf(stderr, "connect(loopback, 2000) failed: %s\n", ec.message().c_str());
return 1;
}
s.shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
if (ec)
{
fprintf(stderr, "shutdown(shutdown_send) failed: %s\n", ec.message().c_str());
return 2;
}
for (;;)
{
char buffer[512];
size_t n = s.read_some(boost::asio::buffer(buffer, sizeof(buffer)), ec);
if (ec)
{
fprintf(stderr, "read_some() failed: %s\n", ec.message().c_str());
return 3;
}
fwrite(buffer, 1, n, stdout);
fflush(stdout);
}
return 0;
#else
boost::system::error_code ec;
boost::asio::ip::tcp::iostream s;
s.connect("127.0.0.1", "2000");
if (!s)
{
fprintf(stderr, "connect(loopback, 2000) failed: %s\n", s.error().message().c_str());
return 1;
}
s.rdbuf()->shutdown(boost::asio::ip::tcp::socket::shutdown_send);
std::string line;
while (std::getline(s, line))
{
printf("%s\n", line.c_str());
fflush(stdout);
}
ec = s.error();
if (ec)
{
fprintf(stderr, "getline() failed: %s\n", ec.message().c_str());
return 2;
}
return 0;
#endif
}
The server accepts a TCP connection, prints start, waits for 130 seconds,prints end and terminates. I expect the client to get both texts and to terminate afterwards.
Works fine on linux systems, but on WIndows, I get an error after 120s:
start
read_some() failed: Ein Verbindungsversuch ist fehlgeschlagen, da die Gegenstelle nach einer bestimmten Zeitspanne nicht richtig reagiert hat, oder die hergestellte Verbindung war fehlerhaft, da der verbundene Host nicht reagiert hat
D:\Projects\test\Debug\AsioStreamTimeout.exe (process 17804) exited with code 3.
I am using VS2017. I observed the problem in boost_1_63_0, but it is still present in boost_1_78_0.
I found out that the error is generated due to GetQueuedCompletionStatus() failing with ERROR_SEM_TIMEOUT.
When I do not call shutdown, the program works fine. Unfortunately, this is not an option for me, as in my client I, do not know the content length of the request when sending the HTTP request header.
Wow, this seems to be a f*cking windows bug. :-(
Also happens when using the native API:
WSADATA wsaData;
int err = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (err)
{
fprintf(stderr, "WSAStartup() failed\n");
return 1;
}
int s = socket(PF_INET, SOCK_STREAM, 0);
if (s < 0)
{
fprintf(stderr, "socket() failed\n");
return 2;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0)
{
fprintf(stderr, "connect() failed\n");
return 3;
}
if (shutdown(s, 1) < 0)
fprintf(stderr, "shutdown() failed\n");
for (;;)
{
char buffer[256];
int n = recv(s, buffer, sizeof(buffer), 0);
if (n < 0)
{
std::error_code ec(GetLastError(), std::system_category());
fprintf(stderr, "recv failed: %s\n", ec.message().c_str());
break;
}
if (!n)
break;
fwrite(buffer, 1, n, stdout);
}
Sorry for any inconvinience.
Hi @Mario-Klebsch,
What happens if you perform shutdown(boost::asio::ip::tcp::socket::shutdown_send...) in the client after receiving response? While I agree with your approach, usually TCP connection is closed after response is received and closing of connection for the client consists of: shutdown_send -> wait (with some timeout) for EOF when reading from socket -> close the socket.
There is a chance of some "race condition" b/w connect, accept and shutdown_send in your case. Maybe on Windows (what version of Windows was tested?) connect returns at client side before accept completed on server side (e.g. accept queue), which causes Winsock to consider the socket closed at some point. There also can be some TCP stack / socket configuration which closes TCP connection with some timeout after shutdown_send event.
Hi @mabrarov,
What happens if you perform shutdown(boost::asio::ip::tcp::socket::shutdown_send...) in the client after receiving response?
It doesn't change anything, if I shutdown the sender after receiving the first bytes of the response.
There is a chance of some "race condition" b/w connect, accept and shutdown_send in your case.
I do not think, this might be a problem. Usually the server is on a different system, so the client system does not even know, wether accept on the server system has returned. It is just my demonstration, thar runs client and server onthe same host in the same process.
what version of Windows was tested?
Windows 10 Version 21H1 Build 19043.1348
Hi @Mario-Klebsch,
I've created https://github.com/mabrarov/asio-964 to reproduce the issue. I also found https://stackoverflow.com/questions/13466639/why-is-socket-receive-timing-out-on-a-half-closed-connection-when-timeout-is-set which seems to be related to this issue (which is not related to Asio).
Hi @Mario-Klebsch
What happens if you perform shutdown(boost::asio::ip::tcp::socket::shutdown_send...) in the client after receiving response?
It doesn't change anything, if I shutdown the sender after receiving the first bytes of the response.
I was talking about something like mabrarov/asio-964#3, which works fine on Windows 10 for me.
@Mario-Klebsch have you looked into boost beast? They offer a decent HTTP client implementation.