cURL / Mailing Lists / curl-library / Single Mail

curl-library

Problem re-using easy handle after call to curl_multi_remove_handle

From: Zachary Kulis <zkulis_at_gmail.com>
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