Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client keep-alive support is broken #1041

Closed
gjasny opened this issue Sep 8, 2021 · 27 comments
Closed

Client keep-alive support is broken #1041

gjasny opened this issue Sep 8, 2021 · 27 comments

Comments

@gjasny
Copy link
Contributor

gjasny commented Sep 8, 2021

Hello,

I recently added this http library to server HTTP requests within the ccache compiler cache. Because a compilation requires up four HTTP requests I enabled keep-alive support in the Client.

But we observed a race condition on Linux:

  1. Connection is opened and a GET request is issued. Turns out to be a cache miss
  2. Compilation starts and takes 90s, meanwhile the server closes the connection
  3. A PUT is issued on the now closed connection
  4. The ccache process receives SIGPIPE.

Somehow the is_alive detection does not work as expected. A rough test case looks like this:

TEST(KeepAlive, SimpleInterface_Online) {

  const auto host = "127.0.0.1";
  const auto port = 8080;
  const auto resourcePath = "/hi";

  Server svr;

  svr.set_keep_alive_timeout(3);

  svr.Get(resourcePath, [](const httplib::Request &, httplib::Response &res) {
    res.set_content("Hello World!", "text/plain");
  });

  auto a2 = std::async(std::launch::async, [&svr, host, port]{ svr.listen(host, port); });

  std::this_thread::sleep_for(std::chrono::milliseconds(200));

  Client cli(host, port);
  cli.set_keep_alive(true);

  auto result = cli.Get(resourcePath);
  ASSERT_TRUE(result);
  EXPECT_EQ(200, result->status);

  std::this_thread::sleep_for(std::chrono::seconds(5));

  result = cli.Get(resourcePath);
  ASSERT_TRUE(result);
  EXPECT_EQ(200, result->status);

  svr.stop();
  a2.wait();
}

Besides the non-working is_alive detection the library should set the MSG_NOSIGNAL on Linux and SO_NOSIGPIPE on Apple platforms to not badly interfere with the library host process.

Thanks,
Gregor

@gjasny
Copy link
Contributor Author

gjasny commented Sep 8, 2021

Call stack leading to SIGPIPE:

#0  0x00007f709fc5b730 in __libc_send (fd=5, buf=0x7f709e7a8aa2, len=9514928, flags=0) at ../sysdeps/unix/sysv/linux/send.c:28
#1  0x000055c1b4b021f2 in httplib::detail::SocketStream::<lambda()>::operator()(void) const (__closure=0x7ffd17839a90) at ../src/third_party/httplib.cpp:2595
#2  0x000055c1b4b157b6 in httplib::detail::handle_EINTR<httplib::detail::SocketStream::write(char const*, size_t)::<lambda()> >(httplib::detail::SocketStream::<lambda()>) (fn=...)
    at ../src/third_party/httplib.cpp:380
#3  0x000055c1b4b0225d in httplib::detail::SocketStream::write (this=0x7ffd1783a2f0, 
    ptr=0x7f709e7a8aa2 "\302R&\205]\253\224\354\373R\331\227\220q8\317!{*\352\224\031\204\231(\203\020\021\364p\342\325\254!\250;\377\205\225\263\225~a\001\355\372.\354:\335\273\341;Pk\364\265\366\300\327\n\003*j?k\351\274-]\307sr\347\262\342\022\356H%\\B\251A:\002\323\235+\246\270\a\361\265\250\aqL1\257\342\212\351\347\325\204\311$\337\017\273J\272\266Mw\335bi\345\375G-(\221\277\247\333\355v\223\375w\212\255\032j?b\210\351\372\231Xs}\235z", size=9514928) at ../src/third_party/httplib.cpp:2594
#4  0x000055c1b4afe56e in httplib::detail::write_data (strm=..., d=0x7f709e761010 "cCrS\001\001\001", l=9808450) at ../src/third_party/httplib.cpp:1502
#5  0x000055c1b4b0bbee in httplib::ClientImpl::write_request (this=0x55c1b500c390, strm=..., req=..., close_connection=false, error=@0x7ffd1783a5c4: httplib::Error::Success)
    at ../src/third_party/httplib.cpp:4140
