| /* ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #include <stdlib.h> |
| |
| #define APR_WANT_MEMFUNC |
| #include <apr_want.h> |
| #include <apr_pools.h> |
| #include <apr_hash.h> |
| #include <apr_strings.h> |
| |
| #include "serf.h" |
| #include "serf_bucket_util.h" |
| |
| #include "serf_private.h" |
| |
| /* Define SERF__DEBUG_UNFREED_MEMORY if you're interested in tracking |
| * unfreed blocks on pool cleanup. */ |
| /* #define SERF__DEBUG_UNFREED_MEMORY */ |
| |
| typedef struct node_header_t { |
| apr_size_t size; |
| union { |
| struct node_header_t *next; /* if size == 0 (freed/inactive) */ |
| /* no data if size == STANDARD_NODE_SIZE */ |
| apr_memnode_t *memnode; /* if size > STANDARD_NODE_SIZE */ |
| } u; |
| } node_header_t; |
| |
| /* The size of a node_header_t, properly aligned. Note that (normally) |
| * this macro will round the size to a multiple of 8 bytes. Keep this in |
| * mind when altering the node_header_t structure. Also, keep in mind that |
| * node_header_t is an overhead for every allocation performed through |
| * the serf_bucket_mem_alloc() function. |
| */ |
| #define SIZEOF_NODE_HEADER_T APR_ALIGN_DEFAULT(sizeof(node_header_t)) |
| |
| |
| /* STANDARD_NODE_SIZE is manually set to an allocation size that will |
| * capture most allocators performed via this API. It must be "large |
| * enough" to avoid lots of spillage to allocating directly from the |
| * apr_allocator associated with the bucket allocator. The apr_allocator |
| * has a minimum size of 8k, which can be expensive if you missed the |
| * STANDARD_NODE_SIZE by just a few bytes. |
| */ |
| /* ### we should define some rules or ways to determine how to derive |
| * ### a "good" value for this. probably log some stats on allocs, then |
| * ### analyze them for size "misses". then find the balance point between |
| * ### wasted space due to min-size allocator, and wasted-space due to |
| * ### size-spill to the 8k minimum. |
| */ |
| #define STANDARD_NODE_SIZE 128 |
| |
| /* When allocating a block of memory from the allocator, we should go for |
| * an 8k block, minus the overhead that the allocator needs. |
| */ |
| #define ALLOC_AMT (8192 - APR_MEMNODE_T_SIZE) |
| |
| /* Define DEBUG_DOUBLE_FREE if you're interested in debugging double-free |
| * calls to serf_bucket_mem_free() and access bucket allocator after it |
| * destroyed. |
| */ |
| #define DEBUG_DOUBLE_FREE |
| |
| /* If we have to create our own allocator, keep this amount of free ram |
| in the allocator before returning memory back to the OS */ |
| #define PRIVATE_ALLOCATOR_MAX_FREE (4 * 1024 * 1024) |
| |
| typedef struct read_status_t { |
| const serf_bucket_t *bucket; |
| apr_status_t last; |
| } read_status_t; |
| |
| #define TRACK_BUCKET_COUNT 100 /* track N buckets' status */ |
| |
| typedef struct track_state_t { |
| int next_index; /* info[] is a ring. next bucket goes at this idx. */ |
| int num_used; |
| |
| read_status_t info[TRACK_BUCKET_COUNT]; |
| } track_state_t; |
| |
| |
| struct serf_bucket_alloc_t { |
| apr_pool_t *pool; |
| apr_allocator_t *allocator; |
| int own_allocator; |
| |
| serf_unfreed_func_t unfreed; |
| void *unfreed_baton; |
| |
| apr_uint32_t num_alloc; |
| |
| node_header_t *freelist; /* free STANDARD_NODE_SIZE blocks */ |
| apr_memnode_t *blocks; /* blocks we allocated for subdividing */ |
| |
| track_state_t *track; |
| |
| #ifdef SERF__DEBUG_UNFREED_MEMORY |
| apr_hash_t *unfreed_blocks; |
| #endif |
| }; |
| |
| /* ==================================================================== */ |
| |
| |
| static apr_status_t allocator_cleanup(void *data) |
| { |
| serf_bucket_alloc_t *allocator = data; |
| |
| #ifdef SERF__DEBUG_UNFREED_MEMORY |
| apr_hash_index_t *hi; |
| for (hi = apr_hash_first(NULL, allocator->unfreed_blocks); |
| hi; hi = apr_hash_next(hi)) |
| { |
| void **block_p = (void**) apr_hash_this_key(hi); |
| apr_size_t size = (apr_size_t) apr_hash_this_val(hi); |
| |
| if (allocator->unfreed) { |
| allocator->unfreed(allocator->unfreed_baton, *block_p); |
| } |
| else { |
| fprintf(stderr, "Unfreed memory: %p (%d bytes)\n", *block_p, size); |
| } |
| } |
| #endif |
| |
| /* If we allocated anything, give it back. */ |
| if (allocator->blocks) { |
| apr_allocator_free(allocator->allocator, allocator->blocks); |
| } |
| |
| /* If we allocated our own allocator (?!), destroy it here. */ |
| if (allocator->own_allocator) { |
| apr_allocator_destroy(allocator->allocator); |
| } |
| |
| #ifdef DEBUG_DOUBLE_FREE |
| /* Set POOL to NULL to detect allocator usage after destroy. */ |
| allocator->pool = NULL; |
| #endif |
| return APR_SUCCESS; |
| } |
| |
| serf_bucket_alloc_t *serf_bucket_allocator_create( |
| apr_pool_t *pool, |
| serf_unfreed_func_t unfreed, |
| void *unfreed_baton) |
| { |
| serf_bucket_alloc_t *allocator = apr_pcalloc(pool, sizeof(*allocator)); |
| |
| allocator->pool = pool; |
| allocator->allocator = apr_pool_allocator_get(pool); |
| if (allocator->allocator == NULL) { |
| /* This most likely means pools are running in debug mode, create our |
| * own allocator to deal with memory ourselves */ |
| apr_allocator_create(&allocator->allocator); |
| apr_allocator_max_free_set(allocator->allocator, |
| PRIVATE_ALLOCATOR_MAX_FREE); |
| allocator->own_allocator = 1; |
| } |
| allocator->unfreed = unfreed; |
| allocator->unfreed_baton = unfreed_baton; |
| |
| #ifdef SERF__DEBUG_UNFREED_MEMORY |
| allocator->unfreed_blocks = apr_hash_make(pool); |
| #endif |
| |
| #ifdef SERF_DEBUG_BUCKET_USE |
| { |
| track_state_t *track; |
| |
| track = allocator->track = apr_palloc(pool, sizeof(*allocator->track)); |
| track->next_index = 0; |
| track->num_used = 0; |
| } |
| #endif |
| |
| /* NOTE: On a fork/exec, the child won't bother cleaning up memory. |
| This is just fine... the memory will go away at exec. |
| |
| NOTE: If the child will NOT perform an exec, then the parent or |
| the child will need to decide who to clean up any |
| outstanding connection/buckets (as appropriate). */ |
| apr_pool_cleanup_register(pool, allocator, |
| allocator_cleanup, apr_pool_cleanup_null); |
| |
| return allocator; |
| } |
| |
| apr_pool_t *serf_bucket_allocator_get_pool( |
| const serf_bucket_alloc_t *allocator) |
| { |
| return allocator->pool; |
| } |
| |
| |
| void *serf_bucket_mem_alloc( |
| serf_bucket_alloc_t *allocator, |
| apr_size_t size) |
| { |
| node_header_t *node; |
| void *block; |
| |
| #ifdef DEBUG_DOUBLE_FREE |
| if (allocator->pool == NULL) { |
| /* Attempt to use bucket allocator after it destroyed by |
| * pool cleanup. */ |
| abort(); |
| } |
| #endif |
| |
| ++allocator->num_alloc; |
| |
| size += SIZEOF_NODE_HEADER_T; |
| if (size <= STANDARD_NODE_SIZE) { |
| if (allocator->freelist) { |
| /* just pull a node off our freelist */ |
| node = allocator->freelist; |
| allocator->freelist = node->u.next; |
| #ifdef DEBUG_DOUBLE_FREE |
| /* When we free an item, we set its size to zero. Thus, when |
| * we return it to the caller, we must ensure the size is set |
| * properly. |
| */ |
| node->size = STANDARD_NODE_SIZE; |
| #endif |
| } |
| else { |
| apr_memnode_t *active = allocator->blocks; |
| |
| if (active == NULL |
| || active->first_avail + STANDARD_NODE_SIZE >= active->endp) { |
| apr_memnode_t *head = allocator->blocks; |
| |
| /* ran out of room. grab another block. */ |
| active = apr_allocator_alloc(allocator->allocator, ALLOC_AMT); |
| |
| /* System couldn't provide us with memory. */ |
| if (active == NULL) |
| return NULL; |
| |
| /* link the block into our tracking list */ |
| allocator->blocks = active; |
| active->next = head; |
| } |
| |
| node = (node_header_t *)active->first_avail; |
| node->size = STANDARD_NODE_SIZE; |
| active->first_avail += STANDARD_NODE_SIZE; |
| } |
| } |
| else { |
| apr_memnode_t *memnode = apr_allocator_alloc(allocator->allocator, |
| size); |
| |
| if (memnode == NULL) |
| return NULL; |
| |
| node = (node_header_t *)memnode->first_avail; |
| node->u.memnode = memnode; |
| node->size = size; |
| } |
| |
| block = ((char *)node) + SIZEOF_NODE_HEADER_T; |
| |
| #ifdef SERF__DEBUG_UNFREED_MEMORY |
| apr_hash_set(allocator->unfreed_blocks, |
| apr_pmemdup(allocator->pool, &block, sizeof(block)), |
| sizeof(block), (const void*) size); |
| #endif |
| |
| return block; |
| } |
| |
| |
| void *serf_bucket_mem_calloc( |
| serf_bucket_alloc_t *allocator, |
| apr_size_t size) |
| { |
| void *mem; |
| mem = serf_bucket_mem_alloc(allocator, size); |
| if (mem == NULL) |
| return NULL; |
| memset(mem, 0, size); |
| return mem; |
| } |
| |
| |
| void serf_bucket_mem_free( |
| serf_bucket_alloc_t *allocator, |
| void *block) |
| { |
| node_header_t *node; |
| |
| #ifdef DEBUG_DOUBLE_FREE |
| if (allocator->pool == NULL) { |
| /* Attempt to use bucket allocator after it destroyed by |
| * pool cleanup. */ |
| abort(); |
| } |
| #endif |
| --allocator->num_alloc; |
| |
| node = (node_header_t *)((char *)block - SIZEOF_NODE_HEADER_T); |
| |
| if (node->size == STANDARD_NODE_SIZE) { |
| /* put the node onto our free list */ |
| node->u.next = allocator->freelist; |
| allocator->freelist = node; |
| |
| #ifdef DEBUG_DOUBLE_FREE |
| /* note that this thing was freed. */ |
| node->size = 0; |
| } |
| else if (node->size == 0) { |
| /* damn thing was freed already. */ |
| abort(); |
| #endif |
| } |
| else { |
| #ifdef DEBUG_DOUBLE_FREE |
| /* note that this thing was freed. */ |
| node->size = 0; |
| #endif |
| |
| /* now free it */ |
| apr_allocator_free(allocator->allocator, node->u.memnode); |
| } |
| |
| #ifdef SERF__DEBUG_UNFREED_MEMORY |
| /* Remove block from unfreed blocks hashtable. */ |
| apr_hash_set(allocator->unfreed_blocks, &block, sizeof(block), NULL); |
| #endif |
| } |
| |
| |
| /* ==================================================================== */ |
| |
| |
| #ifdef SERF_DEBUG_BUCKET_USE |
| |
| static read_status_t *find_read_status( |
| track_state_t *track, |
| const serf_bucket_t *bucket, |
| int create_rs) |
| { |
| read_status_t *rs; |
| |
| if (track->num_used) { |
| int count = track->num_used; |
| int idx = track->next_index; |
| |
| /* Search backwards. In all likelihood, the bucket which just got |
| * read was read very recently. |
| */ |
| while (count-- > 0) { |
| if (!idx--) { |
| /* assert: track->num_used == TRACK_BUCKET_COUNT */ |
| idx = track->num_used - 1; |
| } |
| if ((rs = &track->info[idx])->bucket == bucket) { |
| return rs; |
| } |
| } |
| } |
| |
| /* Only create a new read_status_t when asked. */ |
| if (!create_rs) |
| return NULL; |
| |
| if (track->num_used < TRACK_BUCKET_COUNT) { |
| /* We're still filling up the ring. */ |
| ++track->num_used; |
| } |
| |
| rs = &track->info[track->next_index]; |
| rs->bucket = bucket; |
| rs->last = APR_SUCCESS; /* ### the right initial value? */ |
| |
| if (++track->next_index == TRACK_BUCKET_COUNT) |
| track->next_index = 0; |
| |
| return rs; |
| } |
| |
| #endif /* SERF_DEBUG_BUCKET_USE */ |
| |
| |
| apr_status_t serf_debug__record_read( |
| const serf_bucket_t *bucket, |
| apr_status_t status) |
| { |
| #ifndef SERF_DEBUG_BUCKET_USE |
| return status; |
| #else |
| |
| track_state_t *track = bucket->allocator->track; |
| read_status_t *rs = find_read_status(track, bucket, 1); |
| |
| /* Validate that the previous status value allowed for another read. */ |
| if (SERF_BUCKET_READ_ERROR(rs->last)) { |
| /* Somebody read when they weren't supposed to. Bail. */ |
| /*abort(); */ |
| fprintf(stderr, "Should not read from %p again, last_status=%d, status=%d\n", |
| bucket, rs->last, status); |
| } |
| |
| /* Save the current status for later. */ |
| rs->last = status; |
| |
| return status; |
| #endif |
| } |
| |
| |
| void serf_debug__entered_loop(serf_bucket_alloc_t *allocator) |
| { |
| #ifdef SERF_DEBUG_BUCKET_USE |
| |
| track_state_t *track = allocator->track; |
| read_status_t *rs = &track->info[0]; |
| |
| for ( ; track->num_used; --track->num_used, ++rs ) { |
| if (rs->last == APR_SUCCESS) { |
| /* Somebody should have read this bucket again. */ |
| abort(); |
| } |
| |
| /* ### other status values? */ |
| } |
| |
| /* num_used was reset. also need to reset the next index. */ |
| track->next_index = 0; |
| |
| #endif |
| } |
| |
| |
| void serf_debug__closed_conn(serf_bucket_alloc_t *allocator) |
| { |
| #ifdef SERF_DEBUG_BUCKET_USE |
| |
| /* Just reset the number used so that we don't examine the info[] */ |
| allocator->track->num_used = 0; |
| allocator->track->next_index = 0; |
| |
| #endif |
| } |
| |
| |
| void serf_debug__bucket_destroy(const serf_bucket_t *bucket) |
| { |
| #ifdef SERF_DEBUG_BUCKET_USE |
| |
| track_state_t *track = bucket->allocator->track; |
| read_status_t *rs = find_read_status(track, bucket, 0); |
| |
| if (rs != NULL |
| && !APR_STATUS_IS_EOF(rs->last) |
| && !SERF_BUCKET_READ_ERROR(rs->last)) { |
| /* The bucket was destroyed before it was read to completion. */ |
| |
| serf_bucket_t *bkt; |
| /* Special exception for socket buckets. If a connection remains |
| * open, they are not read to completion. |
| */ |
| if (SERF_BUCKET_IS_SOCKET(bucket)) |
| return; |
| |
| /* Ditto for SSL Decrypt buckets. */ |
| if (SERF_BUCKET_IS_SSL_DECRYPT(bucket)) |
| return; |
| |
| /* Ditto for SSL Encrypt buckets. */ |
| if (SERF_BUCKET_IS_SSL_ENCRYPT(bucket)) |
| return; |
| |
| /* Ditto for barrier buckets. */ |
| if (SERF_BUCKET_IS_BARRIER(bucket)) |
| return; |
| |
| if (SERF_BUCKET_IS_AGGREGATE(bucket)) { |
| apr_status_t status; |
| const char *data; |
| apr_size_t len; |
| |
| serf_bucket_aggregate_hold_open(bucket, NULL, NULL); |
| |
| status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, |
| &data, &len); |
| |
| if (APR_STATUS_IS_EOF(status) && !len) |
| return; |
| } |
| |
| bkt = serf_bucket_read_bucket((serf_bucket_t*)bucket, |
| &serf_bucket_type_ssl_encrypt); |
| |
| if (bkt != NULL) { |
| serf_bucket_destroy(bkt); |
| return; |
| } |
| |
| abort(); |
| } |
| |
| #endif |
| } |
| |
| |
| void serf_debug__bucket_alloc_check( |
| serf_bucket_alloc_t *allocator) |
| { |
| #ifdef SERF_DEBUG_BUCKET_USE |
| if (allocator->num_alloc != 0) { |
| abort(); |
| } |
| #endif |
| } |
| |