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.
This commit is contained in:
Willy Tarreau 2014-01-31 19:40:19 +01:00
parent 416ce618be
commit 818dca5098
2 changed files with 12 additions and 15 deletions

View File

@ -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:

View File

@ -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,