Mozilla 2/Memory/OOM API

From MozillaWiki
< Mozilla 2‎ | Memory
Revision as of 05:08, 21 May 2008 by Jasone (talk | contribs) (reserve_cur() --> reserve_current())
Jump to navigation Jump to search

Rationale

Memory allocation is an integral part of any non-trivial application, and in principle any allocation can fail due to being Out Of Memory (OOM). The traditional approach to OOM handling is to check for failure at every allocation point in the application and execute recovery code. Unfortunately, such recovery can be quite complex, depending on the surrounding code. Even worse, actual OOM conditions are rare, so the recovery code is very difficult to adequately test.

We need to improve our OOM handling approach so that it is possible to reliably and efficiently run with constrained memory resources. This is particularly important when running on mobile platforms.

Standard can-fail API

The standard memory allocation functions can fail due to an OOM condition. If failure occurs, each function sets the return value accordingly, and it is up to the caller to deal appropriately with the error.

void *malloc(size_t size);
void *valloc(size_t size); /* Legacy compatibility function. */
void *calloc(size_t num, size_t size);
void *realloc(void *ptr, size_t size);
int posix_memalign(void **memptr, size_t alignment, size_t size);
void *memalign(size_t alignment, size_t size);

New no-fail API

The no-fail functions will either succeed or terminate the application before returning. See later sections for related information on internal mechanisms that are irrelevant to the caller of these functions. There is a long history of using xmalloc(), xrealloc(), etc. as no-fail wrappers around the standard allocation functions, which motivates the function naming. xvalloc() and xposix_memalign() are omitted since xmemalign() suffices.

void *xmalloc(size_t size);
void *xcalloc(size_t num, size_t size);
void *xrealloc(void *ptr, size_t size);
void *xmemalign(size_t alignment, size_t size);

Memory reserve API

The allocator will maintain a memory reserve that reduces the likelihood of unsatisfiable allocation requests. The reserve can be configured and queried. Additionally, callback functions can be set, so that when the reserve is low/insufficient, those callback functions are given an opportunity to free memory.

reserve_current() returns the current reserve size. Under normal operating conditions, the current reserve will be ≥ the value accessed via reserve_[set_]threshold().

size_t reserve_current(void);
size_t reserve_threshold(void);
bool reserve_set_threshold(size_t threshold);

If the reserve size drops below reserve_threshold(), the "reserve low" callback will be called. The callback function will not be called again unless reserve_low_cb_reset() is called by the application or the callback function returns true (semantically equivalent to reserve_low_cb_reset()). The default callback function does nothing and returns false, thus potentially allowing the reserve to become exhausted.

typedef bool reserve_low_cb_t(void *ctx); /* ctx is opaque app data. */
void reserve_set_low_cb(reserve_low_cb_t *cb, void *ctx);
void reserve_low_cb_reset(void);

If an allocation request cannot be satisfied, the "reserve critical" callback will be called. The callback function may be called repeatedly unless it returns true, indicating that no more memory can be made available. The default callback function does nothing and returns true, thus allowing the allocator to terminate the application.

typedef bool reserve_crit_cb_t(void *ctx, size_t size);
/* size is allocation request size. */ void reserve_set_crit_cb(reserve_crit_cb_t *cb, void *ctx);

If an allocation request permanently fails (i.e. the "reserve critical" callback fails to provide adequate memory to satisfy the request), the "reserve fail" callback will be called. If the callback function returns, the allocator will abort the application. The default callback function does nothing, thus allowing the allocator to abort.

typedef void reserve_fail_cb_t(void *ctx);
void reserve_set_fail_cb(reserve_fail_cb_t *cb, void *ctx);

Implementation (non-)details

Internally, the allocator may manage the reserve as non-contiguous chunks of memory. This means that, for example, even with a reserve of 10MiB, the allocator may fail to allocate a 2MiB object. Similarly, even if the application makes 10MiB of non-contiguous memory available, the reserve may not increase at all, due to memory fragmentation.

If the reserve threshold is not a multiple of the allocator's internal chunk size, the actual threshold may effectively be rounded up to the nearest multiple of the chunk size. This only matters in that it limits the effective granularity with which the reserve can be configured. The chunk size for jemalloc can be queried via jemalloc_stats()[1].