{"id":"69a7096c9d502d44","repo":"curl/curl","slug":"epoll-ctl-del-failed-for-fd-d-s","errorCode":null,"errorMessage":"EPOLL_CTL_DEL failed for fd: %d : %s\n","messagePattern":"EPOLL_CTL_DEL failed for fd: (.+?) : (.+?)\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"docs/examples/ephiperfifo.c","lineNumber":258,"sourceCode":"\n  mresult = curl_multi_socket_action(g->multi, fd, action, &g->still_running);\n  mcode_or_die(\"event_cb: curl_multi_socket_action\", mresult);\n\n  check_multi_info(g);\n  if(g->still_running <= 0) {\n    fprintf(MSG_OUT, \"last transfer done, kill timeout\\n\");\n    memset(&its, 0, sizeof(its));\n    timerfd_settime(g->tfd, 0, &its, NULL);\n  }\n}\n\n/* Clean up the SockInfo structure */\nstatic void remsock(struct SockInfo *f, struct GlobalInfo *g)\n{\n  if(f) {\n    if(f->sockfd) {\n      if(epoll_ctl(g->epfd, EPOLL_CTL_DEL, f->sockfd, NULL))\n        fprintf(stderr, \"EPOLL_CTL_DEL failed for fd: %d : %s\\n\",\n                f->sockfd, strerror(errno));\n    }\n    free(f);\n  }\n}\n\n/* Assign information to a SockInfo structure */\nstatic void setsock(struct SockInfo *f, curl_socket_t s, CURL *e, int act,\n                    struct GlobalInfo *g)\n{\n  struct epoll_event ev;\n  int kind = ((act & CURL_POLL_IN) ? EPOLLIN : 0) |\n             ((act & CURL_POLL_OUT) ? EPOLLOUT : 0);\n\n  if(f->sockfd) {\n    if(epoll_ctl(g->epfd, EPOLL_CTL_DEL, f->sockfd, NULL))\n      fprintf(stderr, \"EPOLL_CTL_DEL failed for fd: %d : %s\\n\",\n              f->sockfd, strerror(errno));","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/curl/curl/blob/527573490eb2564b3d7c9dd51d8bff963b5d6303/docs/examples/ephiperfifo.c#L240-L276","documentation":"Printed in remsock (ephiperfifo.c:253-263) when epoll_ctl(EPOLL_CTL_DEL) fails while tearing down a socket tracked by libcurl's multi interface. EPOLL_CTL_DEL can fail when the fd was already removed, already closed, or never added to this epoll instance. Because remsock is invoked from sock_cb with CURL_POLL_REMOVE, a stale SockInfo whose sockfd no longer matches the epoll set triggers this. The '%d' is f->sockfd and '%s' is strerror(errno) (typically ENOENT or EBADF).","triggerScenarios":"epoll_ctl(epfd, EPOLL_CTL_DEL, f->sockfd, NULL) failing in remsock because the fd was closed before removal, was never added, or was already deleted by a prior setsock/EPOLL_CTL_MOD path; common errno values are ENOENT (not registered) and EBADF (closed).","commonSituations":"Closing the socket (curl_easy_cleanup) before sock_cb(CURL_POLL_REMOVE) runs; double-remove when setsock already swapped the fd; transferring a connection between easy handles so the fd ownership moved; kernel behavior where closing an fd auto-removes it from epoll, making the explicit DEL redundant.","solutions":["Treat ENOENT and EBADF as benign in this cleanup path (do not log them at error severity).","Set f->sockfd = 0 after a successful DEL to prevent double-removal in remsock.","Ensure sockets are removed from epoll before curl_easy_cleanup closes them.","If fd is shared across handles, reference-count the SockInfo rather than freeing on first REMOVE."],"exampleFix":"// before\nstatic void remsock(struct SockInfo *f, struct GlobalInfo *g)\n{\n  if(f) {\n    if(f->sockfd) {\n      if(epoll_ctl(g->epfd, EPOLL_CTL_DEL, f->sockfd, NULL))\n        fprintf(stderr, \"EPOLL_CTL_DEL failed for fd: %d : %s\\n\",\n                f->sockfd, strerror(errno));\n    }\n    free(f);\n  }\n}\n\n// after\nstatic void remsock(struct SockInfo *f, struct GlobalInfo *g)\n{\n  if(f) {\n    if(f->sockfd && epoll_ctl(g->epfd, EPOLL_CTL_DEL, f->sockfd, NULL)) {\n      /* ENOENT/EBADF are expected when the fd was already removed or closed */\n      if(errno != ENOENT && errno != EBADF)\n        fprintf(stderr, \"EPOLL_CTL_DEL failed for fd: %d : %s\\n\",\n                f->sockfd, strerror(errno));\n    }\n    f->sockfd = CURL_SOCKET_BAD;\n    free(f);\n  }\n}","handlingStrategy":"try-catch","validationCode":"/* Only attempt EPOLL_CTL_DEL on an fd that is actually registered. */\nstatic int fd_is_registered(struct GlobalInfo *g, int fd) {\n  if(fd <= 0) return 0;\n  struct epoll_event ev;   /* dummy, ignored for DEL */\n  /* EPOLL_CTL_MOD with no-op events is a cheap liveness probe */\n  return epoll_ctl(g->epfd, EPOLL_CTL_MOD, fd, &ev) == 0 || errno != ENOENT;\n}","typeGuard":null,"tryCatchPattern":"/* remsock: tolerate EPOLL_CTL_DEL failure (fd may already be gone) and\n   still free the SockInfo so no memory leaks. */\nif(f && f->sockfd) {\n  if(epoll_ctl(g->epfd, EPOLL_CTL_DEL, f->sockfd, NULL) && errno != ENOENT)\n    fprintf(stderr, \"EPOLL_CTL_DEL fd %d: %s\\n\", f->sockfd, strerror(errno));\n  f->sockfd = -1;                            /* mark removed */\n}\nfree(f);","preventionTips":["Treat EBADF/ENOENT from EPOLL_CTL_DEL as benign - the fd was already closed or deregistered.","Set the fd field to -1 immediately after a successful DEL to make a double-remove detectable.","Always free the SockInfo in remsock regardless of the epoll_ctl outcome, so the error path does not leak memory.","Centralise epoll_ctl calls behind one helper that logs + classifies errno, so DEL failures are reported consistently."],"tags":["curl","epoll","epoll-ctl","multi-socket","linux","c"],"analyzedSha":"527573490eb2564b3d7c9dd51d8bff963b5d6303","analyzedAt":"2026-08-01T21:00:26.351Z","schemaVersion":2}