#6  0x000055c1b4b0c4bf in httplib::ClientImpl::process_request (this=0x55c1b500c390, strm=..., req=..., res=..., close_connection=false, error=@0x7ffd1783a5c4: httplib::Error::Success)
    at ../src/third_party/httplib.cpp:4262
#7  0x000055c1b4b0aa3b in httplib::ClientImpl::handle_request (this=0x55c1b500c390, strm=..., req=..., res=..., close_connection=false, error=@0x7ffd1783a5c4: httplib::Error::Success)
    at ../src/third_party/httplib.cpp:3912
#8  0x000055c1b4b0a003 in httplib::ClientImpl::<lambda(httplib::Stream&)>::operator()(httplib::Stream &) const (__closure=0x55c1b5016d60, strm=...) at ../src/third_party/httplib.cpp:3854
#9  0x000055c1b4b1a134 in std::_Function_handler<bool(httplib::Stream&), httplib::ClientImpl::send(httplib::Request&, httplib::Response&, httplib::Error&)::<lambda(httplib::Stream&)> >::_M_invoke(const std::_Any_data &, httplib::Stream &) (__functor=..., __args#0=...) at /usr/include/c++/9/bits/std_function.h:285
#10 0x000055c1b4b26319 in std::function<bool (httplib::Stream&)>::operator()(httplib::Stream&) const (this=0x7ffd1783a370, __args#0=...) at /usr/include/c++/9/bits/std_function.h:688
#11 0x000055c1b4afc557 in httplib::detail::process_client_socket(int, long, long, long, long, std::function<bool (httplib::Stream&)>) (sock=5, read_timeout_sec=20, read_timeout_usec=0, write_timeout_sec=20, 
    write_timeout_usec=0, callback=...) at ../src/third_party/httplib.cpp:596
#12 0x000055c1b4b0cab7 in httplib::ClientImpl::process_socket(httplib::ClientImpl::Socket const&, std::function<bool (httplib::Stream&)>) (this=0x55c1b500c390, socket=..., callback=...)
    at ../src/third_party/httplib.cpp:4344
#13 0x000055c1b4b0a3d4 in httplib::ClientImpl::send (this=0x55c1b500c390, req=..., res=..., error=@0x7ffd1783a5c4: httplib::Error::Success) at ../src/third_party/httplib.cpp:3853
#14 0x000055c1b4b0bec7 in httplib::ClientImpl::send_with_content_provider(httplib::Request&, char const*, unsigned long, std::function<bool (unsigned long, unsigned long, httplib::DataSink&)>, std::function<bool (unsigned long, httplib::DataSink&)>, char const*, httplib::Error&) (this=0x55c1b500c390, req=..., body=0x7f709f0bc010 "cCrS\001\001\001", content_length=9808450, content_provider=..., 
    content_provider_without_length=..., content_type=0x55c1b4bcb604 "application/octet-stream", error=@0x7ffd1783a5c4: httplib::Error::Success) at ../src/third_party/httplib.cpp:4228
#15 0x000055c1b4b0c0d6 in httplib::ClientImpl::send_with_content_provider(char const*, char const*, std::multimap<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, httplib::detail::ci, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > const&, char const*, unsigned long, std::function<bool (unsigned long, unsigned long, httplib::DataSink&)>, std::function<bool (unsigned long, httplib::DataSink&)>, char const*) (this=0x55c1b500c390, method=0x55c1b4bce36d "PUT", path=0x55c1b50e0820 "/cache/135di89r02vv5fgopbtsopfjllo01b8lg", Python Exception <class 'AttributeError'> 'NoneType' object has no attribute 'pointer': 
headers=std::multimap with 0 elements, 
    body=0x7f709f0bc010 "cCrS\001\001\001", content_length=9808450, content_provider=..., content_provider_without_length=..., content_type=0x55c1b4bcb604 "application/octet-stream")
    at ../src/third_party/httplib.cpp:4247
