curl/curl ยท warning
EPOLL_CTL_DEL failed for fd: %d : %s
Error message
EPOLL_CTL_DEL failed for fd: %d : %s
What it means
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).
Source
Thrown at docs/examples/ephiperfifo.c:258
mresult = curl_multi_socket_action(g->multi, fd, action, &g->still_running);
mcode_or_die("event_cb: curl_multi_socket_action", mresult);
check_multi_info(g);
if(g->still_running <= 0) {
fprintf(MSG_OUT, "last transfer done, kill timeout\n");
memset(&its, 0, sizeof(its));
timerfd_settime(g->tfd, 0, &its, NULL);
}
}
/* Clean up the SockInfo structure */
static void remsock(struct SockInfo *f, struct GlobalInfo *g)
{
if(f) {
if(f->sockfd) {
if(epoll_ctl(g->epfd, EPOLL_CTL_DEL, f->sockfd, NULL))
fprintf(stderr, "EPOLL_CTL_DEL failed for fd: %d : %s\n",
f->sockfd, strerror(errno));
}
free(f);
}
}
/* Assign information to a SockInfo structure */
static void setsock(struct SockInfo *f, curl_socket_t s, CURL *e, int act,
struct GlobalInfo *g)
{
struct epoll_event ev;
int kind = ((act & CURL_POLL_IN) ? EPOLLIN : 0) |
((act & CURL_POLL_OUT) ? EPOLLOUT : 0);
if(f->sockfd) {
if(epoll_ctl(g->epfd, EPOLL_CTL_DEL, f->sockfd, NULL))
fprintf(stderr, "EPOLL_CTL_DEL failed for fd: %d : %s\n",
f->sockfd, strerror(errno));View on GitHub (pinned to 527573490e)
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.
Example fix
// before
static void remsock(struct SockInfo *f, struct GlobalInfo *g)
{
if(f) {
if(f->sockfd) {
if(epoll_ctl(g->epfd, EPOLL_CTL_DEL, f->sockfd, NULL))
fprintf(stderr, "EPOLL_CTL_DEL failed for fd: %d : %s\n",
f->sockfd, strerror(errno));
}
free(f);
}
}
// after
static void remsock(struct SockInfo *f, struct GlobalInfo *g)
{
if(f) {
if(f->sockfd && epoll_ctl(g->epfd, EPOLL_CTL_DEL, f->sockfd, NULL)) {
/* ENOENT/EBADF are expected when the fd was already removed or closed */
if(errno != ENOENT && errno != EBADF)
fprintf(stderr, "EPOLL_CTL_DEL failed for fd: %d : %s\n",
f->sockfd, strerror(errno));
}
f->sockfd = CURL_SOCKET_BAD;
free(f);
}
} Defensive patterns
Strategy: try-catch
Validate before calling
/* Only attempt EPOLL_CTL_DEL on an fd that is actually registered. */
static int fd_is_registered(struct GlobalInfo *g, int fd) {
if(fd <= 0) return 0;
struct epoll_event ev; /* dummy, ignored for DEL */
/* EPOLL_CTL_MOD with no-op events is a cheap liveness probe */
return epoll_ctl(g->epfd, EPOLL_CTL_MOD, fd, &ev) == 0 || errno != ENOENT;
} Try / catch
/* remsock: tolerate EPOLL_CTL_DEL failure (fd may already be gone) and
still free the SockInfo so no memory leaks. */
if(f && f->sockfd) {
if(epoll_ctl(g->epfd, EPOLL_CTL_DEL, f->sockfd, NULL) && errno != ENOENT)
fprintf(stderr, "EPOLL_CTL_DEL fd %d: %s\n", f->sockfd, strerror(errno));
f->sockfd = -1; /* mark removed */
}
free(f); Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- EPOLL_CTL_ADD failed for fd: %d : %s
- read(tfd) == %ld
- curl curl_easy_setopt failed: %s
- curl init failed!
- curl_easy_perform() failed: %s
AI-assisted analysis of curl/curl@527573490e (2026-08-01).
Data as JSON: /data/errors/69a7096c9d502d44.json.
Report an issue: GitHub.