/src/haproxy/src/dynbuf.c
Line | Count | Source |
1 | | /* |
2 | | * Buffer management functions. |
3 | | * |
4 | | * Copyright 2000-2012 Willy Tarreau <w@1wt.eu> |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU General Public License |
8 | | * as published by the Free Software Foundation; either version |
9 | | * 2 of the License, or (at your option) any later version. |
10 | | * |
11 | | */ |
12 | | |
13 | | #include <ctype.h> |
14 | | #include <stdio.h> |
15 | | #include <string.h> |
16 | | |
17 | | #include <haproxy/api.h> |
18 | | #include <haproxy/cfgparse.h> |
19 | | #include <haproxy/dynbuf.h> |
20 | | #include <haproxy/global.h> |
21 | | #include <haproxy/list.h> |
22 | | #include <haproxy/pool.h> |
23 | | #include <haproxy/tools.h> |
24 | | |
25 | | struct pool_head *pool_head_buffer __read_mostly; |
26 | | struct pool_head *pool_head_large_buffer __read_mostly = NULL; |
27 | | struct pool_head *pool_head_small_buffer __read_mostly; |
28 | | |
29 | | /* perform minimal initializations, report 0 in case of error, 1 if OK. */ |
30 | | int init_buffer() |
31 | 0 | { |
32 | 0 | void *buffer; |
33 | 0 | int thr; |
34 | 0 | int done; |
35 | 0 | int i; |
36 | |
|
37 | 0 | pool_head_buffer = create_aligned_pool("buffer", global.tune.bufsize, 64, MEM_F_SHARED|MEM_F_EXACT); |
38 | 0 | if (!pool_head_buffer) |
39 | 0 | return 0; |
40 | | |
41 | 0 | if (global.tune.bufsize_large) { |
42 | 0 | pool_head_large_buffer = create_aligned_pool("large_buffer", global.tune.bufsize_large, 64, MEM_F_SHARED|MEM_F_EXACT); |
43 | 0 | if (!pool_head_large_buffer) |
44 | 0 | return 0; |
45 | 0 | } |
46 | | |
47 | 0 | if (global.tune.bufsize_small) { |
48 | 0 | pool_head_small_buffer = create_aligned_pool("small_buffer", global.tune.bufsize_small, 64, MEM_F_SHARED|MEM_F_EXACT); |
49 | 0 | if (!pool_head_small_buffer) |
50 | 0 | return 0; |
51 | 0 | } |
52 | | |
53 | | /* make sure any change to the queues assignment isn't overlooked */ |
54 | 0 | BUG_ON(DB_PERMANENT - DB_UNLIKELY - 1 != DYNBUF_NBQ); |
55 | 0 | BUG_ON(DB_MUX_RX_Q < DB_SE_RX_Q || DB_MUX_RX_Q >= DYNBUF_NBQ); |
56 | 0 | BUG_ON(DB_SE_RX_Q < DB_CHANNEL_Q || DB_SE_RX_Q >= DYNBUF_NBQ); |
57 | 0 | BUG_ON(DB_CHANNEL_Q < DB_MUX_TX_Q || DB_CHANNEL_Q >= DYNBUF_NBQ); |
58 | 0 | BUG_ON(DB_MUX_TX_Q >= DYNBUF_NBQ); |
59 | |
|
60 | 0 | for (thr = 0; thr < MAX_THREADS; thr++) { |
61 | 0 | for (i = 0; i < DYNBUF_NBQ; i++) |
62 | 0 | LIST_INIT(&ha_thread_ctx[thr].buffer_wq[i]); |
63 | 0 | ha_thread_ctx[thr].bufq_map = 0; |
64 | 0 | } |
65 | | |
66 | | |
67 | | /* The reserved buffer is what we leave behind us. Thus we always need |
68 | | * at least one extra buffer in minavail otherwise we'll end up waking |
69 | | * up tasks with no memory available, causing a lot of useless wakeups. |
70 | | * That means that we always want to have at least 3 buffers available |
71 | | * (2 for current session, one for next session that might be needed to |
72 | | * release a server connection). |
73 | | */ |
74 | 0 | pool_head_buffer->minavail = MAX(global.tune.reserved_bufs, 3); |
75 | 0 | if (global.tune.buf_limit) |
76 | 0 | pool_head_buffer->limit = global.tune.buf_limit; |
77 | |
|
78 | 0 | for (done = 0; done < pool_head_buffer->minavail - 1; done++) { |
79 | 0 | buffer = pool_alloc_nocache(pool_head_buffer, init_buffer); |
80 | 0 | if (!buffer) |
81 | 0 | return 0; |
82 | 0 | pool_free(pool_head_buffer, buffer); |
83 | 0 | } |
84 | 0 | return 1; |
85 | 0 | } |
86 | | |
87 | | /* |
88 | | * Dumps part or all of a buffer. |
89 | | */ |
90 | | void buffer_dump(FILE *o, struct buffer *b, int from, int to) |
91 | 0 | { |
92 | 0 | fprintf(o, "Dumping buffer %p\n", b); |
93 | 0 | fprintf(o, " orig=%p size=%u head=%u tail=%u data=%u\n", |
94 | 0 | b_orig(b), (unsigned int)b_size(b), (unsigned int)b_head_ofs(b), (unsigned int)b_tail_ofs(b), (unsigned int)b_data(b)); |
95 | |
|
96 | 0 | fprintf(o, "Dumping contents from byte %d to byte %d\n", from, to); |
97 | 0 | fprintf(o, " 0 1 2 3 4 5 6 7 8 9 a b c d e f\n"); |
98 | | /* dump hexa */ |
99 | 0 | while (from < to) { |
100 | 0 | int i; |
101 | |
|
102 | 0 | fprintf(o, " %04x: ", from); |
103 | 0 | for (i = 0; ((from + i) < to) && (i < 16) ; i++) { |
104 | 0 | fprintf(o, "%02x ", (unsigned char)b_orig(b)[from + i]); |
105 | 0 | if (i == 7) |
106 | 0 | fprintf(o, "- "); |
107 | 0 | } |
108 | 0 | if (to - from < 16) { |
109 | 0 | int j = 0; |
110 | |
|
111 | 0 | for (j = 0; j < from + 16 - to; j++) |
112 | 0 | fprintf(o, " "); |
113 | 0 | if (j > 8) |
114 | 0 | fprintf(o, " "); |
115 | 0 | } |
116 | 0 | fprintf(o, " "); |
117 | 0 | for (i = 0; (from + i < to) && (i < 16) ; i++) { |
118 | 0 | fprintf(o, "%c", isprint((unsigned char)b_orig(b)[from + i]) ? b_orig(b)[from + i] : '.') ; |
119 | 0 | if ((i == 15) && ((from + i) != to-1)) |
120 | 0 | fprintf(o, "\n"); |
121 | 0 | } |
122 | 0 | from += i; |
123 | 0 | } |
124 | 0 | fprintf(o, "\n--\n"); |
125 | 0 | fflush(o); |
126 | 0 | } |
127 | | |
128 | | /* see offer_buffers() for details */ |
129 | | void __offer_buffers(void *from, unsigned int count) |
130 | 0 | { |
131 | 0 | struct buffer_wait *wait, *wait_back; |
132 | 0 | int q; |
133 | | |
134 | | /* For now, we consider that all objects need 1 buffer, so we can stop |
135 | | * waking up them once we have enough of them to eat all the available |
136 | | * buffers. Note that we don't really know if they are streams or just |
137 | | * other tasks, but that's a rough estimate. Similarly, for each cached |
138 | | * event we'll need 1 buffer. |
139 | | */ |
140 | 0 | for (q = 0; q < DYNBUF_NBQ; q++) { |
141 | 0 | if (!(th_ctx->bufq_map & (1 << q))) |
142 | 0 | continue; |
143 | 0 | BUG_ON_HOT(LIST_ISEMPTY(&th_ctx->buffer_wq[q])); |
144 | |
|
145 | 0 | list_for_each_entry_safe(wait, wait_back, &th_ctx->buffer_wq[q], list) { |
146 | 0 | if (!count) |
147 | 0 | break; |
148 | | |
149 | 0 | if (wait->target == from || !wait->wakeup_cb(wait->target)) |
150 | 0 | continue; |
151 | | |
152 | 0 | LIST_DEL_INIT(&wait->list); |
153 | 0 | count--; |
154 | 0 | } |
155 | 0 | if (LIST_ISEMPTY(&th_ctx->buffer_wq[q])) |
156 | 0 | th_ctx->bufq_map &= ~(1 << q); |
157 | 0 | } |
158 | 0 | } |
159 | | |
160 | | /* config parser for global "tune.buffers.limit", accepts a number >= 0 */ |
161 | | static int cfg_parse_tune_buffers_limit(char **args, int section_type, struct proxy *curpx, |
162 | | const struct proxy *defpx, const char *file, int line, |
163 | | char **err) |
164 | 0 | { |
165 | 0 | int limit; |
166 | |
|
167 | 0 | if (too_many_args(1, args, err, NULL)) |
168 | 0 | return -1; |
169 | | |
170 | 0 | limit = atoi(args[1]); |
171 | 0 | if (limit < 0) { |
172 | 0 | memprintf(err, "'%s' expects a non-negative number but got '%s'.", args[0], args[1]); |
173 | 0 | return -1; |
174 | 0 | } |
175 | | |
176 | 0 | global.tune.buf_limit = limit; |
177 | 0 | if (global.tune.buf_limit) { |
178 | 0 | if (global.tune.buf_limit < 3) |
179 | 0 | global.tune.buf_limit = 3; |
180 | 0 | } |
181 | |
|
182 | 0 | return 0; |
183 | 0 | } |
184 | | |
185 | | /* config parser for global "tune.buffers.reserve", accepts a number >= 0 */ |
186 | | static int cfg_parse_tune_buffers_reserve(char **args, int section_type, struct proxy *curpx, |
187 | | const struct proxy *defpx, const char *file, int line, |
188 | | char **err) |
189 | 0 | { |
190 | 0 | int reserve; |
191 | |
|
192 | 0 | if (too_many_args(1, args, err, NULL)) |
193 | 0 | return -1; |
194 | | |
195 | 0 | reserve = atoi(args[1]); |
196 | 0 | if (reserve < 0) { |
197 | 0 | memprintf(err, "'%s' expects a non-negative number but got '%s'.", args[0], args[1]); |
198 | 0 | return -1; |
199 | 0 | } |
200 | | |
201 | 0 | global.tune.reserved_bufs = reserve; |
202 | 0 | return 0; |
203 | 0 | } |
204 | | |
205 | | /* config parse for global "tune.bufsize.large" */ |
206 | | static int cfg_parse_tune_bufsize_large(char **args, int section_type, |
207 | | struct proxy *curpx, const struct proxy *defpx, |
208 | | const char *file, int line, char **err) |
209 | 0 | { |
210 | 0 | const char *res; |
211 | 0 | uint size; |
212 | |
|
213 | 0 | if (too_many_args(1, args, err, NULL)) |
214 | 0 | goto err; |
215 | | |
216 | 0 | if (*(args[1]) == 0) { |
217 | 0 | memprintf(err, "'%s' expects an integer argument.\n", args[0]); |
218 | 0 | goto err; |
219 | 0 | } |
220 | | |
221 | 0 | res = parse_size_err(args[1], &size); |
222 | 0 | if (res != NULL) { |
223 | 0 | memprintf(err, "unexpected '%s' after size passed to '%s'", res, args[0]); |
224 | 0 | goto err; |
225 | 0 | } |
226 | | |
227 | 0 | if (size <= 0) { |
228 | 0 | memprintf(err, "'%s' expects a positive integer argument.\n", args[0]); |
229 | 0 | goto err; |
230 | 0 | } |
231 | | |
232 | 0 | global.tune.bufsize_large = size; |
233 | 0 | return 0; |
234 | | |
235 | 0 | err: |
236 | 0 | return -1; |
237 | 0 | } |
238 | | |
239 | | /* config parse for global "tune.bufsize.small" */ |
240 | | static int cfg_parse_tune_bufsize_small(char **args, int section_type, |
241 | | struct proxy *curpx, const struct proxy *defpx, |
242 | | const char *file, int line, char **err) |
243 | 0 | { |
244 | 0 | const char *res; |
245 | 0 | uint size; |
246 | |
|
247 | 0 | if (too_many_args(1, args, err, NULL)) |
248 | 0 | goto err; |
249 | | |
250 | 0 | if (*(args[1]) == 0) { |
251 | 0 | memprintf(err, "'%s' expects an integer argument.\n", args[0]); |
252 | 0 | goto err; |
253 | 0 | } |
254 | | |
255 | 0 | res = parse_size_err(args[1], &size); |
256 | 0 | if (res != NULL) { |
257 | 0 | memprintf(err, "unexpected '%s' after size passed to '%s'", res, args[0]); |
258 | 0 | goto err; |
259 | 0 | } |
260 | | |
261 | 0 | if (size <= 0) { |
262 | 0 | memprintf(err, "'%s' expects a positive integer argument.\n", args[0]); |
263 | 0 | goto err; |
264 | 0 | } |
265 | | |
266 | 0 | global.tune.bufsize_small = size; |
267 | 0 | return 0; |
268 | | |
269 | 0 | err: |
270 | 0 | return -1; |
271 | 0 | } |
272 | | |
273 | | /* allocate emergency buffers for the thread */ |
274 | | static int alloc_emergency_buffers_per_thread(void) |
275 | 0 | { |
276 | 0 | int idx; |
277 | |
|
278 | 0 | th_ctx->emergency_bufs_left = global.tune.reserved_bufs; |
279 | 0 | th_ctx->emergency_bufs = calloc(global.tune.reserved_bufs, sizeof(*th_ctx->emergency_bufs)); |
280 | 0 | if (!th_ctx->emergency_bufs) |
281 | 0 | return 0; |
282 | | |
283 | 0 | for (idx = 0; idx < global.tune.reserved_bufs; idx++) { |
284 | | /* reserved bufs are not subject to the limit, so we must push it */ |
285 | 0 | if (_HA_ATOMIC_LOAD(&pool_head_buffer->limit)) |
286 | 0 | _HA_ATOMIC_INC(&pool_head_buffer->limit); |
287 | 0 | th_ctx->emergency_bufs[idx] = pool_alloc_flag(pool_head_buffer, POOL_F_NO_POISON | POOL_F_NO_FAIL); |
288 | 0 | if (!th_ctx->emergency_bufs[idx]) |
289 | 0 | return 0; |
290 | 0 | } |
291 | | |
292 | 0 | return 1; |
293 | 0 | } |
294 | | |
295 | | /* frees the thread's emergency buffers */ |
296 | | static void free_emergency_buffers_per_thread(void) |
297 | 0 | { |
298 | 0 | int idx; |
299 | |
|
300 | 0 | if (th_ctx->emergency_bufs) { |
301 | 0 | for (idx = 0; idx < global.tune.reserved_bufs; idx++) |
302 | 0 | pool_free(pool_head_buffer, th_ctx->emergency_bufs[idx]); |
303 | 0 | } |
304 | |
|
305 | | ha_free(&th_ctx->emergency_bufs); |
306 | 0 | } |
307 | | |
308 | | /* config keyword parsers */ |
309 | | static struct cfg_kw_list cfg_kws = {ILH, { |
310 | | { CFG_GLOBAL, "tune.buffers.limit", cfg_parse_tune_buffers_limit }, |
311 | | { CFG_GLOBAL, "tune.buffers.reserve", cfg_parse_tune_buffers_reserve }, |
312 | | { CFG_GLOBAL, "tune.bufsize.large", cfg_parse_tune_bufsize_large }, |
313 | | { CFG_GLOBAL, "tune.bufsize.small", cfg_parse_tune_bufsize_small }, |
314 | | { 0, NULL, NULL } |
315 | | }}; |
316 | | |
317 | | INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); |
318 | | REGISTER_PER_THREAD_ALLOC(alloc_emergency_buffers_per_thread); |
319 | | REGISTER_PER_THREAD_FREE(free_emergency_buffers_per_thread); |
320 | | |
321 | | /* |
322 | | * Local variables: |
323 | | * c-indent-level: 8 |
324 | | * c-basic-offset: 8 |
325 | | * End: |
326 | | */ |