I did this
We have the scenario when we put the transfer on pause and resume it after a while and then stop transfer with CURL_WRITEFUNC_ERROR. For that we register CURLOPT_WRITEFUNCTION callback and pause the transfer by returning CURL_WRITEFUNC_PAUSE from this callback. After some delay we resume transfer by calling curl_easy_pause(..., CURLPAUSE_CONT). Then after our callback is called we stop transfer by returning CURL_WRITEFUNC_ERROR. In this scenario we faced with the problem that our callback is called twice after it had returned CURL_WRITEFUNC_ERROR.
We reproduced the behavior using example of code from the comment, but slightly simplify it.
#include <curl/curl.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
bool paused;
int call_counter;
static size_t http_body(void *buffer, size_t size, size_t nmemb, void *userp) {
call_counter++;
fprintf(stderr, "http_body called at %d time\n", call_counter);
if (call_counter == 1) {
paused = true;
fprintf(stderr, "Return pause\n");
return CURL_WRITEFUNC_PAUSE;
}
fprintf(stderr, "Return abort\n");
return CURL_WRITEFUNC_ERROR;
}
int main(int argc, char **argv) {
CURLM *curl_multi_handle;
CURL *curl;
const char *url = "http://httpbin.org";
struct CURLMsg *msg;
int running_handles;
int msgs_left;
call_counter = 0;
paused = false;
curl_global_init(CURL_GLOBAL_DEFAULT);
curl_multi_handle = curl_multi_init();
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 1024);
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_body);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_multi_add_handle(curl_multi_handle, curl);
curl_multi_perform(curl_multi_handle, &running_handles);
while (running_handles > 0) {
usleep(100 * 1000);
/* Resume if transfer is paused */
if (paused) {
fprintf(stderr, "Resume transfer\n");
curl_easy_pause(curl, CURLPAUSE_CONT);
paused = false;
}
curl_multi_perform(curl_multi_handle, &running_handles);
}
msg = curl_multi_info_read(curl_multi_handle, &msgs_left);
fprintf(stderr, "Connection finished: %s\n",
curl_easy_strerror(msg->data.result));
curl_multi_remove_handle(curl_multi_handle, curl);
curl_easy_cleanup(curl);
curl_multi_cleanup(curl_multi_handle);
return 0;
}
In the result, we're observing the same behavior like in our use case.
#> ./pause-test
* Host httpbin.org:80 was resolved.
* IPv6: (none)
* IPv4: 3.223.18.102, 3.208.234.136, 35.168.90.70, 18.210.102.60, 18.204.54.143, 34.232.61.91, 54.87.28.144, 52.203.147.106
* Trying 3.223.18.102:80...
* Connected to httpbin.org (3.223.18.102) port 80
> GET / HTTP/1.1
Host: httpbin.org
Accept: */*
* Request completely sent off
< HTTP/1.1 200 OK
< via: proxy A
< Date: Wed, 10 Apr 2024 10:34:44 GMT
< Server: gunicorn/19.9.0
< Connection: Keep-Alive
< Content-Type: text/html; charset=utf-8
< Content-Length: 9593
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
<
http_body called at 1 time
Return pause
Resume transfer
http_body called at 2 time
Return abort
* Failure writing output to destination, passed 771 returned 4294967295
http_body called at 3 time <--- ?
Return abort
* Failure writing output to destination, passed 1795 returned 4294967295
http_body called at 4 time <--- ?
Return abort
* Failure writing output to destination, passed 1795 returned 4294967295
* Closing connection
Connection finished: Failed writing received data to disk/application
I expected the following
Callback should not be called after return CURL_WRITEFUNC_ERROR from it. Behavior is correct in case we don't put the transfer on pause.
Just comment the following block in the sample:
if (call_counter == 1) {
paused = true;
fprintf(stderr, "Return pause\n");
return CURL_WRITEFUNC_PAUSE;
}
curl/libcurl version
curl 8.7.1
curl 8.7.0-DEV (x86_64-pc-linux-gnu) libcurl/8.7.0-DEV BoringSSL zlib/1.2.11 c-ares/1.27.0 libpsl/0.21.0 nghttp2/1.60.0 quiche/0.20.1
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS HSTS HTTP2 HTTP3 HTTPS-proxy IPv6 Largefile libz NTLM PSL SSL threadsafe UnixSockets
operating system
Linux kwx1252784oftdls 5.13.0-52-generic #59~20.04.1-Ubuntu SMP Thu Jun 16 21:21:28 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
I did this
We have the scenario when we put the transfer on pause and resume it after a while and then stop transfer with
CURL_WRITEFUNC_ERROR. For that we registerCURLOPT_WRITEFUNCTIONcallback and pause the transfer by returningCURL_WRITEFUNC_PAUSEfrom this callback. After some delay we resume transfer by callingcurl_easy_pause(..., CURLPAUSE_CONT). Then after our callback is called we stop transfer by returningCURL_WRITEFUNC_ERROR. In this scenario we faced with the problem that our callback is called twice after it had returnedCURL_WRITEFUNC_ERROR.We reproduced the behavior using example of code from the comment, but slightly simplify it.
In the result, we're observing the same behavior like in our use case.
I expected the following
Callback should not be called after return
CURL_WRITEFUNC_ERRORfrom it. Behavior is correct in case we don't put the transfer on pause.Just comment the following block in the sample:
curl/libcurl version
curl 8.7.1
curl 8.7.0-DEV (x86_64-pc-linux-gnu) libcurl/8.7.0-DEV BoringSSL zlib/1.2.11 c-ares/1.27.0 libpsl/0.21.0 nghttp2/1.60.0 quiche/0.20.1
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS HSTS HTTP2 HTTP3 HTTPS-proxy IPv6 Largefile libz NTLM PSL SSL threadsafe UnixSockets
operating system
Linux kwx1252784oftdls 5.13.0-52-generic #59~20.04.1-Ubuntu SMP Thu Jun 16 21:21:28 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux