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; }
