[MEDIUM] listeners: put listeners in queue upon resource shortage
When an accept() fails because of a connection limit or a memory shortage,
we now disable it and queue it so that it's dequeued only when a connection
is released. This has improved the behaviour of the process near the fd limit
as now a listener with a no connection (eg: stats) will not loop forever
trying to get its connection accepted.
The solution is still not 100% perfect, as we'd like to have this used when
proxy limits are reached (use a per-proxy list) and for safety, we'd need
to have dedicated tasks to periodically re-enable them (eg: to overcome
temporary system-wide resource limitations when no connection is released).
diff --git a/src/haproxy.c b/src/haproxy.c
index 8da7aa9..c3841dd 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -159,6 +159,8 @@
char hostname[MAX_HOSTNAME_LEN];
char localpeer[MAX_HOSTNAME_LEN];
+/* list of the temporarily limited listeners because of lack of resource */
+struct list global_listener_queue = LIST_HEAD_INIT(global_listener_queue);
/*********************************************************************/
/* general purpose functions ***************************************/
diff --git a/src/session.c b/src/session.c
index 6e718c0..750cf40 100644
--- a/src/session.c
+++ b/src/session.c
@@ -2092,6 +2092,10 @@
if (s->listener->state == LI_FULL)
resume_listener(s->listener);
+ /* Dequeues all of the listeners waiting for a resource */
+ if (!LIST_ISEMPTY(&global_listener_queue))
+ dequeue_all_listeners(&global_listener_queue);
+
if (unlikely((global.mode & MODE_DEBUG) &&
(!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)))) {
int len;
diff --git a/src/stream_sock.c b/src/stream_sock.c
index 10e13cb..fa03a62 100644
--- a/src/stream_sock.c
+++ b/src/stream_sock.c
@@ -1192,6 +1192,7 @@
int max_accept = global.tune.maxaccept;
int cfd;
int ret;
+ int loops = 0;
if (unlikely(l->nbconn >= l->maxconn)) {
listener_full(l);
@@ -1208,6 +1209,7 @@
struct sockaddr_storage addr;
socklen_t laddr = sizeof(addr);
+ loops++;
cfd = accept(fd, (struct sockaddr *)&addr, &laddr);
if (unlikely(cfd == -1)) {
switch (errno) {
@@ -1220,16 +1222,14 @@
send_log(p, LOG_EMERG,
"Proxy %s reached system FD limit at %d. Please check system tunables.\n",
p->id, maxfd);
- if (l->nbconn)
- listener_full(l);
+ limit_listener(l, &global_listener_queue);
return 0;
case EMFILE:
if (p)
send_log(p, LOG_EMERG,
"Proxy %s reached process FD limit at %d. Please check 'ulimit-n' and restart.\n",
p->id, maxfd);
- if (l->nbconn)
- listener_full(l);
+ limit_listener(l, &global_listener_queue);
return 0;
case ENOBUFS:
case ENOMEM:
@@ -1237,8 +1237,7 @@
send_log(p, LOG_EMERG,
"Proxy %s reached system memory limit at %d sockets. Please check system tunables.\n",
p->id, maxfd);
- if (l->nbconn)
- listener_full(l);
+ limit_listener(l, &global_listener_queue);
return 0;
default:
return 0;
@@ -1250,6 +1249,7 @@
"Proxy %s reached the configured maximum connection limit. Please check the global 'maxconn' value.\n",
p->id);
close(cfd);
+ limit_listener(l, &global_listener_queue);
return 0;
}
@@ -1276,10 +1276,7 @@
if (ret == 0) /* successful termination */
continue;
- if (p) {
- disable_listener(l);
- p->state = PR_STIDLE;
- }
+ limit_listener(l, &global_listener_queue);
return 0;
}
@@ -1289,6 +1286,11 @@
}
} /* end of while (p->feconn < p->maxconn) */
+
+ /* if we did not even enter the loop, we've reached resource limits */
+ if (!loops && max_accept)
+ limit_listener(l, &global_listener_queue);
+
return 0;
}