mirror of
https://github.com/microsoft/mimalloc.git
synced 2025-05-04 22:49:32 +03:00
make retry on oom an option; revise size options to not overflow the long
This commit is contained in:
parent
e58fa376d4
commit
1b21415dfa
4 changed files with 43 additions and 26 deletions
|
@ -327,7 +327,7 @@ typedef enum mi_option_e {
|
||||||
mi_option_allow_large_os_pages, // allow large (2 or 4 MiB) OS pages, implies eager commit. If false, also disables THP for the process.
|
mi_option_allow_large_os_pages, // allow large (2 or 4 MiB) OS pages, implies eager commit. If false, also disables THP for the process.
|
||||||
mi_option_reserve_huge_os_pages, // reserve N huge OS pages (1GiB pages) at startup
|
mi_option_reserve_huge_os_pages, // reserve N huge OS pages (1GiB pages) at startup
|
||||||
mi_option_reserve_huge_os_pages_at, // reserve huge OS pages at a specific NUMA node
|
mi_option_reserve_huge_os_pages_at, // reserve huge OS pages at a specific NUMA node
|
||||||
mi_option_reserve_os_memory, // reserve specified amount of OS memory in an arena at startup
|
mi_option_reserve_os_memory, // reserve specified amount of OS memory in an arena at startup (internally, this value is in KiB; use `mi_option_get_size`)
|
||||||
mi_option_deprecated_segment_cache,
|
mi_option_deprecated_segment_cache,
|
||||||
mi_option_deprecated_page_reset,
|
mi_option_deprecated_page_reset,
|
||||||
mi_option_abandoned_page_purge, // immediately purge delayed purges on thread termination
|
mi_option_abandoned_page_purge, // immediately purge delayed purges on thread termination
|
||||||
|
@ -341,11 +341,12 @@ typedef enum mi_option_e {
|
||||||
mi_option_max_warnings, // issue at most N warning messages
|
mi_option_max_warnings, // issue at most N warning messages
|
||||||
mi_option_max_segment_reclaim, // max. percentage of the abandoned segments can be reclaimed per try (=10%)
|
mi_option_max_segment_reclaim, // max. percentage of the abandoned segments can be reclaimed per try (=10%)
|
||||||
mi_option_destroy_on_exit, // if set, release all memory on exit; sometimes used for dynamic unloading but can be unsafe
|
mi_option_destroy_on_exit, // if set, release all memory on exit; sometimes used for dynamic unloading but can be unsafe
|
||||||
mi_option_arena_reserve, // initial memory size in KiB for arena reservation (= 1 GiB on 64-bit)
|
mi_option_arena_reserve, // initial memory size for arena reservation (= 1 GiB on 64-bit) (internally, this value is in KiB; use `mi_option_get_size`)
|
||||||
mi_option_arena_purge_mult, // multiplier for `purge_delay` for the purging delay for arenas (=10)
|
mi_option_arena_purge_mult, // multiplier for `purge_delay` for the purging delay for arenas (=10)
|
||||||
mi_option_purge_extend_delay,
|
mi_option_purge_extend_delay,
|
||||||
mi_option_abandoned_reclaim_on_free, // allow to reclaim an abandoned segment on a free (=1)
|
mi_option_abandoned_reclaim_on_free, // allow to reclaim an abandoned segment on a free (=1)
|
||||||
mi_option_disallow_arena_alloc, // 1 = do not use arena's for allocation (except if using specific arena id's)
|
mi_option_disallow_arena_alloc, // 1 = do not use arena's for allocation (except if using specific arena id's)
|
||||||
|
mi_option_retry_on_oom, // retry on out-of-memory for N milli seconds (=400), set to 0 to disable retries. (only on windows)
|
||||||
_mi_option_last,
|
_mi_option_last,
|
||||||
// legacy option names
|
// legacy option names
|
||||||
mi_option_large_os_pages = mi_option_allow_large_os_pages,
|
mi_option_large_os_pages = mi_option_allow_large_os_pages,
|
||||||
|
|
|
@ -358,10 +358,10 @@ static inline bool mi_mul_overflow(size_t count, size_t size, size_t* total) {
|
||||||
}
|
}
|
||||||
#else /* __builtin_umul_overflow is unavailable */
|
#else /* __builtin_umul_overflow is unavailable */
|
||||||
static inline bool mi_mul_overflow(size_t count, size_t size, size_t* total) {
|
static inline bool mi_mul_overflow(size_t count, size_t size, size_t* total) {
|
||||||
#define MI_MUL_NO_OVERFLOW ((size_t)1 << (4*sizeof(size_t))) // sqrt(SIZE_MAX)
|
#define MI_MUL_COULD_OVERFLOW ((size_t)1 << (4*sizeof(size_t))) // sqrt(SIZE_MAX)
|
||||||
*total = count * size;
|
*total = count * size;
|
||||||
// note: gcc/clang optimize this to directly check the overflow flag
|
// note: gcc/clang optimize this to directly check the overflow flag
|
||||||
return ((size >= MI_MUL_NO_OVERFLOW || count >= MI_MUL_NO_OVERFLOW) && size > 0 && (SIZE_MAX / size) < count);
|
return ((size >= MI_MUL_COULD_OVERFLOW || count >= MI_MUL_COULD_OVERFLOW) && size > 0 && (SIZE_MAX / size) < count);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ static mi_option_desc_t options[_mi_option_last] =
|
||||||
{ 0, UNINIT, MI_OPTION_LEGACY(allow_large_os_pages,large_os_pages) }, // use large OS pages, use only with eager commit to prevent fragmentation of VMA's
|
{ 0, UNINIT, MI_OPTION_LEGACY(allow_large_os_pages,large_os_pages) }, // use large OS pages, use only with eager commit to prevent fragmentation of VMA's
|
||||||
{ 0, UNINIT, MI_OPTION(reserve_huge_os_pages) }, // per 1GiB huge pages
|
{ 0, UNINIT, MI_OPTION(reserve_huge_os_pages) }, // per 1GiB huge pages
|
||||||
{-1, UNINIT, MI_OPTION(reserve_huge_os_pages_at) }, // reserve huge pages at node N
|
{-1, UNINIT, MI_OPTION(reserve_huge_os_pages_at) }, // reserve huge pages at node N
|
||||||
{ 0, UNINIT, MI_OPTION(reserve_os_memory) }, // reserve OS memory in advance
|
{ 0, UNINIT, MI_OPTION(reserve_os_memory) }, // reserve N KiB OS memory in advance (use `option_get_size`)
|
||||||
{ 0, UNINIT, MI_OPTION(deprecated_segment_cache) }, // cache N segments per thread
|
{ 0, UNINIT, MI_OPTION(deprecated_segment_cache) }, // cache N segments per thread
|
||||||
{ 0, UNINIT, MI_OPTION(deprecated_page_reset) }, // reset page memory on free
|
{ 0, UNINIT, MI_OPTION(deprecated_page_reset) }, // reset page memory on free
|
||||||
{ 0, UNINIT, MI_OPTION(abandoned_page_purge) }, // purge free page memory when a thread terminates
|
{ 0, UNINIT, MI_OPTION(abandoned_page_purge) }, // purge free page memory when a thread terminates
|
||||||
|
@ -79,20 +79,21 @@ static mi_option_desc_t options[_mi_option_last] =
|
||||||
{ 0, UNINIT, MI_OPTION(use_numa_nodes) }, // 0 = use available numa nodes, otherwise use at most N nodes.
|
{ 0, UNINIT, MI_OPTION(use_numa_nodes) }, // 0 = use available numa nodes, otherwise use at most N nodes.
|
||||||
{ 0, UNINIT, MI_OPTION_LEGACY(disallow_os_alloc,limit_os_alloc) }, // 1 = do not use OS memory for allocation (but only reserved arenas)
|
{ 0, UNINIT, MI_OPTION_LEGACY(disallow_os_alloc,limit_os_alloc) }, // 1 = do not use OS memory for allocation (but only reserved arenas)
|
||||||
{ 100, UNINIT, MI_OPTION(os_tag) }, // only apple specific for now but might serve more or less related purpose
|
{ 100, UNINIT, MI_OPTION(os_tag) }, // only apple specific for now but might serve more or less related purpose
|
||||||
{ 16, UNINIT, MI_OPTION(max_errors) }, // maximum errors that are output
|
{ 32, UNINIT, MI_OPTION(max_errors) }, // maximum errors that are output
|
||||||
{ 16, UNINIT, MI_OPTION(max_warnings) }, // maximum warnings that are output
|
{ 32, UNINIT, MI_OPTION(max_warnings) }, // maximum warnings that are output
|
||||||
{ 10, UNINIT, MI_OPTION(max_segment_reclaim)}, // max. percentage of the abandoned segments per try.
|
{ 10, UNINIT, MI_OPTION(max_segment_reclaim)}, // max. percentage of the abandoned segments to be reclaimed per try.
|
||||||
{ 0, UNINIT, MI_OPTION(destroy_on_exit)}, // release all OS memory on process exit; careful with dangling pointer or after-exit frees!
|
{ 0, UNINIT, MI_OPTION(destroy_on_exit)}, // release all OS memory on process exit; careful with dangling pointer or after-exit frees!
|
||||||
#if (MI_INTPTR_SIZE>4)
|
#if (MI_INTPTR_SIZE>4)
|
||||||
{ 1024L * 1024L, UNINIT, MI_OPTION(arena_reserve) }, // reserve memory N KiB at a time
|
{ 1024L*1024L, UNINIT, MI_OPTION(arena_reserve) }, // reserve memory N KiB at a time (=1GiB) (use `option_get_size`)
|
||||||
#else
|
#else
|
||||||
{ 128L * 1024L, UNINIT, MI_OPTION(arena_reserve) },
|
{ 128L*1024L, UNINIT, MI_OPTION(arena_reserve) }, // =128MiB on 32-bit
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{ 10, UNINIT, MI_OPTION(arena_purge_mult) }, // purge delay multiplier for arena's
|
{ 10, UNINIT, MI_OPTION(arena_purge_mult) }, // purge delay multiplier for arena's
|
||||||
{ 1, UNINIT, MI_OPTION_LEGACY(purge_extend_delay, decommit_extend_delay) },
|
{ 1, UNINIT, MI_OPTION_LEGACY(purge_extend_delay, decommit_extend_delay) },
|
||||||
{ 1, UNINIT, MI_OPTION(abandoned_reclaim_on_free) },// reclaim an abandoned segment on a free
|
{ 1, UNINIT, MI_OPTION(abandoned_reclaim_on_free) },// reclaim an abandoned segment on a free
|
||||||
{ 0, UNINIT, MI_OPTION(disallow_arena_alloc) }, // 1 = do not use arena's for allocation (except if using specific arena id's)
|
{ 0, UNINIT, MI_OPTION(disallow_arena_alloc) }, // 1 = do not use arena's for allocation (except if using specific arena id's)
|
||||||
|
{ 400, UNINIT, MI_OPTION(retry_on_oom) }, // windows only: retry on out-of-memory for N milli seconds (=400), set to 0 to disable retries.
|
||||||
};
|
};
|
||||||
|
|
||||||
static void mi_option_init(mi_option_desc_t* desc);
|
static void mi_option_init(mi_option_desc_t* desc);
|
||||||
|
@ -136,8 +137,12 @@ mi_decl_nodiscard long mi_option_get_clamp(mi_option_t option, long min, long ma
|
||||||
|
|
||||||
mi_decl_nodiscard size_t mi_option_get_size(mi_option_t option) {
|
mi_decl_nodiscard size_t mi_option_get_size(mi_option_t option) {
|
||||||
mi_assert_internal(mi_option_has_size_in_kib(option));
|
mi_assert_internal(mi_option_has_size_in_kib(option));
|
||||||
long x = mi_option_get(option);
|
const long x = mi_option_get(option);
|
||||||
return (x < 0 ? 0 : (size_t)x * MI_KiB);
|
size_t size = (x < 0 ? 0 : (size_t)x);
|
||||||
|
if (mi_option_has_size_in_kib(option)) {
|
||||||
|
size *= MI_KiB;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mi_option_set(mi_option_t option, long value) {
|
void mi_option_set(mi_option_t option, long value) {
|
||||||
|
@ -480,14 +485,20 @@ static void mi_option_init(mi_option_desc_t* desc) {
|
||||||
else {
|
else {
|
||||||
char* end = buf;
|
char* end = buf;
|
||||||
long value = strtol(buf, &end, 10);
|
long value = strtol(buf, &end, 10);
|
||||||
if (desc->option == mi_option_reserve_os_memory || desc->option == mi_option_arena_reserve) {
|
if (mi_option_has_size_in_kib(desc->option)) {
|
||||||
// this option is interpreted in KiB to prevent overflow of `long`
|
// this option is interpreted in KiB to prevent overflow of `long` for large allocations
|
||||||
|
// (long is 32-bit on 64-bit windows, which allows for 4TiB max.)
|
||||||
|
size_t size = (value < 0 ? 0 : (size_t)value);
|
||||||
|
bool overflow = false;
|
||||||
if (*end == 'K') { end++; }
|
if (*end == 'K') { end++; }
|
||||||
else if (*end == 'M') { value *= MI_KiB; end++; }
|
else if (*end == 'M') { overflow = mi_mul_overflow(size,MI_KiB,&size); end++; }
|
||||||
else if (*end == 'G') { value *= MI_MiB; end++; }
|
else if (*end == 'G') { overflow = mi_mul_overflow(size,MI_MiB,&size); end++; }
|
||||||
else { value = (value + MI_KiB - 1) / MI_KiB; }
|
else if (*end == 'T') { overflow = mi_mul_overflow(size,MI_GiB,&size); end++; }
|
||||||
if (end[0] == 'I' && end[1] == 'B') { end += 2; }
|
else { size = (size + MI_KiB - 1) / MI_KiB; }
|
||||||
else if (*end == 'B') { end++; }
|
if (end[0] == 'I' && end[1] == 'B') { end += 2; } // KiB, MiB, GiB, TiB
|
||||||
|
else if (*end == 'B') { end++; } // Kb, Mb, Gb, Tb
|
||||||
|
if (overflow || size > MI_MAX_ALLOC_SIZE) { size = (MI_MAX_ALLOC_SIZE / MI_KiB); }
|
||||||
|
value = (size > LONG_MAX ? LONG_MAX : (long)size);
|
||||||
}
|
}
|
||||||
if (*end == 0) {
|
if (*end == 0) {
|
||||||
desc->value = value;
|
desc->value = value;
|
||||||
|
|
|
@ -220,19 +220,24 @@ static bool win_is_out_of_memory_error(DWORD err) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* win_virtual_alloc_prim(void* addr, size_t size, size_t try_alignment, DWORD flags) {
|
static void* win_virtual_alloc_prim(void* addr, size_t size, size_t try_alignment, DWORD flags) {
|
||||||
for (DWORD tries = 1; tries <= 5; tries++) {
|
long max_retry_msecs = mi_option_get_clamp(mi_option_retry_on_oom, 0, 2000); // at most 2 seconds
|
||||||
|
if (max_retry_msecs == 1) { max_retry_msecs = 100; } // if one sets the option to "true"
|
||||||
|
for (long tries = 1; tries <= 10; tries++) { // try at most 10 times (=2200ms)
|
||||||
void* p = win_virtual_alloc_prim_once(addr, size, try_alignment, flags);
|
void* p = win_virtual_alloc_prim_once(addr, size, try_alignment, flags);
|
||||||
if (p != NULL) {
|
if (p != NULL) {
|
||||||
// success, return the address
|
// success, return the address
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
else if (try_alignment < 2*MI_SEGMENT_ALIGN &&
|
else if (max_retry_msecs > 0 && (try_alignment <= 2*MI_SEGMENT_ALIGN) &&
|
||||||
(flags&MEM_COMMIT)!=0 && (flags&MEM_LARGE_PAGES)==0 &&
|
(flags&MEM_COMMIT) != 0 && (flags&MEM_LARGE_PAGES) == 0 &&
|
||||||
win_is_out_of_memory_error(GetLastError())) {
|
win_is_out_of_memory_error(GetLastError())) {
|
||||||
// if committing regular memory and being out-of-memory,
|
// if committing regular memory and being out-of-memory,
|
||||||
// keep trying for a bit in case memory frees up after all. See issue #894
|
// keep trying for a bit in case memory frees up after all. See issue #894
|
||||||
_mi_warning_message("out-of-memory on OS allocation, try again... (attempt %lu, 0x%zx bytes, error code: 0x%x, address: %p, alignment: 0x%zx, flags: 0x%x)\n", tries, size, GetLastError(), addr, try_alignment, flags);
|
_mi_warning_message("out-of-memory on OS allocation, try again... (attempt %lu, 0x%zx bytes, error code: 0x%x, address: %p, alignment: 0x%zx, flags: 0x%x)\n", tries, size, GetLastError(), addr, try_alignment, flags);
|
||||||
Sleep(tries*25 /* milliseconds */); // try for at most (1+2+3+4+5)x25 = 375ms
|
long sleep_msecs = tries*40; // increasing waits
|
||||||
|
if (sleep_msecs > max_retry_msecs) { sleep_msecs = max_retry_msecs; }
|
||||||
|
max_retry_msecs -= sleep_msecs;
|
||||||
|
Sleep(sleep_msecs);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// otherwise return with an error
|
// otherwise return with an error
|
||||||
|
|
Loading…
Add table
Reference in a new issue