Skip to content

libcurl does not finish CURLOPT_UPLOAD request after a connection lost without data tranfer #11769

@oleg-jukovec

Description

@oleg-jukovec

I did this

I am trying to use the libcurl to communicate with etcd HTTP stream API. The idea of the API to send and receive JSON messages (transferred with https://en.wikipedia.org/wiki/Chunked_transfer_encoding). Everything functions properly until the connection with etcd is unexpectedly lost. The libcurl does not perform/finishes a request with an error until I actually send some data.

The reproducer:

#include <curl/curl.h>

size_t read_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
	/* Just ensure that it works:
	 * static int i = 0;
	 * printf("read_callback %d\n", i++);
	*/

	/*
	 *  You could uncomment the line to ensure that a data transfer helps to perform
	 *  a request after a connection lost.
	 */
	/* if (0) */
	{
		return CURL_READFUNC_PAUSE;
	}
	ptr[0] = '\n';
	return 1;
}

int progress_callback(void *clientp,
                      double dltotal,
                      double dlnow,
                      double ultotal,
                      double ulnow) {
	/*
	 * Ensure that the continue does not actually help without a
	 * data transfer.
	 */
	CURL *curl = (CURL*) clientp;
	curl_easy_pause(curl, CURLPAUSE_CONT);
	return 0;
}

int main() {
	CURL *curl = curl_easy_init();
	if(curl) {
  		/* We want to use our own read function. */
  		curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);

		/* It will help us to continue the read function. */
		curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
		curl_easy_setopt(curl, CURLOPT_XFERINFODATA, curl);
		curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);

		/* It will help us to ensure that keepalive does not help. */
		curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
		curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 1L);
		curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 1L);

  		/* Enable uploading. */
		curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
  		curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
 
  		curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:2379/v3/watch");
		curl_easy_perform(curl);
	}
}

To reproduce:

  1. Download etcd server.
  2. Extract it and run: ./etcd
  3. Save the reproducer as test.c
  4. Compile and run the reproducer: gcc test.c -lcurl && ./a.out
  5. Close etcd server: kill -SIGKILL $(pidof etcd)
  6. See that the reproducer hangs.

I would happily to use an another server to reproduce the error instead of etcd if you help me choose a relevant software..

I expected the following

I expected that the request finishes (with an error) due to a lost connection. So the reproducer will finish too (it actually happens with a data transfer, see comments in read_callback.

curl/libcurl version

curl 8.2.1 (x86_64-pc-linux-gnu) libcurl/8.2.1 OpenSSL/3.1.2 zlib/1.3 brotli/1.0.9 zstd/1.5.5 libidn2/2.3.4 libpsl/0.21.2 (+libidn2/2.3.4) libssh2/1.11.0 nghttp2/1.55.1
Release-Date: 2023-07-26

operating system

Archlinux

Linux host 6.4.11-arch2-1 #1 SMP PREEMPT_DYNAMIC Sat, 19 Aug 2023 15:38:34 +0000 x86_64 GNU/Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions