/////////////////////////////////////////////////////////////////////////////// // /// \file memory_limiter.c /// \brief Limitting memory usage // // Copyright (C) 2007 Lasse Collin // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // /////////////////////////////////////////////////////////////////////////////// #include "common.h" /// Rounds an unsigned integer upwards to the next multiple. #define my_ceil(num, multiple) \ ((num) + (((multiple) - ((num) % (multiple))) % (multiple))) /// Add approximated overhead of malloc() to size and round upwards to the /// next multiple of 2 * sizeof(size_t). I suppose that most malloc() /// implementations align small allocations this way, but the overhead /// varies due to several reasons (free lists, mmap() usage etc.). /// /// This doesn't need to be exact at all. It's enough to take into account /// that there is some overhead. That way our memory usage count won't be /// horribly wrong if we are used to allocate lots of small memory chunks. #define malloc_ceil(size) \ my_ceil((size) + 2 * sizeof(void *), 2 * sizeof(size_t)) typedef struct lzma_memlimit_list_s lzma_memlimit_list; struct lzma_memlimit_list_s { lzma_memlimit_list *next; void *ptr; size_t size; }; struct lzma_memlimit_s { /// List of allocated memory chunks lzma_memlimit_list *list; /// Number of bytes currently allocated; this includes the memory /// needed for the helper structures. size_t used; /// Memory usage limit size_t limit; /// Maximum amount of memory that have been or would have been needed. /// That is, this is updated also if memory allocation fails, letting /// the application check how much memory was tried to be allocated /// in total. size_t max; /// True if lzma_memlimit_alloc() has returned NULL due to memory /// usage limit. bool limit_reached; }; extern LZMA_API lzma_memlimit * lzma_memlimit_create(size_t limit) { const size_t base_size = malloc_ceil(sizeof(lzma_memlimit)); if (limit < base_size) return NULL; lzma_memlimit *mem = malloc(sizeof(lzma_memlimit)); if (mem != NULL) { mem->list = NULL; mem->used = base_size; mem->limit = limit; mem->max = base_size; mem->limit_reached = false; } return mem; } extern LZMA_API void lzma_memlimit_set(lzma_memlimit *mem, size_t limit) { mem->limit = limit; return; } extern LZMA_API size_t lzma_memlimit_get(const lzma_memlimit *mem) { return mem->limit; } extern LZMA_API size_t lzma_memlimit_used(const lzma_memlimit *mem) { return mem->used; } extern LZMA_API size_t lzma_memlimit_max(lzma_memlimit *mem, lzma_bool clear) { const size_t ret = mem->max; if (clear) mem->max = mem->used; return ret; } extern LZMA_API lzma_bool lzma_memlimit_reached(lzma_memlimit *mem, lzma_bool clear) { const bool ret = mem->limit_reached; if (clear) mem->limit_reached = false; return ret; } extern LZMA_API size_t lzma_memlimit_count(const lzma_memlimit *mem) { // This is slow; we could have a counter in lzma_memlimit // for fast version. I expect the primary use of this // function to be limited to easy checking of memory leaks, // in which this implementation is just fine. size_t count = 0; const lzma_memlimit_list *record = mem->list; while (record != NULL) { ++count; record = record->next; } return count; } extern LZMA_API void lzma_memlimit_end(lzma_memlimit *mem, lzma_bool free_allocated) { if (mem == NULL) return; lzma_memlimit_list *record = mem->list; while (record != NULL) { if (free_allocated) free(record->ptr); lzma_memlimit_list *tmp = record; record = record->next; free(tmp); } free(mem); return; } extern LZMA_API void * lzma_memlimit_alloc(lzma_memlimit *mem, size_t nmemb, size_t size) { // While liblzma always sets nmemb to one, do this multiplication // to make these functions usable e.g. with zlib and libbzip2. // Making sure that this doesn't overflow is up to the application. size *= nmemb; // Some malloc() implementations return NULL on malloc(0). We like // to get a non-NULL value. if (size == 0) size = 1; // Calculate how much memory we are going to allocate in reality. const size_t total_size = malloc_ceil(size) + malloc_ceil(sizeof(lzma_memlimit_list)); // Integer overflow protection for total_size and mem->used. if (total_size <= size || SIZE_MAX - total_size < mem->used) { mem->max = SIZE_MAX; mem->limit_reached = true; return NULL; } // Update the maximum memory requirement counter if needed. This // is updated even if memory allocation would fail or limit would // be reached. if (mem->used + total_size > mem->max) mem->max = mem->used + total_size; // Check if we would stay in the memory usage limits. We need to // check also that the current usage is in the limits, because // the application could have decreased the limit between calls // to this function. if (mem->limit < mem->used || mem->limit - mem->used < total_size) { mem->limit_reached = true; return NULL; } // Allocate separate memory chunks for lzma_memlimit_list and the // actual requested memory. Optimizing this to use only one // allocation is not a good idea, because applications may want to // detach lzma_extra structures that have been allocated with // lzma_memlimit_alloc(). lzma_memlimit_list *record = malloc(sizeof(lzma_memlimit_list)); void *ptr = malloc(size); if (record == NULL || ptr == NULL) { free(record); free(ptr); return NULL; } // Add the new entry to the beginning of the list. This should be // more efficient when freeing memory, because usually it is // "last allocated, first freed". record->next = mem->list; record->ptr = ptr; record->size = total_size; mem->list = record; mem->used += total_size; return ptr; } extern LZMA_API void lzma_memlimit_detach(lzma_memlimit *mem, void *ptr) { if (ptr == NULL || mem->list == NULL) return; lzma_memlimit_list *record = mem->list; lzma_memlimit_list *prev = NULL; while (record->ptr != ptr) { prev = record; record = record->next; if (record == NULL) return; } if (prev != NULL) prev->next = record->next; else mem->list = record->next; assert(mem->used >= record->size); mem->used -= record->size; free(record); return; } extern LZMA_API void lzma_memlimit_free(lzma_memlimit *mem, void *ptr) { if (ptr == NULL) return; lzma_memlimit_detach(mem, ptr); free(ptr); return; }