I've noticed that SOCKETFUNCTION is called to POLL_REMOVE the socket
from watched set after the socket was already closed by curl
internally. This can cause problems with applications based on
epoll(). In case application calls fork and closes the filedescriptor
before it is removed from epoll set then epoll_wait will still report
events for that desciptor, even it was already closed in this process.
See question 6 in Questions and answers section of man 7 epoll for
better explanation.

I've attached example which shows epoll_ctl(DEL): Bad file descriptor
error in SOCKETFUNCTION. The internal close can be also checked by
strace.

I think I've identified the code path. It all starts in
Curl_disconnect. At first Curl_conncache_remove_conn unlinks easy
handle from connectdata structure then conn_free is called. conn_free
calls Curl_closesocket which calls Curl_multi_closed. However
Curl_multi_closed expects there is still easy handle associated so it
does nothing (not calling SOCKETFUNCTION). Control is returned to
Curl_multi_closed where socket is closed. Later the SOCKETFUNCTION is
called probably from singlesocket function.

Maybe it helps. I don't feel confident enough to touch the code myself.
/*
 * modified example from: https://sourceforge.net/p/curl/bugs/1248/
 */

#include <assert.h>
#include <curl/curl.h>
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <unistd.h>

void error(const char* string)
{
  perror(string);
  exit(1);
}

int epollFd;
int timeout = -1;

int socketCallback(CURL* easy, curl_socket_t fd, int action, void* u, void* s)
{
  printf("socketCallback\n");

  struct epoll_event event;
  event.events = 0;
  event.data.fd = fd;

  if (action == CURL_POLL_REMOVE) {
    printf(">>> %s: removing fd=%d\n", __func__, fd);

    int res = epoll_ctl(epollFd, EPOLL_CTL_DEL, fd, &event);
    if (res == -1)
      error("epoll_ctl(DEL)");
    return 0;
  }

  if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) {
    event.events |= EPOLLIN;
  }
  if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) {
    event.events |= EPOLLOUT;
  }

  printf(">>> %s: adding fd=%d action=%d\n", __func__, fd, action);
  if (event.events != 0) {
    int res = epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &event);
    if (res == -1)
      error("epoll_ctl(ADD)");
  }

  return 0;
}

int timerCallback(CURLM* multi, long timeout_ms, void* u)
{
  printf(">>> %s: timeout: %ld ms\n", __func__, timeout_ms);
  timeout = timeout_ms;
  return 0;
}

int main(int argc, char** argv)
{
  epollFd = epoll_create(1);
  if (epollFd == -1)
    error("epoll_create");

  CURL* easy = curl_easy_init();
  curl_easy_setopt(easy, CURLOPT_URL, "example.com");
  curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1);

  CURLM* multi = curl_multi_init();
  curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, socketCallback);
  curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, timerCallback);

  curl_multi_add_handle(multi, easy);

  int running_handles = 1;
  while (running_handles > 0) {
    printf(">>> calling epoll_wait\n");
    struct epoll_event event;
    int res = epoll_wait(epollFd, &event, 1, timeout);
    if (res == -1) {
      perror("epoll_wait");
    }
    else if (res == 0) {
      curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0,
                               &running_handles);
    }
    else {
      curl_multi_socket_action(multi, event.data.fd, 0,
                               &running_handles);
    }
  }

  close(epollFd);

  printf(">>> bye bye\n");
  return 0;
}
-------------------------------------------------------------------
Unsubscribe: https://cool.haxx.se/list/listinfo/curl-library
Etiquette:   https://curl.haxx.se/mail/etiquette.html

Reply via email to