From 818dca50984b758b7ec6ebe8874aeb92594ef446 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Fri, 31 Jan 2014 19:40:19 +0100 Subject: [PATCH] BUG/MEDIUM: listener: improve detection of non-working accept4() On ARM, glibc does not implement accept4() and simply returns ENOSYS which was not caught as a reason to fall back to accept(), resulting in a spinning process since poll() would call again. Let's change the error detection mechanism to save the broken status of the syscall into a local variable that is used to fall back to the legacy accept(). In addition to this, since the code was becoming a bit messy, the accept4() was removed, so now the fallback code and the legacy code are the same. This will also increase bug report accuracy if needed. This is 1.5-specific, no backport is needed. --- src/listener.c | 19 ++++++++++++------- src/session.c | 8 -------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/listener.c b/src/listener.c index 1ce35de8c..05bb7435a 100644 --- a/src/listener.c +++ b/src/listener.c @@ -257,6 +257,9 @@ void listener_accept(int fd) int max_accept = l->maxaccept ? l->maxaccept : 1; int cfd; int ret; +#ifdef USE_ACCEPT4 + static int accept4_broken; +#endif if (unlikely(l->nbconn >= l->maxconn)) { listener_full(l); @@ -346,15 +349,17 @@ void listener_accept(int fd) } #ifdef USE_ACCEPT4 - cfd = accept4(fd, (struct sockaddr *)&addr, &laddr, SOCK_NONBLOCK); - if (unlikely(cfd == -1 && errno == EINVAL)) { - /* unsupported syscall, fallback to normal accept()+fcntl() */ + /* only call accept4() if it's known to be safe, otherwise + * fallback to the legacy accept() + fcntl(). + */ + if (unlikely(accept4_broken || + ((cfd = accept4(fd, (struct sockaddr *)&addr, &laddr, SOCK_NONBLOCK)) == -1 && + (errno == ENOSYS || errno == EINVAL || errno == EBADF) && + (accept4_broken = 1)))) +#endif if ((cfd = accept(fd, (struct sockaddr *)&addr, &laddr)) != -1) fcntl(cfd, F_SETFL, O_NONBLOCK); - } -#else - cfd = accept(fd, (struct sockaddr *)&addr, &laddr); -#endif + if (unlikely(cfd == -1)) { switch (errno) { case EAGAIN: diff --git a/src/session.c b/src/session.c index 1faf0c347..aae0c69f8 100644 --- a/src/session.c +++ b/src/session.c @@ -148,14 +148,6 @@ int session_accept(struct listener *l, int cfd, struct sockaddr_storage *addr) goto out_free_session; } -#ifndef USE_ACCEPT4 - /* Adjust some socket options if the connection was accepted by a plain - * accept() syscall. - */ - if (unlikely(fcntl(cfd, F_SETFL, O_NONBLOCK) == -1)) - goto out_free_session; -#endif - /* monitor-net and health mode are processed immediately after TCP * connection rules. This way it's possible to block them, but they * never use the lower data layers, they send directly over the socket,