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

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

Closed
oleg-jukovec opened this issue Aug 30, 2023 · 4 comments

Comments

@oleg-jukovec
Copy link
Contributor

oleg-jukovec commented Aug 30, 2023

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

@oleg-jukovec oleg-jukovec changed the title CURLOPT_UPLOAD does not finished a request due to a lost connection without data tranfer libcurl does not finish CURLOPT_UPLOAD request after a connection lost without data tranfer Aug 30, 2023
@bagder
Copy link
Member

bagder commented Aug 31, 2023

I can reproduce. I suspect it is related to the pause state. I have not figured it out yet.

What's curios in this scenario is that when the connection goes down and curl reads from the socket:

nread = sread(ctx->sock, buf, len);

... it gets a -1 back and EAGAIN in errno.

If I #ifdef out the sending from the reproducer so that it sends nothing and just waits for data to arrive (that never comes), it instead properly immediately reads a 0 on the connection break and exits as it should.

@icing
Copy link
Contributor

icing commented Aug 31, 2023

I have a fix in #11756 for http2 where the HOLD was not cleared for a closed stream and transfer happily kept at it. Maybe this is in the same area?

@bagder
Copy link
Member

bagder commented Sep 4, 2023

I have mostly figured it out, and it is not like #11756 since this happens with plain old HTTP/1.

I'm going to work on writing a test case that reproduces then submit a PR.

bagder added a commit that referenced this issue Sep 4, 2023
Previously this cleared the receiving bit only but in some cases it is
also still sending (like a request-body) when disconnected and neither
direction can continue then.

Fixes #11769
Reported-by: Oleg Jukovec
bagder added a commit that referenced this issue Sep 4, 2023
Previously this cleared the receiving bit only but in some cases it is
also still sending (like a request-body) when disconnected and neither
direction can continue then.

Fixes #11769
Reported-by: Oleg Jukovec
Closes #11795
@bagder bagder closed this as completed in 95a865b Sep 4, 2023
@oleg-jukovec
Copy link
Contributor Author

Thank you for the fix!

ptitSeb pushed a commit to wasix-org/curl that referenced this issue Sep 25, 2023
Previously this cleared the receiving bit only but in some cases it is
also still sending (like a request-body) when disconnected and neither
direction can continue then.

Fixes curl#11769
Reported-by: Oleg Jukovec
Closes curl#11795
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

3 participants