/* memory.c * (c) 2002 Mikulas Patocka * This file is a part of the Links program, released under GPL. */ #include "links.h" struct cache_upcall { list_entry_1st int (*upcall)(int); unsigned char flags; list_entry_last unsigned char name[1]; }; static struct list_head cache_upcalls = { &cache_upcalls, &cache_upcalls }; /* cache_upcall */ void heap_trim(void) { #if defined(HAVE__HEAPMIN) _heapmin(); #endif #if defined(HAVE_MALLOC_TRIM) malloc_trim(0); #endif } int shrink_memory(int type, int flags) { struct cache_upcall *c; struct list_head *lc; int a = 0; foreach(struct cache_upcall, c, lc, cache_upcalls) { if (flags && !(c->flags & flags)) continue; a |= c->upcall(type); } #if defined(HAVE__HEAPMIN) || defined(HAVE_MALLOC_TRIM) { static uttime last_heapmin = 0; static uttime min_interval = 0; uttime now = get_time(); /* malloc_trim degrades performance (unlike _heapmin), so we call it less often */ if (type == SH_FREE_ALL || (now - last_heapmin >= min_interval && #if defined(HAVE_MALLOC_TRIM) now - last_heapmin >= MALLOC_TRIM_INTERVAL #else (a & ST_SOMETHING_FREED || now - last_heapmin >= HEAPMIN_INTERVAL) #endif )) { uttime after; heap_trim(); after = get_time(); min_interval = HEAPMIN_FACTOR * (after - now); last_heapmin = after; } } #endif return a; } void register_cache_upcall(int (*upcall)(int), int flags, unsigned char *name) { struct cache_upcall *c; c = mem_alloc(sizeof(struct cache_upcall) + strlen(cast_const_char name)); c->upcall = upcall; c->flags = (unsigned char)flags; strcpy(cast_char c->name, cast_const_char name); add_to_list(cache_upcalls, c); } void free_all_caches(void) { struct cache_upcall *c; struct list_head *lc; int a, b; do { a = 0; b = ~0; foreach(struct cache_upcall, c, lc, cache_upcalls) { int x = c->upcall(SH_FREE_ALL); a |= x; b &= x; } } while (a & ST_SOMETHING_FREED); if (!(b & ST_CACHE_EMPTY)) { unsigned char *m = init_str(); int l = 0; foreach(struct cache_upcall, c, lc, cache_upcalls) if (!(c->upcall(SH_FREE_ALL) & ST_CACHE_EMPTY)) { if (l) add_to_str(&m, &l, cast_uchar ", "); add_to_str(&m, &l, c->name); } internal_error("could not release entries from caches: %s", m); mem_free(m); } free_list(struct cache_upcall, cache_upcalls); } int malloc_try_hard = 0; int out_of_memory_fl(int flags, unsigned char *msg, size_t size, unsigned char *file, int line) { int sh; retry: sh = shrink_memory(SH_FREE_SOMETHING, flags); /*fprintf(stderr, "out of memory: %d, %d (%s,%d)\n", flags, sh, msg, size);*/ if (sh & ST_SOMETHING_FREED) return 1; if (flags) { flags = 0; goto retry; } if (!malloc_try_hard) { malloc_try_hard = 1; return 1; } if (!msg) return 0; fatal_tty_exit(); fprintf(stderr, "\n"); #ifdef LEAK_DEBUG fprintf(stderr, "Allocated: %lu bytes, %lu blocks\n", (unsigned long)mem_amount, (unsigned long)mem_blocks); #endif fprintf(stderr, "File cache: %lu bytes, %lu files, %lu locked, %lu loading\n", (unsigned long)cache_info(CI_BYTES), (unsigned long)cache_info(CI_FILES), (unsigned long)cache_info(CI_LOCKED), (unsigned long)cache_info(CI_LOADING)); #ifdef HAVE_ANY_COMPRESSION fprintf(stderr, "Decompressed cache: %lu bytes, %lu files, %lu locked\n", (unsigned long)decompress_info(CI_BYTES), (unsigned long)decompress_info(CI_FILES), (unsigned long)decompress_info(CI_LOCKED)); #endif #ifdef G if (F) { fprintf(stderr, "Image cache: %lu bytes, %lu files, %lu locked\n", (unsigned long)imgcache_info(CI_BYTES), (unsigned long)imgcache_info(CI_FILES), (unsigned long)imgcache_info(CI_LOCKED)); fprintf(stderr, "Font cache: %lu bytes, %lu letters\n", (unsigned long)fontcache_info(CI_BYTES), (unsigned long)fontcache_info(CI_FILES)); } #endif fprintf(stderr, "Formatted document cache: %lu documents, %lu locked\n", formatted_info(CI_FILES), formatted_info(CI_LOCKED)); fprintf(stderr, "DNS cache: %lu servers", dns_info(CI_FILES)); #ifdef SSL_SESSION_RESUME fprintf(stderr, ", TLS session cache: %lu servers", session_info(CI_FILES)); #endif fprintf(stderr, "\n"); if (file) fatal_exit("ERROR: out of memory (%s(%lu) at %s:%d returned NULL)", msg, (unsigned long)size, file, line); else fatal_exit("ERROR: out of memory (%s(%lu) returned NULL)", msg, (unsigned long)size); return 0; } #ifdef DEBUG_TEST_FREE struct debug_test_free_slot { list_entry_1st unsigned char *file; int line; unsigned long count; list_entry_last }; static struct list_head debug_test_free_slots = { &debug_test_free_slots, &debug_test_free_slots }; #define DEBUG_TEST_FREE_DEFAULT_PROB 1024 #define DEBUG_TEST_FREE_INIT_COUNT 16 void debug_test_free(unsigned char *file, int line) { struct debug_test_free_slot *sl = NULL; struct list_head *lsl; unsigned long prob; if (!file) { prob = DEBUG_TEST_FREE_DEFAULT_PROB; goto fixed_prob; } foreach(struct debug_test_free_slot, sl, lsl, debug_test_free_slots) { if (sl->line == line && (sl->file == file || !strcmp(cast_const_char sl->file, cast_const_char file))) { del_from_list(sl); goto have_it; } } retry: sl = malloc(sizeof(struct debug_test_free_slot)); if (!sl) { if (out_of_memory(0, NULL, 0)) goto retry; return; } sl->file = file; sl->line = line; sl->count = DEBUG_TEST_FREE_INIT_COUNT; have_it: add_to_list(debug_test_free_slots, sl); prob = sl->count; sl->count++; fixed_prob: if (!prob) prob = 1; if (!(random() % prob)) { if (shrink_memory(SH_FREE_SOMETHING, 0) & ST_SOMETHING_FREED) { /*if (sl) sl->count++;*/ } } } #endif