mirror of
https://git.tukaani.org/xz.git
synced 2024-04-04 12:36:23 +02:00
Improved the memory limitter:
- Added lzma_memlimit_max() and lzma_memlimit_reached() API functions. - Added simple estimation of malloc()'s memory usage overhead. - Fixed integer overflow detection in lzma_memlimit_alloc(). - Made some white space cleanups and added more comments. The description of lzma_memlimit_max() in memlimit.h is bad and should be improved.
This commit is contained in:
parent
e0c3d0043d
commit
7b8fc7e6b5
2 changed files with 119 additions and 15 deletions
|
@ -92,6 +92,41 @@ extern size_t lzma_memlimit_get(const lzma_memlimit *mem);
|
||||||
extern size_t lzma_memlimit_used(const lzma_memlimit *mem);
|
extern size_t lzma_memlimit_used(const lzma_memlimit *mem);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Gets the maximum amount of memory required in total
|
||||||
|
*
|
||||||
|
* Returns how much memory was or would have been allocated at the same time.
|
||||||
|
* If lzma_memlimit_alloc() was requested so much memory that the limit
|
||||||
|
* would have been exceeded or malloc() simply ran out of memory, the
|
||||||
|
* requested amount is still included to the value returned by
|
||||||
|
* lzma_memlimit_max(). This may be used as a hint how much bigger memory
|
||||||
|
* limit would have been needed.
|
||||||
|
*
|
||||||
|
* If the clear flag is set, the internal variable holding the maximum
|
||||||
|
* value is set to the current memory usage (the same value as returned
|
||||||
|
* by lzma_memlimit_used()).
|
||||||
|
*
|
||||||
|
* \note Usually liblzma needs to allocate many chunks of memory, and
|
||||||
|
* displaying a message like "memory usage limit reached, at
|
||||||
|
* least 1024 bytes would have been needed" may be confusing,
|
||||||
|
* because the next allocation could have been e.g. 8 MiB.
|
||||||
|
*
|
||||||
|
* \todo The description of this function is unclear.
|
||||||
|
*/
|
||||||
|
extern size_t lzma_memlimit_max(lzma_memlimit *mem, lzma_bool clear);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Checks if memory limit was reached at some point
|
||||||
|
*
|
||||||
|
* This function is useful to find out if the reason for LZMA_MEM_ERROR
|
||||||
|
* was running out of memory or hitting the memory usage limit imposed
|
||||||
|
* by lzma_memlimit_alloc(). If the clear argument is true, the internal
|
||||||
|
* flag, that indicates that limit was reached, is cleared.
|
||||||
|
*/
|
||||||
|
extern lzma_bool lzma_memlimit_reached(lzma_memlimit *mem, lzma_bool clear);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Gets the number of allocations owned by the memory limitter
|
* \brief Gets the number of allocations owned by the memory limitter
|
||||||
*
|
*
|
||||||
|
|
|
@ -25,9 +25,16 @@
|
||||||
((num) + (((multiple) - ((num) % (multiple))) % (multiple)))
|
((num) + (((multiple) - ((num) % (multiple))) % (multiple)))
|
||||||
|
|
||||||
|
|
||||||
/// Rounds upwards to the next multiple of 2 * sizeof(void*).
|
/// Add approximated overhead of malloc() to size and round upwards to the
|
||||||
/// malloc() tends to align allocations this way.
|
/// next multiple of 2 * sizeof(size_t). I suppose that most malloc()
|
||||||
#define malloc_ceil(num) my_ceil(num, 2 * sizeof(void *))
|
/// 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;
|
typedef struct lzma_memlimit_list_s lzma_memlimit_list;
|
||||||
|
@ -39,24 +46,44 @@ struct lzma_memlimit_list_s {
|
||||||
|
|
||||||
|
|
||||||
struct lzma_memlimit_s {
|
struct lzma_memlimit_s {
|
||||||
size_t used;
|
/// List of allocated memory chunks
|
||||||
size_t limit;
|
|
||||||
lzma_memlimit_list *list;
|
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 *
|
extern LZMA_API lzma_memlimit *
|
||||||
lzma_memlimit_create(size_t limit)
|
lzma_memlimit_create(size_t limit)
|
||||||
{
|
{
|
||||||
if (limit < sizeof(lzma_memlimit))
|
const size_t base_size = malloc_ceil(sizeof(lzma_memlimit));
|
||||||
|
|
||||||
|
if (limit < base_size)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
lzma_memlimit *mem = malloc(sizeof(lzma_memlimit));
|
lzma_memlimit *mem = malloc(sizeof(lzma_memlimit));
|
||||||
|
|
||||||
if (mem != NULL) {
|
if (mem != NULL) {
|
||||||
mem->used = sizeof(lzma_memlimit);
|
|
||||||
mem->limit = limit;
|
|
||||||
mem->list = NULL;
|
mem->list = NULL;
|
||||||
|
mem->used = base_size;
|
||||||
|
mem->limit = limit;
|
||||||
|
mem->max = base_size;
|
||||||
|
mem->limit_reached = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return mem;
|
return mem;
|
||||||
|
@ -85,6 +112,30 @@ lzma_memlimit_used(const lzma_memlimit *mem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
extern LZMA_API size_t
|
||||||
lzma_memlimit_count(const lzma_memlimit *mem)
|
lzma_memlimit_count(const lzma_memlimit *mem)
|
||||||
{
|
{
|
||||||
|
@ -94,12 +145,12 @@ lzma_memlimit_count(const lzma_memlimit *mem)
|
||||||
// in which this implementation is just fine.
|
// in which this implementation is just fine.
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
const lzma_memlimit_list *record = mem->list;
|
const lzma_memlimit_list *record = mem->list;
|
||||||
|
|
||||||
while (record != NULL) {
|
while (record != NULL) {
|
||||||
++count;
|
++count;
|
||||||
record = record->next;
|
record = record->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,18 +191,36 @@ lzma_memlimit_alloc(lzma_memlimit *mem, size_t nmemb, size_t size)
|
||||||
size = 1;
|
size = 1;
|
||||||
|
|
||||||
// Calculate how much memory we are going to allocate in reality.
|
// Calculate how much memory we are going to allocate in reality.
|
||||||
// TODO: We should add some rough estimate how much malloc() needs
|
|
||||||
// for its internal structures.
|
|
||||||
const size_t total_size = malloc_ceil(size)
|
const size_t total_size = malloc_ceil(size)
|
||||||
+ malloc_ceil(sizeof(lzma_memlimit_list));
|
+ malloc_ceil(sizeof(lzma_memlimit_list));
|
||||||
|
|
||||||
// Integer overflow protection
|
// Integer overflow protection for total_size and mem->used.
|
||||||
if (SIZE_MAX - size <= total_size)
|
if (total_size <= size || SIZE_MAX - total_size < mem->used) {
|
||||||
|
mem->max = SIZE_MAX;
|
||||||
|
mem->limit_reached = true;
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (mem->limit < mem->used || mem->limit - mem->used < total_size)
|
// 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;
|
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));
|
lzma_memlimit_list *record = malloc(sizeof(lzma_memlimit_list));
|
||||||
void *ptr = malloc(size);
|
void *ptr = malloc(size);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue