curl-library
RE: Connection being reused following a cancellation (HTTP)
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
- text/plain attachment: multi-cancelProblem.c