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