|
|
///////////////////////////////////////////////////////////////////////////////
//
/// \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;
}
|