#16 0x000055c1b4b0f7d1 in httplib::ClientImpl::Put (this=0x55c1b500c390, path=0x55c1b50e0820 "/cache/135di89r02vv5fgopbtsopfjllo01b8lg", Python Exception <class 'AttributeError'> 'NoneType' object has no attribute 'pointer': 
headers=std::multimap with 0 elements, 
    body=0x7f709f0bc010 "cCrS\001\001\001", content_length=9808450, content_type=0x55c1b4bcb604 "application/octet-stream") at ../src/third_party/httplib.cpp:4604
#17 0x000055c1b4b0f6d2 in httplib::ClientImpl::Put (this=0x55c1b500c390, path=0x55c1b50e0820 "/cache/135di89r02vv5fgopbtsopfjllo01b8lg", body=0x7f709f0bc010 "cCrS\001\001\001", content_length=9808450, 
    content_type=0x55c1b4bcb604 "application/octet-stream") at ../src/third_party/httplib.cpp:4597
#18 0x000055c1b4b134d4 in httplib::Client::Put (this=0x55c1b50106c8, path=0x55c1b50e0820 "/cache/135di89r02vv5fgopbtsopfjllo01b8lg", body=0x7f709f0bc010 "cCrS\001\001\001", content_length=9808450, 
    content_type=0x55c1b4bcb604 "application/octet-stream") at ../src/third_party/httplib.cpp:5804

@yhirose
Copy link
Owner

yhirose commented Sep 9, 2021

@gjasny, for clarification, does MSG_NOSIGNAL fix the problem on Linux on your machine?

yhirose added a commit that referenced this issue Sep 13, 2021
@yhirose
Copy link
Owner

yhirose commented Sep 13, 2021

Any response please?

@gjasny
Copy link
Contributor Author

gjasny commented Sep 13, 2021

Hello,

I'm going to test ccache 4.4.1 which ignores the signal: ccache/ccache@b2a88e7

The test should be done tomorrow.

Thanks for your patience,
Gregor

@gjasny
Copy link
Contributor Author

gjasny commented Sep 14, 2021

Even with ignoring SIGPIPE it's still broken: ccache/ccache#934

@yhirose
Copy link
Owner

yhirose commented Sep 15, 2021

@gjasny, thanks for the additional comment.

@stingray-11
Copy link
Contributor

I'm having an issue where Post() returns an httplib::Result with a "Read" error code when using keep alive. It happens when doing a request with a long delay before it. My guess is that the server is terminating the connection and the HTTP lib is not picking up on it. Not sure if it's the same issue as here.

@yhirose
Copy link
Owner

yhirose commented Oct 14, 2021

@lightray22, I am not sure whether it's the same as @gjasny reported. Could you try to make the smallest possible code to reproduce it? It will tell the truth. Thanks!

@stingray-11
Copy link
Contributor

stingray-11 commented Oct 15, 2021

I think it is likely the same problem. I wrote a minimal program that produces a backtrace with valgrind. Ends with a SIGPIPE.

#include "httplib.h"
#include <iostream>
#include <thread>
#include <chrono>

httplib::Client httpClient("http://localhost");

void doRequest()
{
    httplib::Params urlParams {{"app","server"},{"action","getconfig"}};

    std::string url("/Andromeda2/api/index.php?" +
        httplib::detail::params_to_query_str(urlParams));

    httplib::MultipartFormDataItems postParams;

    std::cout << "doing request..." << std::endl;

    httplib::Result response(httpClient.Post(url.c_str(), postParams));

    std::cout << "request done!" << std::endl;

    if (!response) 
    { 
        std::cout << "failed"; std::cout << ": " << response.error() << std::endl;
    }
    else
    {
        std::cout << "success"; std::cout << ": " << response->status << " " << response->body << std::endl;

        httplib::Headers::iterator it = response->headers.begin();
        for (; it != response->headers.end(); it++)
        {
            std::cout << "\t" << it->first << " " << it->second << std::endl;
        }
    }
}

