Skip to content

On MacOS, if a remote socket is closed while curl is connecting to it, the program will spin until the connection timeout elapses #7595

Closed
@Marc-Aldorasi-Imprivata

Description

I did this

Compiled and ran the following program:

#include <curl/curl.h>
#include <boost/asio.hpp>

using namespace boost::asio;

int progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
	static int progress_callback_times = 0;

	++progress_callback_times;
	// Close the socket when mstate == MSTATE_CONNECTING
	if (progress_callback_times == 2)
	{
		auto &a = *(ip::tcp::acceptor *)clientp;
		auto sock = a.accept();
		sock.close();
	}
	return 0;
}

int main()
{
	io_context ctx(1);
	ip::tcp::acceptor a(ctx.get_executor());

	a.open(ip::tcp::v4());
	socket_base::reuse_address ra(true);
	a.set_option(ra);
	ip::tcp::endpoint e{ip::address_v4::loopback(), 8080};
	a.bind(e);
	a.listen();

	auto easy = curl_easy_init();
	curl_easy_setopt(easy, CURLOPT_URL, "http://127.0.0.1:8080");
	curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L);
	curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 0L);
	curl_easy_setopt(easy, CURLOPT_XFERINFOFUNCTION, progress_callback);
	curl_easy_setopt(easy, CURLOPT_XFERINFODATA, &a);

	curl_easy_perform(easy);
	return 0;
}

I expected the following

The program should exit almost immediately, since it is attempting to communicate with a closed socket

What actually happened

The program consumed an entire CPU core for 5 minutes before the connection timed out.

This only happens on Mac; Linux works fine.

The issue appears to be that when waiting for the socket to be ready, multi_wait calls poll with events set to POLLOUT, and the OS sets revents to POLLOUT and returns immediately. However, when multi_runsingle calls poll it sets events to POLLOUT|POLLPRI and the OS sets revents to POLLHUP|POLLPRI. Curl interprets the POLLHUP as an error, but then when verifyconnect checks SO_ERROR it gets back 0 so it assumes that there wasn't actually an error. Because revents didn't contain POLLOUT curl thinks that it needs to wait for the socket to be writable, so it calls multi_wait and the cycle repeats until the connect timeout elapses.

curl/libcurl version

curl 7.78.0-DEV (Darwin) libcurl/7.78.0-DEV SecureTransport zlib/1.2.11
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher gophers http https imap imaps ldap mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp 
Features: alt-svc AsynchDNS Debug HSTS IPv6 Largefile libz NTLM SSL TrackMemory UnixSockets

I built curl off of commit bfbde88

operating system

Darwin NYC-L5169-MAC.local 19.5.0 Darwin Kernel Version 19.5.0: Tue May 26 20:41:44 PDT 2020; root:xnu-6153.121.2~2/RELEASE_X86_64 x86_64 i386 MacBookPro16,1 Darwin

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions