1 commit c98cdf7cc755c579a8b9cceee4809089267615ce
2 Author: Willy Tarreau <w@1wt.eu>
3 Date: Wed Feb 27 19:32:32 2019 +0100
5 BUG/MEDIUM: listener: make sure the listener never accepts too many conns
7 We were not checking p->feconn nor the global actconn soon enough. In
8 older versions this could result in a frontend accepting more connections
9 than allowed by its maxconn or the global maxconn, exactly N-1 extra
10 connections where N is the number of threads, provided each of these
11 threads were running a different listener. But with the lock removal,
12 it became worse, the excess could be the listener's maxconn multiplied
13 by the number of threads. Among the nasty side effect was that LI_FULL
14 could be removed while the limit was still over and in some cases the
15 polling on the socket was no re-enabled.
17 This commit takes care of updating and checking p->feconn and the global
18 actconn *before* processing the connection, so that the listener can be
19 turned off before accepting the socket if needed. This requires to move
20 some of the bookkeeping operations form session to listen, which totally
21 makes sense in this context.
23 Now the limits are properly respected, even if a listener's maxconn is
24 over a frontend's. This only applies on top of the listener lock removal
25 series and doesn't have to be backported.
27 (cherry picked from commit 82c9789ac4f0cba9a74d16c1b730fc64e1f95a6e)
28 [wt: backported since it fixes the previous patch set]
29 Signed-off-by: Willy Tarreau <w@1wt.eu>
30 (cherry picked from commit f54a86a229e1ee4b256d5614c0a65924f447df09)
31 Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
33 diff --git a/src/listener.c b/src/listener.c
34 index dab07a5e..68c84fbe 100644
37 @@ -408,6 +408,8 @@ void listener_accept(int fd)
41 + int next_feconn = 0;
42 + int next_actconn = 0;
46 @@ -478,12 +480,15 @@ void listener_accept(int fd)
47 * worst case. If we fail due to system limits or temporary resource
48 * shortage, we try again 100ms later in the worst case.
50 - for (; max_accept-- > 0; next_conn = 0) {
51 + for (; max_accept-- > 0; next_conn = next_feconn = next_actconn = 0) {
52 struct sockaddr_storage addr;
53 socklen_t laddr = sizeof(addr);
56 - /* pre-increase the number of connections without going too far */
57 + /* pre-increase the number of connections without going too far.
58 + * We process the listener, then the proxy, then the process.
59 + * We know which ones to unroll based on the next_xxx value.
63 if (count >= l->maxconn) {
64 @@ -501,15 +506,42 @@ void listener_accept(int fd)
68 - if (unlikely(actconn >= global.maxconn) && !(l->options & LI_O_UNLIMITED)) {
69 - limit_listener(l, &global_listener_queue);
70 - task_schedule(global_listener_queue_task, tick_add(now_ms, 1000)); /* try again in 1 second */
75 + if (count >= p->maxconn) {
76 + /* the frontend was marked full or another
77 + * thread is going to do it.
82 + next_feconn = count + 1;
83 + } while (!HA_ATOMIC_CAS(&p->feconn, &count, next_feconn));
85 + if (unlikely(next_feconn == p->maxconn)) {
86 + /* we just filled it */
87 + limit_listener(l, &p->listener_queue);
91 - if (unlikely(p && p->feconn >= p->maxconn)) {
92 - limit_listener(l, &p->listener_queue);
94 + if (!(l->options & LI_O_UNLIMITED)) {
97 + if (count >= global.maxconn) {
98 + /* the process was marked full or another
99 + * thread is going to do it.
104 + next_actconn = count + 1;
105 + } while (!HA_ATOMIC_CAS(&actconn, &count, next_actconn));
107 + if (unlikely(next_actconn == global.maxconn)) {
108 + limit_listener(l, &global_listener_queue);
109 + task_schedule(global_listener_queue_task, tick_add(now_ms, 1000)); /* try again in 1 second */
114 @@ -544,6 +576,10 @@ void listener_accept(int fd)
117 HA_ATOMIC_SUB(&l->nbconn, 1);
119 + HA_ATOMIC_SUB(&p->feconn, 1);
120 + if (!(l->options & LI_O_UNLIMITED))
121 + HA_ATOMIC_SUB(&actconn, 1);
125 @@ -574,18 +610,20 @@ void listener_accept(int fd)
127 HA_ATOMIC_UPDATE_MAX(&l->counters->conn_max, next_conn);
130 + HA_ATOMIC_UPDATE_MAX(&p->fe_counters.conn_max, next_feconn);
132 + proxy_inc_fe_conn_ctr(l, p);
134 if (!(l->options & LI_O_UNLIMITED)) {
135 count = update_freq_ctr(&global.conn_per_sec, 1);
136 HA_ATOMIC_UPDATE_MAX(&global.cps_max, count);
137 - HA_ATOMIC_ADD(&actconn, 1);
140 if (unlikely(cfd >= global.maxsock)) {
141 send_log(p, LOG_EMERG,
142 "Proxy %s reached the configured maximum connection limit. Please check the global 'maxconn' value.\n",
144 - if (!(l->options & LI_O_UNLIMITED))
145 - HA_ATOMIC_SUB(&actconn, 1);
147 limit_listener(l, &global_listener_queue);
148 task_schedule(global_listener_queue_task, tick_add(now_ms, 1000)); /* try again in 1 second */
149 @@ -593,11 +631,13 @@ void listener_accept(int fd)
152 /* past this point, l->accept() will automatically decrement
153 - * l->nbconn and actconn once done. Setting next_conn=0 allows
154 - * the error path not to rollback on nbconn. It's more convenient
155 - * than duplicating all exit labels.
156 + * l->nbconn, feconn and actconn once done. Setting next_*conn=0
157 + * allows the error path not to rollback on nbconn. It's more
158 + * convenient than duplicating all exit labels.
164 ret = l->accept(l, cfd, &addr);
165 if (unlikely(ret <= 0)) {
166 @@ -644,7 +684,14 @@ void listener_accept(int fd)
168 HA_ATOMIC_SUB(&l->nbconn, 1);
170 - if (l->nbconn < l->maxconn && l->state == LI_FULL) {
171 + if (p && next_feconn)
172 + HA_ATOMIC_SUB(&p->feconn, 1);
175 + HA_ATOMIC_SUB(&actconn, 1);
177 + if ((l->state == LI_FULL && l->nbconn < l->maxconn) ||
178 + (l->state == LI_LIMITED && ((!p || p->feconn < p->maxconn) && (actconn < global.maxconn)))) {
179 /* at least one thread has to this when quitting */
182 @@ -668,8 +715,11 @@ void listener_release(struct listener *l)
184 if (!(l->options & LI_O_UNLIMITED))
185 HA_ATOMIC_SUB(&actconn, 1);
187 + HA_ATOMIC_SUB(&fe->feconn, 1);
188 HA_ATOMIC_SUB(&l->nbconn, 1);
189 - if (l->state == LI_FULL)
191 + if (l->state == LI_FULL || l->state == LI_LIMITED)
194 /* Dequeues all of the listeners waiting for a resource */
195 diff --git a/src/session.c b/src/session.c
196 index b91d67ee..c1515261 100644
199 @@ -53,10 +53,6 @@ struct session *session_new(struct proxy *fe, struct listener *li, enum obj_type
200 vars_init(&sess->vars, SCOPE_SESS);
202 sess->t_handshake = -1; /* handshake not done yet */
203 - HA_ATOMIC_UPDATE_MAX(&fe->fe_counters.conn_max,
204 - HA_ATOMIC_ADD(&fe->feconn, 1));
206 - proxy_inc_fe_conn_ctr(li, fe);
207 HA_ATOMIC_ADD(&totalconn, 1);
208 HA_ATOMIC_ADD(&jobs, 1);
210 @@ -65,7 +61,6 @@ struct session *session_new(struct proxy *fe, struct listener *li, enum obj_type
212 void session_free(struct session *sess)
214 - HA_ATOMIC_SUB(&sess->fe->feconn, 1);
216 listener_release(sess->listener);
217 session_store_counters(sess);