Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I immediately cancel a curl operation?

I'm using libcurl in C++, and I'm calling curl_easy_perform in a separate thread from my UI using Boost.Thread.

The main UI has a cancel button that I'd like to be perfectly responsive (i.e., when a user clicks on it, it should immediately react). I have read, write, and progress callbacks set up to read an atomic should_cancel variable (as in this question), but there are two problems:

  1. There's often a very small (but noticeable) delay from when cancel is pressed to when the curl operation completes.

  2. Occasionally, there's a very long (sometimes interminable) delay. In this case, either:

    a. the progress, read, and write callbacks simply aren't called for a long time, or

    b. the progress callback is called, I return a nonzero value (meaning it should terminate), but the curl operation doesn't complete for a while longer (in fact, the progress function is called again in the meantime!)

So:

  1. Why do the long delays happen (especially without calling the progress function)?
  2. What should I do instead to allow the cancel button to react properly?

One possibility is to tell the UI that the cancel operation succeeded, but keep running the curl thread in the background until it cancels. The problem with this (I think) is that it forces the should_cancel variable to be global, instead of scoped to the dialog box where the operation began.

like image 835
Jesse Beder Avatar asked Dec 15 '10 23:12

Jesse Beder


3 Answers

I met the same problem with curl 7.21.6. When tring to abort smtp protocol. Returning CURL_READFUNC_ABORT from read callback stops the transfer but curl_easy_perform does not return for next 5+ minutes. Probably it waits for tcp timeout.

To walk it around i store socket used by curl (replace curl_opensocket_callback), and close this socket directly when needed.

curl_socket_t storeCurlSocket(SOCKET *data, curlsocktype purpose, struct curl_sockaddr *addr) {
    SOCKET sock = socket(addr->family, addr->socktype, addr->protocol);
    *data = sock;
    return sock;
}

size_t abort_payload(void *ptr, size_t size, size_t nmemb, SOCKET *curl_socket) {
    SOCKET l_socket = INVALID_SOCKET;
    swap(l_socket, *curl_socket);
    if (l_socket != INVALID_SOCKET) {
        shutdown(l_socket, SD_BOTH);
        closesocket(l_socket);
    }
    return CURL_READFUNC_ABORT;
}

...calling perform...
        SOCKET curlSocket;
        curet = curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, storeCurlSocket);
        curet = curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &curlSocket);
        curet = curl_easy_setopt(curl, CURLOPT_READFUNCTION, abort_payload);
        curet = curl_easy_setopt(curl, CURLOPT_READDATA, &curlSocket);
        curet = curl_easy_perform(curl);
        if (curet == CURLE_ABORTED_BY_CALLBACK) {
          //expected abort
        }
...
like image 176
Fantastory Avatar answered Sep 29 '22 09:09

Fantastory


Your basic idea is right. You should detach the curl operation from the UI. However, the implementation should be changed a bit. You shouldn't be using a global should_cancel. Instead, you should have a global current_request pointer, to an object of type Request. This type should have an internal cancel flag, and a public Cancel() function. In response to the cancel button, you call Cancel on the current_request and then null it. A cancelled request is then repsonsible for its own cleanup at a later time (it's a thread, after all).

You'll need to be careful with your mutexes to prevent zombie objects. There's an inherent race condition between a cancel and a request completion.

like image 42
MSalters Avatar answered Sep 29 '22 09:09

MSalters


The delay could happens because curl_easy_perform is busy with the write or read callback, try to return 0 from the write callback or CURL_READFUNC_ABORT from the read callback. According with the documentation, if the value returned by the write callback differs from the value received by the function the transfer will be aborted, and if the read callback return CURL_READFUNC_ABORT, the transfer will be aborted too (valid from version 7.12.1 of the library).

like image 24
Luis G. Costantini R. Avatar answered Sep 29 '22 07:09

Luis G. Costantini R.