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

  1. Treat ENOENT and EBADF as benign in this cleanup path (do not log them at error severity).
  2. Set f->sockfd = 0 after a successful DEL to prevent double-removal in remsock.
  3. Ensure sockets are removed from epoll before curl_easy_cleanup closes them.
  4. 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

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


AI-assisted analysis of curl/curl@527573490e (2026-08-01). Data as JSON: /data/errors/69a7096c9d502d44.json. Report an issue: GitHub.