9 ms·
What happened to good old returning NULL when no memory is available? No let's do overcommit (malloc always works) and OOM-kill some random process when under
by ben_bai 9y ago
What happened to good old returning NULL when no memory is available?
No let's do overcommit (malloc always works) and OOM-kill some random process when under memory pressure!
- JeremyBanks 9y agoWe collectively decided that this is less annoying that introducing thousands of difficult error cases to handle in every application.
- IgorPartola 9y agoHow is it difficult to handle? void* xmalloc(size_t s) { void *p; if (!(p = malloc(s)) { panic(“OOM”); } return p; } If your process ran out of RAM, you get to quit. Why offload it on some other random process? This is how your database process runs out of memory, and your web workers get killed (or vice versa). In either case your system isn’t usable, but one is harder to debug. In fact having the above in stdlib would have been just as effective at fixing thousands of little bugs and would have collectively saved us all thousands if not more man hours of developing the OOM killer and then raging online about it.
- jacquesm 9y agoMalloc may very well succeed when there is not enough free memory in the system. You'll only know it when you try to write to memory and the system runs out of real memory to back your virtual memory. This can happen much later in time. Memory allocated by malloc is not necessarily there by the time you need it. Another issue is that a fork will simply copy the task state and set all the pages to 'copy on write' in the clone. That way the fork can execute very rapidly and only when the child process starts writing pages is there some actual memory overhead.
- geofft 9y agoBecause it's not necessarily your process that ran out of RAM. Imagine you have background process A which has allocated 1 GB of RAM just in case and never used it, and then process B tries to allocate maybe 100 MB, and the kernel says "No, I can't do that." Then process B quits. Is this really a better outcome than killing process A? On my current laptop, pulseaudio has 2.7 GB virtual memory and 17 MB physical memory. If I open a new Firefox tab, I would much prefer for pulseaudio to die than for Firefox to die. (You might say, "Well, pulseaudio shouldn't allocate 2.7 GB virtual memory," to which I'd say, "Sure, if you'd rather spend the thousands of hours fixing pulseaudio and every other program like it, go for it.") Also, "panic" isn't a standard C function - are you envisioning a panic that unwinds or one that doesn't? If it unwinds, what happens if you need to allocate memory during the unwind to save state? If it doesn't, why is it a better state of the world for Firefox to crash periodically and require me to recover my work, without even giving it the option to synchronize its state?
- floatboth 9y ago> pulseaudio has 2.7 GB virtual memory heh, anything compiled with GHC allocates a TERABYTE of virtual memory, good luck running that w/o overcommit
- concrete-faucet 9y agoWell, what about adding a new signal (SIGXMEM) with a default action of ignore? If the system is running low on memory it can send this to some or all processes and wait for a little bit to see if things get better. This is how iOS has handled things since version 2.0: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623063-applicationdidreceivememorywarni https://developer.apple.com/documentation/uikit/uiapplicatio... > It is strongly recommended that you implement this method. If your app does not release enough memory during low-memory conditions, the system may terminate it outright.
- JeremyBanks 9y agoIt's a good idea.
- geofft 9y agoSee section 11 "Memory Pressure" of https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt https://www.kernel.org/doc/Documentation/cgroup-v1/memory.tx... - there's a way to get notified via eventfd() if your current cgroup's memory gets low. I believe you can just do this on the root memory cgroup (/sys/fs/cgroup/memory/memory.pressure_level) if you're not setting up actual cgroups for your application. (Signals for asynchronous conditions are an awkward interface because they can interrupt you between any two assembly instructions. You're not able to release memory in the handler itself; you have to set a flag that gets handled by the main loop. So eventfd makes sense here. I'm assuming iOS is doing something similar by queueing an Objective-C method call. Signals make a lot more sense for segfaults and the like, where you're being interrupted at the exact instruction that isn't working and you need to handle it before executing any more instructions.)
- deleted 9y ago[deleted]
- walrus 9y agoIt's not the case that malloc always works under Linux. It may return NULL if the memory is not available. Try it out! Write a program that allocates half your memory, fills it with 0xFF, then sleeps. Run two instances of it. The malloc will fail in the second one. According to [1], the OOM killer is a consequence of the fork syscall, and can't be removed without breaking backward compatibility. [1] https://drewdevault.com/2018/01/02/The-case-against-fork.html https://drewdevault.com/2018/01/02/The-case-against-fork.htm...
- dooglius 9y agoThe man page for fork indicates that it can fail and return ENOMEM just fine. Technically, it's only supposed to be returned in special conditions where the kernel doesn't have enough memory, but this seems like much less of a breakage than going around killing random processes.
- twoodfin 9y agoThe point is more that processes holding on to a lot of memory often fork and exec and don’t actually need 2X their current allocation in-between. Yes, there are alternative APIs that avoid the need to temporarily hold on to that memory in both processes, but the idiom is still incredibly common. And it’s not unusual for server-style processing to fork without an exec and largely share their parent’s allocation via copy-on-write. That defers allocation until there’s a page fault on a simple memory write, which doesn’t have a particularly helpful mapping to C if you want to return failure to the copying process.
- geofft 9y agoIt certainly is permitted to fail by spec. The goal of the entire overcommit / OOM killer business is to yield better real-world outcomes. If I have overcommit off, and a program that allocates more than half of my physical memory (RAM + swap), it can't fork - even if it's going to immediately exec a tiny program. If I have overcommit on, it can fork and exec, and nobody gets OOM killed. The tradeoff is that, if I have overcommit on and the child process starts modifying every page instead of exec'ing, then the OOM killer triggers. The bet is that a) probably the child process will be killed, since it has the most physical memory in use (assuming the parent process has not touched every page it has allocated), so this isn't worse than preventing the child process in the first place, and b) this situation is rare.
- xxs 9y agomany applications just straight use mmap
- maxlybbert 9y agoYou can turn off overcommit. The basic idea isn’t that having malloc return NULL on failure is worse than killing a random process when any process runs out of memory. The idea is that programs often ask for memory that they don’t need, or don’t need right away. When a program actually uses too much memory, there isn’t a way to signal failure, so the OOM gets involved. If this is wrong, turn off overcommit. Unfortunately, overcommit is a system-wide setting, so individual programs can’t opt in or out.
- wilun 9y agoOh but there actually are ways to signal failure (flushing caches, etc.), and there probably could and should be even more, but still the OOM killer is "needed" as a last resort measure. I recently saw an interesting Windows VM feature, where a process can indicate that some pages can be sacrificed to the OS if needed, and another function to try to get them back unmodified if by chance they have not been. I don't know if similar Linux syscalls exists, but I found the principle interesting.
- BeeOnRope 9y agoYou can get a similar effect on recent Linux versions with madvise() with MADV_FREE, although the interface for determining if the page was freed is a bit awkward: the page will be appear as zeroed if the OS decided to free it. Depending on what you are storing there and whether zero data is valid it may be awkward to detect. You can cancel the free request by performing a write, which is also a bit of an awkward API.
- ycombobreaker 9y agoYou can set ulimits for memory (IIRC max vss, but check bash manpage). Then sbrk and mmap will honor the limits, which will cause malloc to fail gracefully.
- geofft 9y agoNote that this is a limit on virtual memory - the OOM killer triggers on running out of physical memory (or swap). It certainly helps more than not using it, but it's hard to tune exactly, because it ignores the specific thing that the OOM killer is designed to support: applications allocating large amounts of virtual memory and not using it so that no backing physical memory/swap needs to be allocated.
- burfog 9y agoThat doesn't work for the stack. Consider a recursive function. The amount of memory it could need is essentially infinite, though obviously stuff breaks when the address space wraps around or when the stack collides with a different allocation. So, do we just prohibit recursion? Do we need to have a privileged compiler (making signed executables) that refuses to generate code unless the stack usage can be proven at compile time? Pretty much, if recursion is supported, you have overcommit on some level. For example, if we force developers to specify a stack size, we still have a form of overcommit. We've just moved it to the developer making promises that he probably can't verify to be safe.
- dooglius 9y ago> So, do we just prohibit recursion? This is what MISRA C does > Do we need to have a privileged compiler (making signed executables) that refuses to generate code unless the stack usage can be proven at compile time? This is roughly what the Linux kernel does (I believe it's more a set of checks that aren't formally proven) So yes, if you want to have run a process reliably, you shouldn't be doing any potentially unbounded recursion. > We've just moved it to the developer making promises that he probably can't verify to be safe. I don't agree, programs get a pretty large amount of stack space by default, and as long as you aren't doing any crazy amount of alloca or recursion (which is in fact the case for most programs), you'll be fine. They may not be formally verified to be safe, but practically the are. OTOH, the OOM killer can take you down regardless of what you're doing, based on what other processes are doing, so developers can have no assurance whatsoever that their programs won't suddenly die. Anecdotally, I've only even seen stack overflows due to infinite recursion bugs that are caught during development, but I see OOM-related problems not at all infrequently.
- geofft 9y agoI don't think I'd call that "overcommit." Overcommit is when there's more virtual memory (allegedly) available than actual backing physical memory (RAM+swap). You don't need overcommit to implement a fixed-size stack: just put a guard page (a page table entry that is intentionally invalid and reserved) at the end of the stack. If the stack grows into the guard page, you get a segfault. The actual stack pages for each thread can still be physically reserved. It still does result in killed processes by default - but you can set up a segfault handler with an alternate (preallocated and non-overcommitted) stack which can handle it gracefully, unlike the OOM killer's SIGKILL. Or you can write your code so that each function call checks that stack space is available before using it. Go and Rust both used to do this, via a function called __morestack, because they both used to support segmented stacks. GCC and LLVM both support this scheme. (And for a while Rust still called __morestack for the purpose of printing a nicer error message without having to set a segfault handler: https://ldpreload.com/blog/stack-smashes-you https://ldpreload.com/blog/stack-smashes-you ) But "overcommit" isn't a synonym for "something that kills processes when they exceed their memory allocation."