1 /* ====================================================================
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 * ====================================================================
19 */
20
21 #include <stdlib.h>
22
23 #include <apr_pools.h>
24
25 #include "serf.h"
26 #include "serf_bucket_util.h"
27
28
29 typedef struct node_header_t {
30 apr_size_t size;
31 union {
32 struct node_header_t *next; /* if size == 0 (freed/inactive) */
33 /* no data if size == STANDARD_NODE_SIZE */
34 apr_memnode_t *memnode; /* if size > STANDARD_NODE_SIZE */
35 } u;
36 } node_header_t;
37
38 /* The size of a node_header_t, properly aligned. Note that (normally)
39 * this macro will round the size to a multiple of 8 bytes. Keep this in
40 * mind when altering the node_header_t structure. Also, keep in mind that
41 * node_header_t is an overhead for every allocation performed through
42 * the serf_bucket_mem_alloc() function.
43 */
44 #define SIZEOF_NODE_HEADER_T APR_ALIGN_DEFAULT(sizeof(node_header_t))
45
46
47 /* STANDARD_NODE_SIZE is manually set to an allocation size that will
48 * capture most allocators performed via this API. It must be "large
49 * enough" to avoid lots of spillage to allocating directly from the
50 * apr_allocator associated with the bucket allocator. The apr_allocator
51 * has a minimum size of 8k, which can be expensive if you missed the
52 * STANDARD_NODE_SIZE by just a few bytes.
53 */
54 /* ### we should define some rules or ways to determine how to derive
55 * ### a "good" value for this. probably log some stats on allocs, then
56 * ### analyze them for size "misses". then find the balance point between
57 * ### wasted space due to min-size allocator, and wasted-space due to
58 * ### size-spill to the 8k minimum.
59 */
60 #define STANDARD_NODE_SIZE 128
61
62 /* When allocating a block of memory from the allocator, we should go for
63 * an 8k block, minus the overhead that the allocator needs.
64 */
65 #define ALLOC_AMT (8192 - APR_MEMNODE_T_SIZE)
66
67 /* Define DEBUG_DOUBLE_FREE if you're interested in debugging double-free
68 * calls to serf_bucket_mem_free().
69 */
70 #define DEBUG_DOUBLE_FREE
71
72
73 typedef struct {
74 const serf_bucket_t *bucket;
75 apr_status_t last;
76 } read_status_t;
77
78 #define TRACK_BUCKET_COUNT 100 /* track N buckets' status */
79
80 typedef struct {
81 int next_index; /* info[] is a ring. next bucket goes at this idx. */
82 int num_used;
83
84 read_status_t info[TRACK_BUCKET_COUNT];
85 } track_state_t;
86
87
88 struct serf_bucket_alloc_t {
89 apr_pool_t *pool;
90 apr_allocator_t *allocator;
91 int own_allocator;
92
93 serf_unfreed_func_t unfreed;
94 void *unfreed_baton;
95
96 apr_uint32_t num_alloc;
97
98 node_header_t *freelist; /* free STANDARD_NODE_SIZE blocks */
99 apr_memnode_t *blocks; /* blocks we allocated for subdividing */
100
101 track_state_t *track;
102 };
103
104 /* ==================================================================== */
105
106
allocator_cleanup(void * data)107 static apr_status_t allocator_cleanup(void *data)
108 {
109 serf_bucket_alloc_t *allocator = data;
110
111 /* If we allocated anything, give it back. */
112 if (allocator->blocks) {
113 apr_allocator_free(allocator->allocator, allocator->blocks);
114 }
115
116 /* If we allocated our own allocator (?!), destroy it here. */
117 if (allocator->own_allocator) {
118 apr_allocator_destroy(allocator->allocator);
119 }
120
121 return APR_SUCCESS;
122 }
123
serf_bucket_allocator_create(apr_pool_t * pool,serf_unfreed_func_t unfreed,void * unfreed_baton)124 serf_bucket_alloc_t *serf_bucket_allocator_create(
125 apr_pool_t *pool,
126 serf_unfreed_func_t unfreed,
127 void *unfreed_baton)
128 {
129 serf_bucket_alloc_t *allocator = apr_pcalloc(pool, sizeof(*allocator));
130
131 allocator->pool = pool;
132 allocator->allocator = apr_pool_allocator_get(pool);
133 if (allocator->allocator == NULL) {
134 /* This most likely means pools are running in debug mode, create our
135 * own allocator to deal with memory ourselves */
136 apr_allocator_create(&allocator->allocator);
137 allocator->own_allocator = 1;
138 }
139 allocator->unfreed = unfreed;
140 allocator->unfreed_baton = unfreed_baton;
141
142 #ifdef SERF_DEBUG_BUCKET_USE
143 {
144 track_state_t *track;
145
146 track = allocator->track = apr_palloc(pool, sizeof(*allocator->track));
147 track->next_index = 0;
148 track->num_used = 0;
149 }
150 #endif
151
152 /* NOTE: On a fork/exec, the child won't bother cleaning up memory.
153 This is just fine... the memory will go away at exec.
154
155 NOTE: If the child will NOT perform an exec, then the parent or
156 the child will need to decide who to clean up any
157 outstanding connection/buckets (as appropriate). */
158 apr_pool_cleanup_register(pool, allocator,
159 allocator_cleanup, apr_pool_cleanup_null);
160
161 return allocator;
162 }
163
serf_bucket_allocator_get_pool(const serf_bucket_alloc_t * allocator)164 apr_pool_t *serf_bucket_allocator_get_pool(
165 const serf_bucket_alloc_t *allocator)
166 {
167 return allocator->pool;
168 }
169
170
serf_bucket_mem_alloc(serf_bucket_alloc_t * allocator,apr_size_t size)171 void *serf_bucket_mem_alloc(
172 serf_bucket_alloc_t *allocator,
173 apr_size_t size)
174 {
175 node_header_t *node;
176
177 ++allocator->num_alloc;
178
179 size += SIZEOF_NODE_HEADER_T;
180 if (size <= STANDARD_NODE_SIZE) {
181 if (allocator->freelist) {
182 /* just pull a node off our freelist */
183 node = allocator->freelist;
184 allocator->freelist = node->u.next;
185 #ifdef DEBUG_DOUBLE_FREE
186 /* When we free an item, we set its size to zero. Thus, when
187 * we return it to the caller, we must ensure the size is set
188 * properly.
189 */
190 node->size = STANDARD_NODE_SIZE;
191 #endif
192 }
193 else {
194 apr_memnode_t *active = allocator->blocks;
195
196 if (active == NULL
197 || active->first_avail + STANDARD_NODE_SIZE >= active->endp) {
198 apr_memnode_t *head = allocator->blocks;
199
200 /* ran out of room. grab another block. */
201 active = apr_allocator_alloc(allocator->allocator, ALLOC_AMT);
202
203 /* System couldn't provide us with memory. */
204 if (active == NULL)
205 return NULL;
206
207 /* link the block into our tracking list */
208 allocator->blocks = active;
209 active->next = head;
210 }
211
212 node = (node_header_t *)active->first_avail;
213 node->size = STANDARD_NODE_SIZE;
214 active->first_avail += STANDARD_NODE_SIZE;
215 }
216 }
217 else {
218 apr_memnode_t *memnode = apr_allocator_alloc(allocator->allocator,
219 size);
220
221 if (memnode == NULL)
222 return NULL;
223
224 node = (node_header_t *)memnode->first_avail;
225 node->u.memnode = memnode;
226 node->size = size;
227 }
228
229 return ((char *)node) + SIZEOF_NODE_HEADER_T;
230 }
231
232
serf_bucket_mem_calloc(serf_bucket_alloc_t * allocator,apr_size_t size)233 void *serf_bucket_mem_calloc(
234 serf_bucket_alloc_t *allocator,
235 apr_size_t size)
236 {
237 void *mem;
238 mem = serf_bucket_mem_alloc(allocator, size);
239 if (mem == NULL)
240 return NULL;
241 memset(mem, 0, size);
242 return mem;
243 }
244
245
serf_bucket_mem_free(serf_bucket_alloc_t * allocator,void * block)246 void serf_bucket_mem_free(
247 serf_bucket_alloc_t *allocator,
248 void *block)
249 {
250 node_header_t *node;
251
252 --allocator->num_alloc;
253
254 node = (node_header_t *)((char *)block - SIZEOF_NODE_HEADER_T);
255
256 if (node->size == STANDARD_NODE_SIZE) {
257 /* put the node onto our free list */
258 node->u.next = allocator->freelist;
259 allocator->freelist = node;
260
261 #ifdef DEBUG_DOUBLE_FREE
262 /* note that this thing was freed. */
263 node->size = 0;
264 }
265 else if (node->size == 0) {
266 /* damn thing was freed already. */
267 abort();
268 #endif
269 }
270 else {
271 #ifdef DEBUG_DOUBLE_FREE
272 /* note that this thing was freed. */
273 node->size = 0;
274 #endif
275
276 /* now free it */
277 apr_allocator_free(allocator->allocator, node->u.memnode);
278 }
279 }
280
281
282 /* ==================================================================== */
283
284
285 #ifdef SERF_DEBUG_BUCKET_USE
286
find_read_status(track_state_t * track,const serf_bucket_t * bucket,int create_rs)287 static read_status_t *find_read_status(
288 track_state_t *track,
289 const serf_bucket_t *bucket,
290 int create_rs)
291 {
292 read_status_t *rs;
293
294 if (track->num_used) {
295 int count = track->num_used;
296 int idx = track->next_index;
297
298 /* Search backwards. In all likelihood, the bucket which just got
299 * read was read very recently.
300 */
301 while (count-- > 0) {
302 if (!idx--) {
303 /* assert: track->num_used == TRACK_BUCKET_COUNT */
304 idx = track->num_used - 1;
305 }
306 if ((rs = &track->info[idx])->bucket == bucket) {
307 return rs;
308 }
309 }
310 }
311
312 /* Only create a new read_status_t when asked. */
313 if (!create_rs)
314 return NULL;
315
316 if (track->num_used < TRACK_BUCKET_COUNT) {
317 /* We're still filling up the ring. */
318 ++track->num_used;
319 }
320
321 rs = &track->info[track->next_index];
322 rs->bucket = bucket;
323 rs->last = APR_SUCCESS; /* ### the right initial value? */
324
325 if (++track->next_index == TRACK_BUCKET_COUNT)
326 track->next_index = 0;
327
328 return rs;
329 }
330
331 #endif /* SERF_DEBUG_BUCKET_USE */
332
333
serf_debug__record_read(const serf_bucket_t * bucket,apr_status_t status)334 apr_status_t serf_debug__record_read(
335 const serf_bucket_t *bucket,
336 apr_status_t status)
337 {
338 #ifndef SERF_DEBUG_BUCKET_USE
339 return status;
340 #else
341
342 track_state_t *track = bucket->allocator->track;
343 read_status_t *rs = find_read_status(track, bucket, 1);
344
345 /* Validate that the previous status value allowed for another read. */
346 if (APR_STATUS_IS_EAGAIN(rs->last) /* ### or APR_EOF? */) {
347 /* Somebody read when they weren't supposed to. Bail. */
348 abort();
349 }
350
351 /* Save the current status for later. */
352 rs->last = status;
353
354 return status;
355 #endif
356 }
357
358
serf_debug__entered_loop(serf_bucket_alloc_t * allocator)359 void serf_debug__entered_loop(serf_bucket_alloc_t *allocator)
360 {
361 #ifdef SERF_DEBUG_BUCKET_USE
362
363 track_state_t *track = allocator->track;
364 read_status_t *rs = &track->info[0];
365
366 for ( ; track->num_used; --track->num_used, ++rs ) {
367 if (rs->last == APR_SUCCESS) {
368 /* Somebody should have read this bucket again. */
369 abort();
370 }
371
372 /* ### other status values? */
373 }
374
375 /* num_used was reset. also need to reset the next index. */
376 track->next_index = 0;
377
378 #endif
379 }
380
381
serf_debug__closed_conn(serf_bucket_alloc_t * allocator)382 void serf_debug__closed_conn(serf_bucket_alloc_t *allocator)
383 {
384 #ifdef SERF_DEBUG_BUCKET_USE
385
386 /* Just reset the number used so that we don't examine the info[] */
387 allocator->track->num_used = 0;
388 allocator->track->next_index = 0;
389
390 #endif
391 }
392
393
serf_debug__bucket_destroy(const serf_bucket_t * bucket)394 void serf_debug__bucket_destroy(const serf_bucket_t *bucket)
395 {
396 #ifdef SERF_DEBUG_BUCKET_USE
397
398 track_state_t *track = bucket->allocator->track;
399 read_status_t *rs = find_read_status(track, bucket, 0);
400
401 if (rs != NULL && rs->last != APR_EOF) {
402 /* The bucket was destroyed before it was read to completion. */
403
404 /* Special exception for socket buckets. If a connection remains
405 * open, they are not read to completion.
406 */
407 if (SERF_BUCKET_IS_SOCKET(bucket))
408 return;
409
410 /* Ditto for SSL Decrypt buckets. */
411 if (SERF_BUCKET_IS_SSL_DECRYPT(bucket))
412 return;
413
414 /* Ditto for SSL Encrypt buckets. */
415 if (SERF_BUCKET_IS_SSL_ENCRYPT(bucket))
416 return;
417
418 /* Ditto for barrier buckets. */
419 if (SERF_BUCKET_IS_BARRIER(bucket))
420 return;
421
422
423 abort();
424 }
425
426 #endif
427 }
428
429
serf_debug__bucket_alloc_check(serf_bucket_alloc_t * allocator)430 void serf_debug__bucket_alloc_check(
431 serf_bucket_alloc_t *allocator)
432 {
433 #ifdef SERF_DEBUG_BUCKET_USE
434 if (allocator->num_alloc != 0) {
435 abort();
436 }
437 #endif
438 }
439
440