mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-08-10 00:57:02 +02:00
MINOR: sock: implement sock_accept_conn() to accept a connection
The socket-specific accept() code in listener_accept() has nothing to do there. Let's move it to sock.c where it can be significantly cleaned up. It will now directly return an accepted connection and provide a status code instead of letting listener_accept() deal with various errno values. Note that this doesn't support the sockpair specific code. The function is now responsible for dealing with its own receiver's polling state and calling fd_cant_recv() when facing EAGAIN. One tiny change from the previous implementation is that the connection's sockaddr is now allocated before trying accept(), which saves a memcpy() of the resulting address for each accept at the expense of a cheap pool_alloc/pool_free on the final accept returning EAGAIN. This still apparently slightly improves accept performance in microbencharks.
This commit is contained in:
parent
1e509a7231
commit
f1dc9f2f17
@ -41,6 +41,7 @@ int sock_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir);
|
|||||||
int sock_get_old_sockets(const char *unixsocket);
|
int sock_get_old_sockets(const char *unixsocket);
|
||||||
int sock_find_compatible_fd(const struct receiver *rx);
|
int sock_find_compatible_fd(const struct receiver *rx);
|
||||||
int sock_accepting_conn(const struct receiver *rx);
|
int sock_accepting_conn(const struct receiver *rx);
|
||||||
|
struct connection *sock_accept_conn(struct listener *l, int *status);
|
||||||
|
|
||||||
#endif /* _HAPROXY_SOCK_H */
|
#endif /* _HAPROXY_SOCK_H */
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@ static struct protocol proto_tcpv4 = {
|
|||||||
.unbind = default_unbind_listener,
|
.unbind = default_unbind_listener,
|
||||||
.suspend = default_suspend_listener,
|
.suspend = default_suspend_listener,
|
||||||
.resume = default_resume_listener,
|
.resume = default_resume_listener,
|
||||||
|
.accept_conn = sock_accept_conn,
|
||||||
.rx_enable = sock_enable,
|
.rx_enable = sock_enable,
|
||||||
.rx_disable = sock_disable,
|
.rx_disable = sock_disable,
|
||||||
.rx_unbind = sock_unbind,
|
.rx_unbind = sock_unbind,
|
||||||
@ -96,6 +97,7 @@ static struct protocol proto_tcpv6 = {
|
|||||||
.unbind = default_unbind_listener,
|
.unbind = default_unbind_listener,
|
||||||
.suspend = default_suspend_listener,
|
.suspend = default_suspend_listener,
|
||||||
.resume = default_resume_listener,
|
.resume = default_resume_listener,
|
||||||
|
.accept_conn = sock_accept_conn,
|
||||||
.rx_enable = sock_enable,
|
.rx_enable = sock_enable,
|
||||||
.rx_disable = sock_disable,
|
.rx_disable = sock_disable,
|
||||||
.rx_unbind = sock_unbind,
|
.rx_unbind = sock_unbind,
|
||||||
|
@ -61,6 +61,7 @@ static struct protocol proto_unix = {
|
|||||||
.disable = uxst_disable_listener,
|
.disable = uxst_disable_listener,
|
||||||
.unbind = default_unbind_listener,
|
.unbind = default_unbind_listener,
|
||||||
.suspend = default_suspend_listener,
|
.suspend = default_suspend_listener,
|
||||||
|
.accept_conn = sock_accept_conn,
|
||||||
.rx_enable = sock_enable,
|
.rx_enable = sock_enable,
|
||||||
.rx_disable = sock_disable,
|
.rx_disable = sock_disable,
|
||||||
.rx_unbind = sock_unbind,
|
.rx_unbind = sock_unbind,
|
||||||
|
131
src/sock.c
131
src/sock.c
@ -10,6 +10,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@ -27,6 +28,7 @@
|
|||||||
#include <haproxy/api.h>
|
#include <haproxy/api.h>
|
||||||
#include <haproxy/connection.h>
|
#include <haproxy/connection.h>
|
||||||
#include <haproxy/listener-t.h>
|
#include <haproxy/listener-t.h>
|
||||||
|
#include <haproxy/log.h>
|
||||||
#include <haproxy/namespace.h>
|
#include <haproxy/namespace.h>
|
||||||
#include <haproxy/sock.h>
|
#include <haproxy/sock.h>
|
||||||
#include <haproxy/sock_inet.h>
|
#include <haproxy/sock_inet.h>
|
||||||
@ -35,6 +37,135 @@
|
|||||||
/* the list of remaining sockets transferred from an older process */
|
/* the list of remaining sockets transferred from an older process */
|
||||||
struct xfer_sock_list *xfer_sock_list = NULL;
|
struct xfer_sock_list *xfer_sock_list = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
/* Accept an incoming connection from listener <l>, and return it, as well as
|
||||||
|
* a CO_AC_* status code into <status> if not null. Null is returned on error.
|
||||||
|
* <l> must be a valid listener with a valid frontend.
|
||||||
|
*/
|
||||||
|
struct connection *sock_accept_conn(struct listener *l, int *status)
|
||||||
|
{
|
||||||
|
#ifdef USE_ACCEPT4
|
||||||
|
static int accept4_broken;
|
||||||
|
#endif
|
||||||
|
struct proxy *p = l->bind_conf->frontend;
|
||||||
|
struct connection *conn;
|
||||||
|
socklen_t laddr;
|
||||||
|
int ret;
|
||||||
|
int cfd;
|
||||||
|
|
||||||
|
conn = conn_new(&l->obj_type);
|
||||||
|
if (!conn)
|
||||||
|
goto fail_conn;
|
||||||
|
|
||||||
|
if (!sockaddr_alloc(&conn->src, NULL, 0))
|
||||||
|
goto fail_addr;
|
||||||
|
|
||||||
|
/* accept() will mark all accepted FDs O_NONBLOCK and the ones accepted
|
||||||
|
* in the master process as FD_CLOEXEC. It's not done for workers
|
||||||
|
* because 1) workers are not supposed to execute anything so there's
|
||||||
|
* no reason for uselessly slowing down everything, and 2) that would
|
||||||
|
* prevent us from implementing fd passing in the future.
|
||||||
|
*/
|
||||||
|
#ifdef USE_ACCEPT4
|
||||||
|
laddr = sizeof(*conn->src);
|
||||||
|
|
||||||
|
/* only call accept4() if it's known to be safe, otherwise fallback to
|
||||||
|
* the legacy accept() + fcntl().
|
||||||
|
*/
|
||||||
|
if (unlikely(accept4_broken) ||
|
||||||
|
(((cfd = accept4(l->rx.fd, (struct sockaddr *)conn->src, &laddr,
|
||||||
|
SOCK_NONBLOCK | (master ? SOCK_CLOEXEC : 0))) == -1) &&
|
||||||
|
(errno == ENOSYS || errno == EINVAL || errno == EBADF) &&
|
||||||
|
(accept4_broken = 1)))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
laddr = sizeof(*conn->src);
|
||||||
|
if ((cfd = accept(l->rx.fd, (struct sockaddr *)conn->src, &laddr)) != -1) {
|
||||||
|
fcntl(cfd, F_SETFL, O_NONBLOCK);
|
||||||
|
if (master)
|
||||||
|
fcntl(cfd, F_SETFD, FD_CLOEXEC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (likely(cfd != -1)) {
|
||||||
|
/* Perfect, the connection was accepted */
|
||||||
|
conn->handle.fd = cfd;
|
||||||
|
conn->flags |= CO_FL_ADDR_FROM_SET;
|
||||||
|
ret = CO_AC_DONE;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* error conditions below */
|
||||||
|
conn_free(conn);
|
||||||
|
conn = NULL;
|
||||||
|
|
||||||
|
switch (errno) {
|
||||||
|
case EAGAIN:
|
||||||
|
ret = CO_AC_DONE; /* nothing more to accept */
|
||||||
|
if (fdtab[l->rx.fd].ev & (FD_POLL_HUP|FD_POLL_ERR)) {
|
||||||
|
/* the listening socket might have been disabled in a shared
|
||||||
|
* process and we're a collateral victim. We'll just pause for
|
||||||
|
* a while in case it comes back. In the mean time, we need to
|
||||||
|
* clear this sticky flag.
|
||||||
|
*/
|
||||||
|
_HA_ATOMIC_AND(&fdtab[l->rx.fd].ev, ~(FD_POLL_HUP|FD_POLL_ERR));
|
||||||
|
ret = CO_AC_PAUSE;
|
||||||
|
}
|
||||||
|
fd_cant_recv(l->rx.fd);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EINVAL:
|
||||||
|
/* might be trying to accept on a shut fd (eg: soft stop) */
|
||||||
|
ret = CO_AC_PAUSE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EINTR:
|
||||||
|
case ECONNABORTED:
|
||||||
|
ret = CO_AC_RETRY;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ENFILE:
|
||||||
|
if (p)
|
||||||
|
send_log(p, LOG_EMERG,
|
||||||
|
"Proxy %s reached system FD limit (maxsock=%d). Please check system tunables.\n",
|
||||||
|
p->id, global.maxsock);
|
||||||
|
ret = CO_AC_PAUSE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EMFILE:
|
||||||
|
if (p)
|
||||||
|
send_log(p, LOG_EMERG,
|
||||||
|
"Proxy %s reached process FD limit (maxsock=%d). Please check 'ulimit-n' and restart.\n",
|
||||||
|
p->id, global.maxsock);
|
||||||
|
ret = CO_AC_PAUSE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ENOBUFS:
|
||||||
|
case ENOMEM:
|
||||||
|
if (p)
|
||||||
|
send_log(p, LOG_EMERG,
|
||||||
|
"Proxy %s reached system memory limit (maxsock=%d). Please check system tunables.\n",
|
||||||
|
p->id, global.maxsock);
|
||||||
|
ret = CO_AC_PAUSE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
/* unexpected result, let's give up and let other tasks run */
|
||||||
|
ret = CO_AC_YIELD;
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
if (status)
|
||||||
|
*status = ret;
|
||||||
|
return conn;
|
||||||
|
|
||||||
|
fail_addr:
|
||||||
|
conn_free(conn);
|
||||||
|
conn = NULL;
|
||||||
|
fail_conn:
|
||||||
|
ret = CO_AC_PAUSE;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
/* Create a socket to connect to the server in conn->dst (which MUST be valid),
|
/* Create a socket to connect to the server in conn->dst (which MUST be valid),
|
||||||
* using the configured namespace if needed, or the one passed by the proxy
|
* using the configured namespace if needed, or the one passed by the proxy
|
||||||
* protocol if required to do so. It ultimately calls socket() or socketat()
|
* protocol if required to do so. It ultimately calls socket() or socketat()
|
||||||
|
Loading…
Reference in New Issue
Block a user