]> git.99rst.org Git - openwrt-packages.git/blob
a1ca73b2e45c8032d8ea53c0c991505d7842987d
[openwrt-packages.git] /
1 commit c98cdf7cc755c579a8b9cceee4809089267615ce
2 Author: Willy Tarreau <w@1wt.eu>
3 Date: Wed Feb 27 19:32:32 2019 +0100
4
5 BUG/MEDIUM: listener: make sure the listener never accepts too many conns
6
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.
16
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.
22
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.
26
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>
32
33 diff --git a/src/listener.c b/src/listener.c
34 index dab07a5e..68c84fbe 100644
35 --- a/src/listener.c
36 +++ b/src/listener.c
37 @@ -408,6 +408,8 @@ void listener_accept(int fd)
38 struct proxy *p;
39 int max_accept;
40 int next_conn = 0;
41 + int next_feconn = 0;
42 + int next_actconn = 0;
43 int expire;
44 int cfd;
45 int ret;
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.
49 */
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);
54 unsigned int count;
55
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.
60 + */
61 do {
62 count = l->nbconn;
63 if (count >= l->maxconn) {
64 @@ -501,15 +506,42 @@ void listener_accept(int fd)
65 listener_full(l);
66 }
67
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 */
71 - goto end;
72 + if (p) {
73 + do {
74 + count = p->feconn;
75 + if (count >= p->maxconn) {
76 + /* the frontend was marked full or another
77 + * thread is going to do it.
78 + */
79 + next_feconn = 0;
80 + goto end;
81 + }
82 + next_feconn = count + 1;
83 + } while (!HA_ATOMIC_CAS(&p->feconn, &count, next_feconn));
84 +
85 + if (unlikely(next_feconn == p->maxconn)) {
86 + /* we just filled it */
87 + limit_listener(l, &p->listener_queue);
88 + }
89 }
90
91 - if (unlikely(p && p->feconn >= p->maxconn)) {
92 - limit_listener(l, &p->listener_queue);
93 - goto end;
94 + if (!(l->options & LI_O_UNLIMITED)) {
95 + do {
96 + count = actconn;
97 + if (count >= global.maxconn) {
98 + /* the process was marked full or another
99 + * thread is going to do it.
100 + */
101 + next_actconn = 0;
102 + goto end;
103 + }
104 + next_actconn = count + 1;
105 + } while (!HA_ATOMIC_CAS(&actconn, &count, next_actconn));
106 +
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 */
110 + }
111 }
112
113 #ifdef USE_ACCEPT4
114 @@ -544,6 +576,10 @@ void listener_accept(int fd)
115 case EINTR:
116 case ECONNABORTED:
117 HA_ATOMIC_SUB(&l->nbconn, 1);
118 + if (p)
119 + HA_ATOMIC_SUB(&p->feconn, 1);
120 + if (!(l->options & LI_O_UNLIMITED))
121 + HA_ATOMIC_SUB(&actconn, 1);
122 continue;
123 case ENFILE:
124 if (p)
125 @@ -574,18 +610,20 @@ void listener_accept(int fd)
126 if (l->counters)
127 HA_ATOMIC_UPDATE_MAX(&l->counters->conn_max, next_conn);
128
129 + if (p)
130 + HA_ATOMIC_UPDATE_MAX(&p->fe_counters.conn_max, next_feconn);
131 +
132 + proxy_inc_fe_conn_ctr(l, p);
133 +
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);
138 }
139
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",
143 p->id);
144 - if (!(l->options & LI_O_UNLIMITED))
145 - HA_ATOMIC_SUB(&actconn, 1);
146 close(cfd);
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)
150 }
151
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.
159 */
160 next_conn = 0;
161 + next_feconn = 0;
162 + next_actconn = 0;
163
164 ret = l->accept(l, cfd, &addr);
165 if (unlikely(ret <= 0)) {
166 @@ -644,7 +684,14 @@ void listener_accept(int fd)
167 if (next_conn)
168 HA_ATOMIC_SUB(&l->nbconn, 1);
169
170 - if (l->nbconn < l->maxconn && l->state == LI_FULL) {
171 + if (p && next_feconn)
172 + HA_ATOMIC_SUB(&p->feconn, 1);
173 +
174 + if (next_actconn)
175 + HA_ATOMIC_SUB(&actconn, 1);
176 +
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 */
180 resume_listener(l);
181
182 @@ -668,8 +715,11 @@ void listener_release(struct listener *l)
183
184 if (!(l->options & LI_O_UNLIMITED))
185 HA_ATOMIC_SUB(&actconn, 1);
186 + if (fe)
187 + HA_ATOMIC_SUB(&fe->feconn, 1);
188 HA_ATOMIC_SUB(&l->nbconn, 1);
189 - if (l->state == LI_FULL)
190 +
191 + if (l->state == LI_FULL || l->state == LI_LIMITED)
192 resume_listener(l);
193
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
197 --- a/src/session.c
198 +++ b/src/session.c
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);
201 sess->task = NULL;
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));
205 - if (li)
206 - proxy_inc_fe_conn_ctr(li, fe);
207 HA_ATOMIC_ADD(&totalconn, 1);
208 HA_ATOMIC_ADD(&jobs, 1);
209 }
210 @@ -65,7 +61,6 @@ struct session *session_new(struct proxy *fe, struct listener *li, enum obj_type
211
212 void session_free(struct session *sess)
213 {
214 - HA_ATOMIC_SUB(&sess->fe->feconn, 1);
215 if (sess->listener)
216 listener_release(sess->listener);
217 session_store_counters(sess);
git clone https://git.99rst.org/PROJECT