int main()
{
    httpClient.set_keep_alive(true);

    for (int wait : {1, 5, 90, 120, 150})
    {
        std::cout << "waiting " << wait << " seconds..." << std::endl;

        std::this_thread::sleep_for(std::chrono::seconds(wait));

        doRequest();
    }

    std::cout << "return" << std::endl; return 1;
}

Compile and run... g++ test.cpp -g -o test && valgrind -v ./test

Program output...

waiting 1 seconds...
doing request...
request done!
success: 200 {"ok":true,"code":200,"appdata":{"api":2,"apps":{"server":"2.0","accounts":"2.0","files":"2.0","test":"2.0"},"features":{"enabled":true,"read_only":"off"}}}
        Cache-Control no-cache
        Connection keep-alive
        Content-Type application/json
        Date Fri, 15 Oct 2021 19:34:36 GMT
        Server nginx/1.20.1
        Transfer-Encoding chunked
waiting 5 seconds...
doing request...
request done!
success: 200 {"ok":true,"code":200,"appdata":{"api":2,"apps":{"server":"2.0","accounts":"2.0","files":"2.0","test":"2.0"},"features":{"enabled":true,"read_only":"off"}}}
        Cache-Control no-cache
        Connection keep-alive
        Content-Type application/json
        Date Fri, 15 Oct 2021 19:34:41 GMT
        Server nginx/1.20.1
        Transfer-Encoding chunked
waiting 90 seconds...
doing request...
==179226==
==179226== Process terminating with default action of signal 13 (SIGPIPE)
==179226==    at 0x4BAF3E0: send (send.c:28)
==179226==    by 0x11BDD7: httplib::detail::SocketStream::write(char const*, unsigned long)::{lambda()#1}::operator()() const (httplib.h:4399)
==179226==    by 0x12A714: long httplib::detail::handle_EINTR<httplib::detail::SocketStream::write(char const*, unsigned long)::{lambda()#1}>(httplib::detail::SocketStream::write(char const*, unsigned long)::{lambda()#1}) (httplib.h:2185)
==179226==    by 0x11BE42: httplib::detail::SocketStream::write(char const*, unsigned long) (httplib.h:4398)
==179226==    by 0x1187ED: httplib::detail::write_data(httplib::Stream&, char const*, unsigned long) (httplib.h:3306)
==179226==    by 0x124465: httplib::ClientImpl::write_request(httplib::Stream&, httplib::Request&, bool, httplib::Error&) (httplib.h:5944)
==179226==    by 0x124D3C: httplib::ClientImpl::process_request(httplib::Stream&, httplib::Request&, httplib::Response&, bool, httplib::Error&) (httplib.h:6066)
==179226==    by 0x1232B4: httplib::ClientImpl::handle_request(httplib::Stream&, httplib::Request&, httplib::Response&, bool, httplib::Error&) (httplib.h:5716)
==179226==    by 0x1222CA: httplib::ClientImpl::send(httplib::Request&, httplib::Response&, httplib::Error&)::{lambda(httplib::Stream&)#1}::operator()(httplib::Stream&) const (httplib.h:5658)
==179226==    by 0x1481BE: bool std::__invoke_impl<bool, httplib::ClientImpl::send(httplib::Request&, httplib::Response&, httplib::Error&)::{lambda(httplib::Stream&)#1}&, httplib::Stream&>(std::__invoke_other, httplib::ClientImpl::send(httplib::Request&, httplib::Response&, httplib::Error&)::{lambda(httplib::Stream&)#1}&, httplib::Stream&) (invoke.h:60)
==179226==    by 0x140A9E: std::enable_if<std::__and_<std::__not_<std::is_void<bool> >, std::is_convertible<std::__invoke_result<httplib::ClientImpl::send(httplib::Request&, httplib::Response&, httplib::Error&)::{lambda(httplib::Stream&)#1}&, httplib::Stream&>::type, std::is_void> >::value, std::is_void>::type std::__invoke_r<bool, httplib::ClientImpl::send(httplib::Request&, httplib::Response&, httplib::Error&)::{lambda(httplib::Stream&)#1}&, httplib::Stream&>(std::__invoke_result&&, (httplib::ClientImpl::send(httplib::Request&, httplib::Response&, httplib::Error&)::{lambda(httplib::Stream&)#1}&)...) (invoke.h:141)
==179226==    by 0x13737A: std::_Function_handler<bool (httplib::Stream&), httplib::ClientImpl::send(httplib::Request&, httplib::Response&, httplib::Error&)::{lambda(httplib::Stream&)#1}>::_M_invoke(std::_Any_data const&, httplib::Stream&) (std_function.h:291)
--179226-- Discarding syms at 0x51d8560-0x51df08c in /usr/lib/x86_64-linux-gnu/libnss_files-2.33.so (have_dinfo 1)
==179226==
==179226== HEAP SUMMARY:
==179226==     in use at exit: 10,149 bytes in 40 blocks
==179226==   total heap usage: 3,328 allocs, 3,288 frees, 144,349 bytes allocated
==179226==
==179226== Searching for pointers to 40 not-freed blocks
==179226== Checked 141,888 bytes
==179226==
==179226== LEAK SUMMARY:
==179226==    definitely lost: 0 bytes in 0 blocks
==179226==    indirectly lost: 0 bytes in 0 blocks
==179226==      possibly lost: 0 bytes in 0 blocks
==179226==    still reachable: 10,149 bytes in 40 blocks
==179226==         suppressed: 0 bytes in 0 blocks
==179226== Rerun with --leak-check=full to see details of leaked memory
==179226==
==179226== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

I'm guessing that the server terminated the connection and httplib does not handle it.

@yhirose
Copy link
Owner

yhirose commented Oct 15, 2021

@lightray22, thanks for the detailed information! I now got two evidences. I am too busy to work on it though, hope I'll get back to this problem sometime this year.

@stingray-11
Copy link
Contributor

Also my compiler is g++ (Ubuntu 10.3.0-1ubuntu1) 10.3.0

@tsilia
Copy link
Contributor

tsilia commented Dec 11, 2021

Hello @yhirose
I tried what you suggested here:

@gjasny, for clarification, does MSG_NOSIGNAL fix the problem on Linux on your machine?

and I can confirm it really works for another issue I described in #1121 after you had fixed the original one.
I did it as simple as that:

#ifndef CPPHTTPLIB_RECV_FLAGS
#define CPPHTTPLIB_RECV_FLAGS MSG_NOSIGNAL
#endif

#ifndef CPPHTTPLIB_SEND_FLAGS
#define CPPHTTPLIB_SEND_FLAGS MSG_NOSIGNAL
#endif

and hope it's the correct way.
Thanks again!

@yhirose
Copy link
Owner

yhirose commented Dec 12, 2021

@lightray22, could you take a look at the @tsilia's comment above. At least, the SIGPIPE issue can be handled accordingly.

@stingray-11
Copy link
Contributor

stingray-11 commented Dec 12, 2021

Based on @gjasny 's comment it seems that might fix the crashing issue but the keep-alive still won't work correctly.

@yhirose
Copy link
Owner

yhirose commented Dec 12, 2021

@gjasny, I finally started investigating this problem. As you mentioned, is_alive doesn't say false after the server closes the socket due to expiration of keep-alive timeout. Here is the line that causes the problem.

is_alive = detail::select_write(socket_.sock, 0, 0) > 0;

return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);

I thought select system call with timeout second 0 will tell us whether the socket is still alive or closed. But apparently it doesn't... According to my research, there is basically no way to detect a TCP socket closed unless read/recv is used... The following stackoverflow post is one of them explaining this fact.
https://stackoverflow.com/questions/6404008/how-to-detect-a-tcp-socket-disconnection-with-c-berkeley-socket

Please let me know if you know a cross-platform way to detect a socket closed on the other side. I'll also do more research on it. Thanks for your help!

@tsilia
Copy link
Contributor

tsilia commented Dec 13, 2021

@yhirose

I thought select system call with timeout second 0 will tell us whether the socket is still alive or closed. But apparently it doesn't...

This is probably because you only use write FDs in the mentioned select call. I read somewhere that if remote party closes the connection gracefully (i.e. using close/closesocket) then select with a read FD on your end will indicate an available read operation, and if a subsequent call to read (or recv) returns 0 it means the connection has been closed.

Maybe you should consider trying this approach.

@yhirose
Copy link
Owner

yhirose commented Dec 13, 2021

@tsilia, thanks for the comment. But unless the client send (write) a HTTP request, there won't be any data to read. So I don't think this won't work. Am I missing something to understand your idea fully?

@tsilia
Copy link
Contributor

tsilia commented Dec 13, 2021

Yes, I know the server woulnd't write to the socket on its end until we asked it to.
The idea is that socket is made readable by the client network stack, not by the server's writing to it (it should work for stream sockets, but not for datagram ones, as 0-sized datagrams are allowed). And you can't actually read anything from the socket in this particular case, you can only attempt to read and the ::read (or ::recv) function will return 0 which indicates that socket has been closed by the remote party.

@tsilia
Copy link
Contributor

tsilia commented Dec 13, 2021

Ok, this is kinda quick and dirty, but it actually works (I borrowed the socket-related code from you and someone else). You can try it yourself:

void ClientThreadFunc(bool& bStop)
{
	struct addrinfo hints, *res;
	int sockfd;

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	getaddrinfo(ServerAddress.c_str(), std::to_string(ServerPort).c_str(), &hints, &res);
	sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
	connect(sockfd, res->ai_addr, res->ai_addrlen);

	while (!bStop)
	{
		fd_set fdsr;
		FD_ZERO(&fdsr);
		FD_SET(sockfd, &fdsr);

		fd_set fdsw;
		FD_ZERO(&fdsw);
		FD_SET(sockfd, &fdsw);

		timeval tv;
		tv.tv_sec = static_cast<long>(0);
		tv.tv_usec = static_cast<decltype(tv.tv_usec)>(0);

		auto sel = select(static_cast<int>(sockfd + 1), &fdsr, nullptr, nullptr, &tv);
		if (sel == 1)
		{
			if (FD_ISSET(sockfd, &fdsr))
			{
				char buf[1]{};
				if (read(sockfd, &buf[0], sizeof(buf)) == 0)
				{
					std::cout << "Remote peer disconnected!" << std::endl;
					break;
				}
			}
		}
		std::this_thread::sleep_for(1ms);
	}
}

int main()
{
	bool bStop = false;
	std::thread t(&ClientThreadFunc, std::ref(bStop));

	std::cout << "Press enter key to stop client..." << std::endl;
	std::cin.get();
	std::cerr << "* Stopping client..." << std::endl;
	bStop = true;
	if (t.joinable())
		t.join();

	std::cerr << "* Client stopped. Exiting..." << std::endl;
	return 0;
}

This code doesn't even need to write to socket anything, you can connect to a cpphttplib-powered server which has keep alive turned on and timeout set to, say, 5 seconds, and wait for the server to close socket. Once the server does so, the client will output 'Remote peer disconnected!'.

Sorry if my comment was misleading or confusing. I probably should have mentioned that it's actually the TCP protocol that allows this possible, because when a peer closes the connection gracefully, there's some stuff going on between the peers at the TCP level.

Detecting a lost connection (e.g., hung) is completely different story though. You will probably have to use TCP keep alive feature which can be turned on using SO_KEEPALIVE at SOL_SOCKET level.

@tsilia
Copy link
Contributor

tsilia commented Dec 14, 2021

@yhirose
Sorry, I must say that I screwed up the code, but the idea is working anyway, just not in the old snippet above ^

I've fixed the previous post.

yhirose added a commit that referenced this issue Dec 14, 2021
yhirose added a commit that referenced this issue Dec 14, 2021
@yhirose
Copy link
Owner

yhirose commented Dec 14, 2021

@gjasny, @tsilia, I think I fixed the problem. Could you take a look at my change and unit test? If you don't find anything, I'll merge it. Thanks a lot for your help!
https://github.com/yhirose/cpp-httplib/pull/1132/files

@gjasny
Copy link
Contributor Author

gjasny commented Dec 14, 2021

I'll try to test it this week.

Thanks,
Gregor

@tsilia
Copy link
Contributor

tsilia commented Dec 14, 2021

@yhirose
Thanks for the change.

The unit test is passing on my Linux machine (Ubuntu 20.04)

But there's something wrong.
Here's how the output of my test POC looked like before this change
(I've put a debug message 'close_socket' into the detail::close_socket function):

./client                                                ./server 
Press enter key to stop client...                       Press enter key to stop server...
* Socket is not open!                                   2064301129
Successfully posted 2064301129                          196863862
Successfully posted 196863862                           2023120747
Successfully posted 2023120747                          1317591912
Successfully posted 1317591912                          1171891629
Successfully posted 1171891629                          199499494
Successfully posted 199499494                           379010654
Successfully posted 379010654                           723693663
Successfully posted 723693663                           173929576
Successfully posted 173929576                           1809587108
Successfully posted 1809587108                          1041672509
Successfully posted 1041672509                          46698475
Successfully posted 46698475                            306287642
Successfully posted 306287642                           641106898
Successfully posted 641106898                           close_socket
* Stopping client...                                    * Stopping server...
close_socket                                            close_socket
* Client stopped. Exiting...                            * Server stopped. Exiting...

But after the change the output is totally different; looks like now the same program recompiled with the issue1041 branch closes socket after each send:

./client                                            ./server 
Press enter key to stop client...                   Press enter key to stop server...
* Socket is not open!                               814967304
Successfully posted 814967304                       close_socket
close_socket                                        1928591877
Successfully posted 1928591877                      close_socket
close_socket                                        1746614995
Successfully posted 1746614995                      close_socket
close_socket                                        456466714
Successfully posted 456466714                       close_socket
close_socket                                        970170489
Successfully posted 970170489                       close_socket
close_socket                                        1833997372
Successfully posted 1833997372                      close_socket
* Stopping client...                                * Stopping server...
close_socket                                        close_socket
* Client stopped. Exiting...                        * Server stopped. Exiting...

I also commented in the pull request.

UPD: I've checked with ss -ta and the screen is flooded with TCP connections in the TIME-WAIT state and destination address:port matching my POC server. So, the program does close the socket on each send.

@yhirose
Copy link
Owner

yhirose commented Dec 14, 2021

@tsilia, thanks for the quick code review. I updated is_socket_alive accordingly. Could you test again?

@yhirose
Copy link
Owner

yhirose commented Dec 17, 2021

@tsilia, thanks for all your reviews and suggestions for the pull request! It has finally been resolved. :)

@gjasny, @lightray22, I would like to let you know that all the unit tests and @tsilia's test (https://github.com/tsilia/poc-httplib-inf-keepalive) passed successfully, and merged the pull request to the master. Please let me know if you find anything.

Thanks.

@tsilia
Copy link
Contributor

tsilia commented Dec 17, 2021

@yhirose
Thank you for all the time and efforts you put into this library!

For testing, I would suggest using @lightray22's code:
#1041 (comment)
rather than my POC as it's specific to the #1121 issue which had been resolved.
For #1041 I only used my POC with a modified version of cpp-httplib (with debug output) to detect crashes or connections being closed when not necessary. Aside from that, I've been using slightly modified @lightray's code I referenced above.

@stingray-11
Copy link
Contributor

Sorry for the delayed response... the new code works well for me also. Thanks!

ExclusiveOrange pushed a commit to ExclusiveOrange/cpp-httplib-exor that referenced this issue May 2, 2023
* Fix yhirose#1041

* Fixed problem with is_socket_alive

* Adjust the way to check if the sockt is still alive.

* Revert "Adjust the way to check if the sockt is still alive."

This reverts commit 6c673b2.

* Adjust is_socket_alive according to the code review
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants