curl-library
Problem re-using easy handle after call to curl_multi_remove_handle
Date: Thu, 23 Jul 2009 10:18:44 -0400
This message is a follow-up to the post "problem with multi interface
and ftp" dated Jun 6, 2009. I couldn't figure out how to continue the
thread from the mail archive, so I'm adding this new post.
I have encountered similar difficulties re-using easy handles after a
call to curl_multi_remove_handle(). I am using FTP and the problem
occurs (segmentation fault) immediately after the "delayed kill" message
is displayed (in verbose mode). I am attaching sample code that
demonstrates the problem. The sample code is based loosely on the
"hiperfifo.c" example, and uses libevent for the event handling
infrastructure. I am using libcurl version 7.19.5 and libevent version
1.4.10-stable on Mac OS X 10.5 (and also on iPhoneOS 2.2.1).
The problem occurs when removing an easy handle from a multi after a
download has completed (i.e. curl_multi_info_read() returns
CURLMSG_DONE). If the easy handle is removed immediately, then the next
time it is re-used, a segmentation violation occurs. I have discovered a
possible work-around by delaying the removal of the easy handle until
*right before* it is re-used again (via a call to
curl_multi_add_handle()). The sample code attached highlights both the
bug and the work-around. Use the #define flag (SHOW_BUG) to control
whether the seg fault occurs or not.
My question is: do you see any potential problems with my work-around? I
have written a bunch of test cases for my application, and I haven't
seen any more seg faults using the fix described above. I appreciate
your hard work on this library.
Zach
#include <iostream>
#include <curl/curl.h>
#include <sys/time.h>
#include <event.h>
// Use this #define to control whether the buggy code is run or not.
// Just comment it out to run the code without the bug...
#define SHOW_BUG
CURLM* multiHandle;
CURL* easyHandles[2];
struct event timerEvent;
bool downloadCompleted = false;
const char* kURL = "ftp://ftp-test.mozilla.org/README";
typedef struct SocketContext {
SocketContext() : eventSet(false) {}
struct event event;
bool eventSet;
} SocketContext;
void initEasyHandles();
void initMulti();
void doDownload(CURL* easy);
void handleCompletedDownloads();
/* Callback to handle socket information requests [libcurl] */
int curlSocketCallback(CURL*, curl_socket_t, int, void*, void*);
/* Callback to set a new timeout value [libcurl] */
int curlGetTimeoutCallback(CURLM*, long, void*);
/* File writer callback function */
int curlWriterCallback(void*, size_t, size_t, void *);
/* Callback invoked when socket activity is detected [libevent] */
void libeventSocketCallback(int, short, void*);
/* Callback invoked on a timeout [libevent] */
void timeoutCallback(int, short, void*);
int main (int argc, char * const argv[]) {
// Initialize libcurl and libevent.
curl_global_init(CURL_GLOBAL_ALL);
event_init();
// Create the timer and worker notify events.
evtimer_set(&timerEvent, &timeoutCallback, NULL);
event_add(&timerEvent, NULL);
initEasyHandles();
initMulti();
// Step 1: Start a download on the first easy handle.
doDownload(easyHandles[0]);
// Step 2: Start a download on the second easy handle.
doDownload(easyHandles[1]);
// Step 3: Start a download on the first easy handle.
doDownload(easyHandles[0]);
}
void doDownload(CURL* easy) {
downloadCompleted = false;
struct timeval timeout;
curl_easy_setopt(easy, CURLOPT_URL, kURL);
#ifndef SHOW_BUG
curl_multi_remove_handle(multiHandle, easy);
#endif
curl_multi_add_handle(multiHandle, easy);
do {
timeout.tv_sec = 0;
timeout.tv_usec = 250000;
event_loopexit(&timeout);
event_loop(0);
} while (!downloadCompleted);
}
void initEasyHandles() {
for (int i = 0; i < 2; ++i) {
CURL* easy;
easy = easyHandles[i] = curl_easy_init();
curl_easy_setopt(easy, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(easy, CURLOPT_VERBOSE, 1);
curl_easy_setopt(easy, CURLOPT_HEADER, 1);
curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, &curlWriterCallback);
}
}
void initMulti() {
multiHandle = curl_multi_init();
int maxPersistentConnections = 2;
curl_multi_setopt(multiHandle, CURLMOPT_MAXCONNECTS, &maxPersistentConnections);
curl_multi_setopt(multiHandle, CURLMOPT_SOCKETFUNCTION, &curlSocketCallback);
curl_multi_setopt(multiHandle, CURLMOPT_TIMERFUNCTION, &curlGetTimeoutCallback);
curl_multi_setopt(multiHandle, CURLMOPT_SOCKETDATA, NULL);
curl_multi_setopt(multiHandle, CURLMOPT_TIMERDATA, NULL);
}
// Copied from hiperfifo.c
void handleCompletedDownloads() {
CURLMsg *msg;
int msgs_left;
CURL*easy;
CURLcode res;
char *eff_url=NULL;
/*
I am still uncertain whether it is safe to remove an easy handle
from inside the curl_multi_info_read loop, so here I will search
for completed transfers in the inner "while" loop, and then remove
them in the outer "do-while" loop...
*/
do {
easy=NULL;
while ((msg = curl_multi_info_read(multiHandle, &msgs_left))) {
if (msg->msg == CURLMSG_DONE) {
easy=msg->easy_handle;
res=msg->data.result;
break;
}
}
if (easy) {
curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
printf("DONE: %s => (%d)\n", eff_url, res);
#ifdef SHOW_BUG
curl_multi_remove_handle(multiHandle, easy);
#endif
downloadCompleted = true;
// Don't clean up, because we're going to reuse the easy handle!
//curl_easy_cleanup(easy);
}
} while ( easy );
}
// The libevent timeout callback...
void timeoutCallback(int fd, short eventType, void* userData) {
// Invoked when the timer scheduled with libevent times out.
// We just need to inform the libcurl multihandle that the timeout occurred.
(void) fd;
(void) eventType;
(void) userData;
CURLMcode rc;
int numRunning;
do {
rc = curl_multi_socket_action(multiHandle, CURL_SOCKET_TIMEOUT, 0, &numRunning);
} while (rc == CURLM_CALL_MULTI_PERFORM);
//handleCompletedDownloads();
}
// The libevent socket callback
void libeventSocketCallback(int fd, short eventType, void* userData) {
// Invoked when socket activity is detected.
(void) fd;
(void) userData;
CURLMcode rc;
int numRunning;
// Set the socket activity flags.
int action = (eventType&EV_READ?CURL_CSELECT_IN:0)|(eventType&EV_WRITE?CURL_CSELECT_OUT:0);
do {
rc = curl_multi_socket_action(multiHandle, fd, action, &numRunning);
} while (rc == CURLM_CALL_MULTI_PERFORM);
handleCompletedDownloads();
}
// The libcurl writer callback
int curlWriterCallback(void* buf, size_t size, size_t nmemb, void* userData) {
(void) buf;
(void) userData;
return size*nmemb;
}
// The libcurl get timeout callback
int curlGetTimeoutCallback(CURLM* handle, long timeoutInMillis, void* userData) {
(void) handle;
(void) userData;
struct timeval timeout;
timeout.tv_sec = timeoutInMillis/1000;
timeout.tv_usec = (timeoutInMillis-timeout.tv_sec*1000)*1000;
evtimer_add(&timerEvent, &timeout); /* replaces old timeout value with new one */
return 0;
}
// The libcurl socket action callback
int curlSocketCallback(CURL* easyHandle, curl_socket_t socket, int action,
void* userData, void* socketData) {
(void)userData;
SocketContext* sd = (SocketContext*)socketData;
switch (action) {
case CURL_POLL_NONE:
break;
case CURL_POLL_IN:
case CURL_POLL_OUT:
case CURL_POLL_INOUT:
if (!sd) {
sd = new SocketContext();
curl_multi_assign(multiHandle, socket, sd);
printf ("Registering new socket: %d\n", socket);
} else if (sd->eventSet) {
event_del(&sd->event);
}
short flags = (action&CURL_POLL_IN?EV_READ:0)|(action&CURL_POLL_OUT?EV_WRITE:0)|EV_PERSIST;
event_set(&sd->event, socket, flags, &libeventSocketCallback, NULL);
event_add(&sd->event, NULL);
sd->eventSet = 1;
break;
case CURL_POLL_REMOVE:
// Unregister socket.
if (sd) {
if (sd->eventSet) {
event_del(&sd->event);
}
delete sd;
}
curl_multi_assign(multiHandle, socket, NULL);
printf ("Unregistering socket: %d\n", socket);
break;
default:
break;
}
return 0;
}
Received on 2009-07-23