Skip to content

Write function callback is called twice after resume transfer and return CURL_WRITEFUNC_ERROR #13337

Description

@pkropachev

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions