cURL / Mailing Lists / curl-library / Single Mail

curl-library

RE: Connection being reused following a cancellation (HTTP)

From: Vladimir Grishchenko <vladgri_at_hotmail.com>
Date: Fri, 30 Sep 2011 13:27:02 -0700

>
> On Tue, 27 Sep 2011, Vladimir Grishchenko wrote:
>
> > Is it possible that an early cancellation request (i.e. first call to the
> > progress callback function) may leave a connection open and available for
> > reuse despite the fact the headers were already sent out (HTTP)? This is
> > using the multi-intreface. We are still on 7.21.3, I looked at the change
> > log for later releases and couldn't find any bug fixes that were obviously
> > related to this.
>
> You're asking if a bug is possible? Yes, it is.
>
> I don't think we've seen any reports about it before though. Can you try to
> write up a recipe we can use to repeat it?
>
> --
>
> / daniel.haxx.se
> -------------------------------------------------------------------
> List admin: http://cool.haxx.se/list/listinfo/curl-library
> Etiquette: http://curl.haxx.se/mail/etiquette.html

 

 

 

Ok, I think I have a reproducible case in hand, which of course may be a user error. I'm going to try and attach the example and also copy it here. Look at the end of debug output, I don't know if reflects what is actually happening but it appears that an early cancellation leaves a connection alive despite the fact HTTP headers were already sent out. I see it happening with 7.22.0. Running on Win32.

 

Thanks in advance.

 

Debug output

---------------------------------------------------------------

 

0000: lotsa data
<= Recv data, 0000000010 bytes (0x0000000a)
0000: lotsa data
<= Recv data, 0000000010 bytes (0x0000000a)
0000: lotsa data
== Info: Connection #0 to host www.amazon.com left intact <------- END OF FIRST SUCCESSFUL LOAD
== Info: Re-using existing connection! (#0) with host www.amazon.com
== Info: Connected to www.amazon.com (72.21.194.1) port 80 (#0)
== Info: Callback aborted
=> Send header, 0000000053 bytes (0x00000035) <------------------- HEADER GOES OUT
0000: GET / HTTP/1.1
0010: Host: www.amazon.com
0026: Accept: */*
0033:
== Info: Callback aborted
== Info: Callback aborted <------------------- WE ASK TO STOP
== Info: Connection #0 to host www.amazon.com left intact <------- ISSUE: REQUEST OUTSTANDING BUT CONNECTION LEFT OPEN
== Info: Re-using existing connection! (#0) with host www.amazon.com
== Info: Connected to www.amazon.com (72.21.194.1) port 80 (#0)
== Info: Callback aborted
=> Send header, 0000000053 bytes (0x00000035)
0000: GET / HTTP/1.1
0010: Host: www.amazon.com
0026: Accept: */*
0033:
== Info: Callback aborted
== Info: Callback aborted
== Info: Connection #0 to host www.amazon.com left intact

 

Sample code

------------------------------------------------------------------------------------------------

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <curl/curl.h>

struct data {
  char trace_ascii; /* 1 or 0 */
};

static void dump(const char *text, FILE *stream, unsigned char *ptr, size_t size, char nohex)
{
  size_t i;
  size_t c;

  unsigned int width=0x10;

  if(nohex)
    /* without the hex output, we can fit more on screen */
    width = 0x40;

  fprintf(stream, "%s, %010.10ld bytes (0x%08.8lx)\n",
          text, (long)size, (long)size);

  for(i=0; i<size; i+= width) {

    fprintf(stream, "%04.4lx: ", (long)i);

    if(!nohex) {
      /* hex not disabled, show it */
      for(c = 0; c < width; c++)
        if(i+c < size)
          fprintf(stream, "%02x ", ptr[i+c]);
        else
          fputs(" ", stream);
    }

    for(c = 0; (c < width) && (i+c < size); c++) {
      /* check for 0D0A; if found, skip past and start a new line of output */
      if (nohex && (i+c+1 < size) && ptr[i+c]==0x0D && ptr[i+c+1]==0x0A) {
        i+=(c+2-width);
        break;
      }
      fprintf(stream, "%c",
              (ptr[i+c]>=0x20) && (ptr[i+c]<0x80)?ptr[i+c]:'.');
      /* check again for 0D0A, to avoid an extra \n if it's at width */
      if (nohex && (i+c+2 < size) && ptr[i+c+1]==0x0D && ptr[i+c+2]==0x0A) {
        i+=(c+3-width);
        break;
      }
    }
    fputc('\n', stream); /* newline */
  }
  fflush(stream);
}

static int my_trace(CURL *handle, curl_infotype type, char *data, size_t size, void *userp)
{
  char * dataToDump = data;
  struct data *config = (struct data *)userp;
  const char *text;
  (void)handle; /* prevent compiler warning */

  switch (type) {
  case CURLINFO_TEXT:
    fprintf(stderr, "== Info: %s", data);
  default: /* in case a new one is introduced to shock us */
    return 0;

  case CURLINFO_HEADER_OUT:
    text = "=> Send header";
    break;
  case CURLINFO_DATA_OUT:
    text = "=> Send data";
    break;
  case CURLINFO_SSL_DATA_OUT:
    text = "=> Send SSL data";
    break;
  case CURLINFO_HEADER_IN:
    text = "<= Recv header";
    break;
  case CURLINFO_DATA_IN:
 dataToDump = "lotsa data";
 size = 10;
    text = "<= Recv data";
    break;
  case CURLINFO_SSL_DATA_IN:
 dataToDump = "lotsa SSL data";
 size = 14;
    text = "<= Recv SSL data";
    break;
  }

  dump(text, stderr, (unsigned char *)dataToDump, size, config->trace_ascii);
  return 0;
}

static int abortTransfer = 0;

int progressCallback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
 return abortTransfer;
}

//no-op write callback to reduce screen clutter
size_t writeCallback(char * buf, size_t size, size_t nmemb, void * data) {
 return (nmemb * size);
}

void checkTransfers(CURLM * multi_handle) {
 CURLMsg * msg;
 int msgs_left;
 CURL * easy;
 while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) {
        if (msg->msg == CURLMSG_DONE) {
            easy = msg->easy_handle;

            //transfer done - remove easy from multi and cleanup easy
            curl_multi_remove_handle(multi_handle, easy);
   curl_easy_cleanup(easy);
        }
    }
}

void runMulti(CURLM * multi_handle, CURL * easy_handle) {
  int still_running; /* keep number of running handles */

  curl_multi_add_handle(multi_handle, easy_handle);

  /* we start some action by calling perform right away */
  
  curl_multi_perform(multi_handle, &still_running);
  checkTransfers(multi_handle);

  while(still_running) {
    struct timeval timeout;
    int rc; /* select() return code */

    fd_set fdread;
    fd_set fdwrite;
    fd_set fdexcep;
    int maxfd = -1;

    long curl_timeo = -1;

    FD_ZERO(&fdread);
    FD_ZERO(&fdwrite);
    FD_ZERO(&fdexcep);

    /* set a suitable timeout to play around with */
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;

    curl_multi_timeout(multi_handle, &curl_timeo);
    if(curl_timeo >= 0) {
      timeout.tv_sec = curl_timeo / 1000;
      if(timeout.tv_sec > 1)
        timeout.tv_sec = 1;
      else
        timeout.tv_usec = (curl_timeo % 1000) * 1000;
    }

    /* get file descriptors from the transfers */
    curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);

    /* In a real-world program you OF COURSE check the return code of the
       function calls. On success, the value of maxfd is guaranteed to be
       greater or equal than -1. We call select(maxfd + 1, ...), specially in
       case of (maxfd == -1), we call select(0, ...), which is basically equal
       to sleep. */

    rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);

    switch(rc) {
    case -1:
      /* select error */
      break;
    case 0:
    default:
      /* timeout or readable/writable sockets */
      curl_multi_perform(multi_handle, &still_running);
      break;
    }

 checkTransfers(multi_handle);
  }
}

int main(int argc, char *argv[])
{
  struct data config;

  CURL *http_handle;
  CURL *http_handle2;
  CURL *http_handle3;
  CURLM *multi_handle;

  http_handle = curl_easy_init();
  http_handle2 = curl_easy_init();
  http_handle3 = curl_easy_init();

  /* set options 1*/
  curl_easy_setopt(http_handle, CURLOPT_URL, "http://www.amazon.com/");
  curl_easy_setopt(http_handle, CURLOPT_NOPROGRESS, 0);
  curl_easy_setopt(http_handle, CURLOPT_PROGRESSFUNCTION, progressCallback);
  curl_easy_setopt(http_handle, CURLOPT_WRITEFUNCTION, writeCallback);
  curl_easy_setopt(http_handle, CURLOPT_VERBOSE, 1);
  curl_easy_setopt(http_handle, CURLOPT_DEBUGFUNCTION, my_trace);
  curl_easy_setopt(http_handle, CURLOPT_DEBUGDATA, &config);

  /* set options 2*/
  curl_easy_setopt(http_handle2, CURLOPT_URL, "http://www.amazon.com/");
  curl_easy_setopt(http_handle2, CURLOPT_NOPROGRESS, 0);
  curl_easy_setopt(http_handle2, CURLOPT_PROGRESSFUNCTION, progressCallback);
  curl_easy_setopt(http_handle2, CURLOPT_WRITEFUNCTION, writeCallback);
  curl_easy_setopt(http_handle2, CURLOPT_VERBOSE, 1);
  curl_easy_setopt(http_handle2, CURLOPT_DEBUGFUNCTION, my_trace);
  curl_easy_setopt(http_handle2, CURLOPT_DEBUGDATA, &config);

  /* set options 3*/
  curl_easy_setopt(http_handle3, CURLOPT_URL, "http://www.amazon.com/");
  curl_easy_setopt(http_handle3, CURLOPT_NOPROGRESS, 0);
  curl_easy_setopt(http_handle3, CURLOPT_PROGRESSFUNCTION, progressCallback);
  curl_easy_setopt(http_handle3, CURLOPT_WRITEFUNCTION, writeCallback);
  curl_easy_setopt(http_handle3, CURLOPT_VERBOSE, 1);
  curl_easy_setopt(http_handle3, CURLOPT_DEBUGFUNCTION, my_trace);
  curl_easy_setopt(http_handle3, CURLOPT_DEBUGDATA, &config);

  /* init a multi stack */
  multi_handle = curl_multi_init();

  //let first handle run to completion - this should leave connection intact
  abortTransfer = 0;
  runMulti(multi_handle, http_handle);

  //***ISSUE: cancel early - libcurl will send the headers out, stop but leave the
  //connection available for reuse after both runs
  abortTransfer = 1;
  runMulti(multi_handle, http_handle2);
  runMulti(multi_handle, http_handle3);

  //done with multi
  curl_multi_cleanup(multi_handle);

  return 0;
}

-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette: http://curl.haxx.se/mail/etiquette.html

Received on 2011-09-30